aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
Diffstat (limited to 'engines')
-rw-r--r--engines/glk/glulxe/glulxe.cpp4
-rw-r--r--engines/glk/glulxe/glulxe.h120
-rw-r--r--engines/glk/glulxe/glulxe_types.h21
-rw-r--r--engines/glk/glulxe/serial.cpp1144
-rw-r--r--engines/glk/module.mk1
5 files changed, 1271 insertions, 19 deletions
diff --git a/engines/glk/glulxe/glulxe.cpp b/engines/glk/glulxe/glulxe.cpp
index d16ecfb8cc..9db784c969 100644
--- a/engines/glk/glulxe/glulxe.cpp
+++ b/engines/glk/glulxe/glulxe.cpp
@@ -40,7 +40,9 @@ Glulxe::Glulxe(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst,
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) {
+ 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) {
g_vm = this;
}
diff --git a/engines/glk/glulxe/glulxe.h b/engines/glk/glulxe/glulxe.h
index 5dac1444de..8402a1b75e 100644
--- a/engines/glk/glulxe/glulxe.h
+++ b/engines/glk/glulxe/glulxe.h
@@ -86,8 +86,8 @@ public:
* @{
*/
- uint heap_start = 0; ///< zero for inactive heap
- int alloc_count = 0;
+ uint heap_start; ///< zero for inactive heap
+ int alloc_count;
/* The heap_head/heap_tail is a doubly-linked list of blocks, both
free and allocated. It is kept in address order. It should be
@@ -104,8 +104,8 @@ public:
free-list. To make free more efficient, we could keep a hash
table of allocations.
*/
- heapblock_t *heap_head = NULL;
- heapblock_t *heap_tail = NULL;
+ heapblock_t *heap_head;
+ heapblock_t *heap_tail;
/**@}*/
@@ -119,6 +119,30 @@ public:
* for the first 128 opcodes, which are the ones used most frequently.
*/
const operandlist_t *fast_operandlist[0x80];
+
+ /**@}*/
+
+ /**
+ * \defgroup serial fields
+ * @{
+ */
+
+ /**
+ * This can be adjusted before startup by platform-specific startup code -- that is, preference code.
+ */
+ int max_undo_level;
+
+ int undo_chain_size;
+ int undo_chain_num;
+ byte **undo_chain;
+
+ /**
+ * This will contain a copy of RAM (ramstate to endmem) as it exists in the game file.
+ */
+ byte *ramcache;
+
+ /**@}*/
+
protected:
/**
* \defgroup glkop fields
@@ -140,7 +164,7 @@ protected:
classtable_t **classes;
/**@}*/
-protected:
+
/**
* \defgroup accel support methods
* @{
@@ -260,6 +284,34 @@ protected:
void fetchkey(unsigned char *keybuf, uint key, uint keysize, uint options);
/**@}*/
+
+ /**
+ * \defgroup serial support methods
+ * @{
+ */
+
+ uint write_memstate(dest_t *dest);
+ uint write_heapstate(dest_t *dest, int portable);
+ uint write_stackstate(dest_t *dest, int portable);
+ uint read_memstate(dest_t *dest, uint chunklen);
+ uint read_heapstate(dest_t *dest, uint chunklen, int portable, uint *sumlen, uint **summary);
+ uint read_stackstate(dest_t *dest, uint chunklen, int portable);
+ uint write_heapstate_sub(uint sumlen, uint *sumarray, dest_t *dest, int portable);
+ static int sort_heap_summary(void *p1, void *p2);
+
+ int read_byte(dest_t *dest, byte *val);
+ int read_short(dest_t *dest, uint16 *val);
+ int read_long(dest_t *dest, uint *val);
+
+ int write_byte(dest_t *dest, byte val);
+ int write_short(dest_t *dest, uint16 val);
+ int write_long(dest_t *dest, uint val);
+
+ int read_buffer(dest_t *dest, byte *ptr, uint len);
+ int reposition_write(dest_t *dest, uint pos);
+ int write_buffer(dest_t *dest, const byte *ptr, uint len);
+
+ /**@}*/
public:
/**
* Constructor
@@ -508,15 +560,6 @@ public:
* @{
*/
- int max_undo_level;
- int init_serial(void);
- void final_serial(void);
- uint perform_save(strid_t str);
- uint perform_restore(strid_t str, int fromshell);
- uint perform_saveundo(void);
- uint perform_restoreundo(void);
- uint perform_verify(void);
-
/**@}*/
/**
@@ -729,10 +772,6 @@ public:
*/
#ifdef FLOAT_SUPPORT
- /* You may have to edit the definition of gfloat32 to make sure it's really
- a 32-bit floating-point type. */
- typedef float gfloat32;
-
/* Uncomment this definition if your gfloat32 type is not a standard
IEEE-754 single-precision (32-bit) format. Normally, Glulxe assumes
that it can reinterpret-cast IEEE-754 int values into gfloat32
@@ -754,6 +793,51 @@ public:
#endif /* FLOAT_SUPPORT */
/**@}*/
+
+ /**
+ * \defgroup serial access methods
+ * @{
+ */
+
+ /**
+ * Set up the undo chain and anything else that needs to be set up.
+ */
+ bool init_serial();
+
+ /**
+ * Clean up memory when the VM shuts down.
+ */
+ void final_serial();
+
+ /**
+ * Write the state to the output stream. This returns 0 on success, 1 on failure.
+ */
+ uint perform_save(strid_t str);
+
+ /**
+ * Pull a state pointer from a stream. This returns 0 on success, 1 on failure. Note that if it succeeds,
+ * the frameptr, localsbase, and valstackbase registers are invalid; they must be rebuilt from the stack.
+ *
+ * If fromshell is true, the restore is being invoked by the library shell (an autorestore of some kind).
+ * This currently happens only in iosglk.
+ */
+ uint perform_restore(strid_t str, int fromshell);
+
+ /**
+ * Add a state pointer to the undo chain. This returns 0 on success, 1 on failure.
+ */
+ uint perform_saveundo();
+
+ /**
+ * Pull a state pointer from the undo chain. This returns 0 on success, 1 on failure.
+ * Note that if it succeeds, the frameptr, localsbase, and valstackbase registers are invalid;
+ * they must be rebuilt from the stack.
+ */
+ uint perform_restoreundo();
+
+ uint perform_verify();
+
+ /**@}*/
};
extern Glulxe *g_vm;
diff --git a/engines/glk/glulxe/glulxe_types.h b/engines/glk/glulxe/glulxe_types.h
index 50b62a0f15..5b14a6aaf8 100644
--- a/engines/glk/glulxe/glulxe_types.h
+++ b/engines/glk/glulxe/glulxe_types.h
@@ -266,6 +266,11 @@ enum gestulx {
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;
@@ -358,6 +363,22 @@ struct heapblock_struct {
};
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 {
+ int ismem;
+
+ /* If it's a Glk stream: */
+ strid_t str;
+
+ /* If it's a block of memory: */
+ byte *ptr;
+ uint pos;
+ uint size;
+};
+typedef dest_struct dest_t;
+
} // End of namespace Glulxe
} // End of namespace Glk
diff --git a/engines/glk/glulxe/serial.cpp b/engines/glk/glulxe/serial.cpp
new file mode 100644
index 0000000000..66464e80f9
--- /dev/null
+++ b/engines/glk/glulxe/serial.cpp
@@ -0,0 +1,1144 @@
+/* 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 {
+
+#define IFFID(c1, c2, c3, c4) MKTAG(c1, c2, c3, c4)
+
+bool Glulxe::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;
+ glk_stream_set_position(gamefile, gamefile_start+ramstart, seekmode_Start);
+ res = glk_get_buffer_stream(gamefile, (char *)ramcache, len);
+ if (res != len)
+ return false;
+ }
+#endif /* SERIALIZE_CACHE_RAM */
+
+ return true;
+}
+
+void Glulxe::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 = NULL;
+ undo_chain_size = 0;
+ undo_chain_num = 0;
+
+#ifdef SERIALIZE_CACHE_RAM
+ if (ramcache) {
+ glulx_free(ramcache);
+ ramcache = NULL;
+ }
+#endif /* SERIALIZE_CACHE_RAM */
+}
+
+uint Glulxe::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;
+ dest.size = 0;
+ dest.pos = 0;
+ dest.ptr = NULL;
+ dest.str = NULL;
+
+ 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] = NULL;
+ }
+ 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 = NULL;
+ }
+ else {
+ /* It didn't work. */
+ if (dest.ptr) {
+ glulx_free(dest.ptr);
+ dest.ptr = NULL;
+ }
+ }
+
+ return res;
+}
+
+uint Glulxe::perform_restoreundo() {
+ dest_t dest;
+ uint res, val = 0;
+ uint heapsumlen = 0;
+ uint *heapsumarr = NULL;
+
+ /* If profiling is enabled and active then fail. */
+ #if 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.size = 0;
+ dest.pos = 0;
+ dest.ptr = undo_chain[0];
+ dest.str = NULL;
+
+ 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 = NULL;
+ }
+ else {
+ /* It didn't work. */
+ dest.ptr = NULL;
+ }
+
+ return res;
+}
+
+uint Glulxe::perform_save(strid_t str) {
+ dest_t dest;
+ int ix;
+ uint res, lx, val;
+ uint memstart = 0, memlen = 0, stackstart = 0, stacklen = 0;
+ uint heapstart = 0, heaplen = 0, filestart = 0, filelen = 0;
+
+ stream_get_iosys(&val, &lx);
+ if (val != 2) {
+ /* Not using the Glk I/O system, so bail. This function only
+ knows how to write to a Glk stream. */
+ fatal_error("Streams are only available in Glk I/O system.");
+ }
+
+ if (str == 0)
+ return 1;
+
+ dest.ismem = false;
+ dest.size = 0;
+ dest.pos = 0;
+ dest.ptr = NULL;
+ dest.str = str;
+
+ res = 0;
+
+ /* Quetzal header. */
+ if (res == 0) {
+ res = write_long(&dest, IFFID('F', 'O', 'R', 'M'));
+ }
+ if (res == 0) {
+ res = write_long(&dest, 0); /* space for file length */
+ filestart = dest.pos;
+ }
+
+ if (res == 0) {
+ res = write_long(&dest, IFFID('I', 'F', 'Z', 'S')); /* ### ? */
+ }
+
+ /* Header chunk. This is the first 128 bytes of memory. */
+ if (res == 0) {
+ res = write_long(&dest, IFFID('I', 'F', 'h', 'd'));
+ }
+ if (res == 0) {
+ res = write_long(&dest, 128);
+ }
+ for (ix=0; res==0 && ix<128; ix++) {
+ res = write_byte(&dest, Mem1(ix));
+ }
+ /* Always even, so no padding necessary. */
+
+ /* Memory chunk. */
+ if (res == 0) {
+ res = write_long(&dest, IFFID('C', 'M', 'e', 'm'));
+ }
+ 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 && (memlen & 1) != 0) {
+ res = write_byte(&dest, 0);
+ }
+
+ /* Heap chunk. */
+ if (res == 0) {
+ res = write_long(&dest, IFFID('M', 'A', 'l', 'l'));
+ }
+ if (res == 0) {
+ res = write_long(&dest, 0); /* space for chunk length */
+ }
+ if (res == 0) {
+ heapstart = dest.pos;
+ res = write_heapstate(&dest, true);
+ heaplen = dest.pos - heapstart;
+ }
+ /* Always even, so no padding necessary. */
+
+ /* Stack chunk. */
+ if (res == 0) {
+ res = write_long(&dest, IFFID('S', 't', 'k', 's'));
+ }
+ if (res == 0) {
+ res = write_long(&dest, 0); /* space for chunk length */
+ }
+ if (res == 0) {
+ stackstart = dest.pos;
+ res = write_stackstate(&dest, true);
+ stacklen = dest.pos - stackstart;
+ }
+ if (res == 0 && (stacklen & 1) != 0) {
+ res = write_byte(&dest, 0);
+ }
+
+ filelen = dest.pos - filestart;
+
+ /* Okay, fill in all the lengths. */
+ 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) {
+ res = reposition_write(&dest, filestart-4);
+ }
+ if (res == 0) {
+ res = write_long(&dest, filelen);
+ }
+
+ /* All done. */
+
+ return res;
+}
+
+uint Glulxe::perform_restore(strid_t str, int fromshell) {
+ dest_t dest;
+ int ix;
+ uint lx, res, val;
+ uint filestart, filelen = 0;
+ uint heapsumlen = 0;
+ uint *heapsumarr = NULL;
+
+ /* If profiling is enabled and active then fail. */
+ #if VM_PROFILING
+ if (profile_profiling_active())
+ return 1;
+ #endif /* VM_PROFILING */
+
+ stream_get_iosys(&val, &lx);
+ if (val != 2 && !fromshell) {
+ /* Not using the Glk I/O system, so bail. This function only
+ knows how to read from a Glk stream. (But in the autorestore
+ case, iosys hasn't been set yet, so ignore this test.) */
+ fatal_error("Streams are only available in Glk I/O system.");
+ }
+
+ if (str == 0)
+ return 1;
+
+ dest.ismem = false;
+ dest.size = 0;
+ dest.pos = 0;
+ dest.ptr = NULL;
+ dest.str = str;
+
+ res = 0;
+
+ /* ### the format errors checked below should send error messages to
+ the current stream. */
+
+ if (res == 0) {
+ res = read_long(&dest, &val);
+ }
+ if (res == 0 && val != IFFID('F', 'O', 'R', 'M')) {
+ /* ### bad header */
+ return 1;
+ }
+ if (res == 0) {
+ res = read_long(&dest, &filelen);
+ }
+ filestart = dest.pos;
+
+ if (res == 0) {
+ res = read_long(&dest, &val);
+ }
+ if (res == 0 && val != IFFID('I', 'F', 'Z', 'S')) { /* ### ? */
+ /* ### bad header */
+ return 1;
+ }
+
+ while (res == 0 && dest.pos < filestart+filelen) {
+ /* Read a chunk and deal with it. */
+ uint chunktype=0, chunkstart=0, chunklen=0;
+ unsigned char dummy;
+
+ if (res == 0) {
+ res = read_long(&dest, &chunktype);
+ }
+ if (res == 0) {
+ res = read_long(&dest, &chunklen);
+ }
+ chunkstart = dest.pos;
+
+ if (chunktype == IFFID('I', 'F', 'h', 'd')) {
+ for (ix=0; res==0 && ix<128; ix++) {
+ res = read_byte(&dest, &dummy);
+ if (res == 0 && Mem1(ix) != dummy) {
+ /* ### non-matching header */
+ return 1;
+ }
+ }
+ }
+ else if (chunktype == IFFID('C', 'M', 'e', 'm')) {
+ res = read_memstate(&dest, chunklen);
+ }
+ else if (chunktype == IFFID('M', 'A', 'l', 'l')) {
+ res = read_heapstate(&dest, chunklen, true, &heapsumlen, &heapsumarr);
+ }
+ else if (chunktype == IFFID('S', 't', 'k', 's')) {
+ res = read_stackstate(&dest, chunklen, true);
+ }
+ else {
+ /* Unknown chunk type. Skip it. */
+ for (lx=0; res==0 && lx<chunklen; lx++) {
+ res = read_byte(&dest, &dummy);
+ }
+ }
+
+ if (chunkstart+chunklen != dest.pos) {
+ /* ### funny chunk length */
+ return 1;
+ }
+
+ if ((chunklen & 1) != 0) {
+ if (res == 0) {
+ res = read_byte(&dest, &dummy);
+ }
+ }
+ }
+
+ if (res == 0) {
+ 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);
+ }
+ }
+
+ if (res)
+ return 1;
+
+ return 0;
+}
+
+int Glulxe::reposition_write(dest_t *dest, uint pos) {
+ if (dest->ismem) {
+ dest->pos = pos;
+ } else {
+ glk_stream_set_position(dest->str, pos, seekmode_Start);
+ dest->pos = pos;
+ }
+
+ return 0;
+}
+
+int Glulxe::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 {
+ glk_put_buffer_stream(dest->str, (char *)ptr, len);
+ }
+
+ dest->pos += len;
+
+ return 0;
+}
+
+int Glulxe::read_buffer(dest_t *dest, unsigned char *ptr, uint len) {
+ uint newlen;
+
+ if (dest->ismem) {
+ memcpy(ptr, dest->ptr+dest->pos, len);
+ }
+ else {
+ newlen = glk_get_buffer_stream(dest->str, (char *)ptr, len);
+ if (newlen != len)
+ return 1;
+ }
+
+ dest->pos += len;
+
+ return 0;
+}
+
+int Glulxe::write_long(dest_t *dest, uint val) {
+ unsigned char buf[4];
+ Write4(buf, val);
+ return write_buffer(dest, buf, 4);
+}
+
+int Glulxe::write_short(dest_t *dest, uint16 val) {
+ unsigned char buf[2];
+ Write2(buf, val);
+ return write_buffer(dest, buf, 2);
+}
+
+int Glulxe::write_byte(dest_t *dest, unsigned char val) {
+ return write_buffer(dest, &val, 1);
+}
+
+int Glulxe::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 Glulxe::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 Glulxe::read_byte(dest_t *dest, byte *val) {
+ return read_buffer(dest, val, 1);
+}
+
+uint Glulxe::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 */
+ glk_stream_set_position(gamefile, gamefile_start+ramstart, seekmode_Start);
+#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 Glulxe::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 */
+ glk_stream_set_position(gamefile, gamefile_start+ramstart, seekmode_Start);
+#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 */
+ val = glk_get_char_stream(gamefile);
+ if (val == -1) {
+ fatal_error("The game file ended unexpectedly while restoring.");
+ }
+#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 Glulxe::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 Glulxe::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 Glulxe::sort_heap_summary(void *p1, void *p2) {
+ uint v1 = *(uint *)p1;
+ uint v2 = *(uint *)p2;
+
+ if (v1 < v2)
+ return -1;
+ if (v1 > v2)
+ return 1;
+ return 0;
+}
+
+uint Glulxe::read_heapstate(dest_t *dest, uint chunklen, int portable, uint *sumlen, uint **summary) {
+ uint res, count, lx;
+ uint *arr;
+
+ *sumlen = 0;
+ *summary = NULL;
+
+ 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 Glulxe::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 Glulxe::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 Glulxe::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;
+
+ glk_stream_set_position(gamefile, gamefile_start, seekmode_Start);
+ newsum = 0;
+
+ /* Read the header */
+ for (ix=0; ix<9; ix++) {
+ newlen = glk_get_buffer_stream(gamefile, (char *)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 = glk_get_buffer_stream(gamefile, (char *)buf, 4);
+ if (newlen != 4)
+ return 1;
+ val = Read4(buf);
+ newsum += val;
+ }
+
+ if (newsum != chksum)
+ return 1;
+
+ return 0;
+}
+
+} // End of namespace Glulxe
+} // End of namespace Glk
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index e09a63ec0a..6cd298e3fa 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -68,6 +68,7 @@ MODULE_OBJS := \
glulxe/heap.o \
glulxe/operand.o \
glulxe/search.o \
+ glulxe/serial.o \
magnetic/detection.o \
magnetic/magnetic.o \
scott/detection.o \