/* 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_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 && lxismem) { 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= 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; pospos >= 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 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 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