diff options
author | Paul Gilbert | 2019-04-15 20:20:33 -0700 |
---|---|---|
committer | Paul Gilbert | 2019-04-17 20:46:06 -0700 |
commit | 1c4649018388e7df15bc0787ed66b6dc7755db1d (patch) | |
tree | 15f54169008eca2a6b578ffb38f5aa401d7cd3a6 | |
parent | a6cf55862d8c4ece72fc1f0ecda083b78b853597 (diff) | |
download | scummvm-rg350-1c4649018388e7df15bc0787ed66b6dc7755db1d.tar.gz scummvm-rg350-1c4649018388e7df15bc0787ed66b6dc7755db1d.tar.bz2 scummvm-rg350-1c4649018388e7df15bc0787ed66b6dc7755db1d.zip |
GLK: GLULXE: Add serial methods
-rw-r--r-- | engines/glk/glulxe/glulxe.cpp | 4 | ||||
-rw-r--r-- | engines/glk/glulxe/glulxe.h | 120 | ||||
-rw-r--r-- | engines/glk/glulxe/glulxe_types.h | 21 | ||||
-rw-r--r-- | engines/glk/glulxe/serial.cpp | 1144 | ||||
-rw-r--r-- | engines/glk/module.mk | 1 |
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 \ |