mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
988 lines
20 KiB
C++
988 lines
20 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "glk/glulx/glulx.h"
|
|
|
|
namespace Glk {
|
|
namespace Glulx {
|
|
|
|
#define IFFID(c1, c2, c3, c4) MKTAG(c1, c2, c3, c4)
|
|
|
|
bool Glulx::init_serial() {
|
|
undo_chain_num = 0;
|
|
undo_chain_size = max_undo_level;
|
|
undo_chain = (unsigned char **)glulx_malloc(sizeof(unsigned char *) * undo_chain_size);
|
|
if (!undo_chain)
|
|
return false;
|
|
|
|
#ifdef SERIALIZE_CACHE_RAM
|
|
{
|
|
uint len = (endmem - ramstart);
|
|
uint res;
|
|
ramcache = (unsigned char *)glulx_malloc(sizeof(unsigned char *) * len);
|
|
if (!ramcache)
|
|
return false;
|
|
|
|
_gameFile.seek(gamefile_start + ramstart);
|
|
res = _gameFile.read(ramcache, len);
|
|
if (res != len)
|
|
return false;
|
|
}
|
|
#endif /* SERIALIZE_CACHE_RAM */
|
|
|
|
return true;
|
|
}
|
|
|
|
void Glulx::final_serial() {
|
|
if (undo_chain) {
|
|
int ix;
|
|
for (ix = 0; ix < undo_chain_num; ix++) {
|
|
glulx_free(undo_chain[ix]);
|
|
}
|
|
glulx_free(undo_chain);
|
|
}
|
|
undo_chain = nullptr;
|
|
undo_chain_size = 0;
|
|
undo_chain_num = 0;
|
|
|
|
#ifdef SERIALIZE_CACHE_RAM
|
|
if (ramcache) {
|
|
glulx_free(ramcache);
|
|
ramcache = nullptr;
|
|
}
|
|
#endif /* SERIALIZE_CACHE_RAM */
|
|
}
|
|
|
|
uint Glulx::perform_saveundo() {
|
|
dest_t dest;
|
|
uint res;
|
|
uint memstart = 0, memlen = 0, heapstart = 0, heaplen = 0;
|
|
uint stackstart = 0, stacklen = 0;
|
|
|
|
/* The format for undo-saves is simpler than for saves on disk. We
|
|
just have a memory chunk, a heap chunk, and a stack chunk, in
|
|
that order. We skip the IFF chunk headers (although the size
|
|
fields are still there.) We also don't bother with IFF's 16-bit
|
|
alignment. */
|
|
|
|
if (undo_chain_size == 0)
|
|
return 1;
|
|
|
|
dest._isMem = true;
|
|
|
|
res = 0;
|
|
if (res == 0) {
|
|
res = write_long(&dest, 0); /* space for chunk length */
|
|
}
|
|
if (res == 0) {
|
|
memstart = dest._pos;
|
|
res = write_memstate(&dest);
|
|
memlen = dest._pos - memstart;
|
|
}
|
|
if (res == 0) {
|
|
res = write_long(&dest, 0); /* space for chunk length */
|
|
}
|
|
if (res == 0) {
|
|
heapstart = dest._pos;
|
|
res = write_heapstate(&dest, false);
|
|
heaplen = dest._pos - heapstart;
|
|
}
|
|
if (res == 0) {
|
|
res = write_long(&dest, 0); /* space for chunk length */
|
|
}
|
|
if (res == 0) {
|
|
stackstart = dest._pos;
|
|
res = write_stackstate(&dest, false);
|
|
stacklen = dest._pos - stackstart;
|
|
}
|
|
|
|
if (res == 0) {
|
|
/* Trim it down to the perfect size. */
|
|
dest._ptr = (byte *)glulx_realloc(dest._ptr, dest._pos);
|
|
if (!dest._ptr)
|
|
res = 1;
|
|
}
|
|
if (res == 0) {
|
|
res = reposition_write(&dest, memstart - 4);
|
|
}
|
|
if (res == 0) {
|
|
res = write_long(&dest, memlen);
|
|
}
|
|
if (res == 0) {
|
|
res = reposition_write(&dest, heapstart - 4);
|
|
}
|
|
if (res == 0) {
|
|
res = write_long(&dest, heaplen);
|
|
}
|
|
if (res == 0) {
|
|
res = reposition_write(&dest, stackstart - 4);
|
|
}
|
|
if (res == 0) {
|
|
res = write_long(&dest, stacklen);
|
|
}
|
|
|
|
if (res == 0) {
|
|
/* It worked. */
|
|
if (undo_chain_num >= undo_chain_size) {
|
|
glulx_free(undo_chain[undo_chain_num - 1]);
|
|
undo_chain[undo_chain_num - 1] = nullptr;
|
|
}
|
|
if (undo_chain_size > 1)
|
|
memmove(undo_chain + 1, undo_chain,
|
|
(undo_chain_size - 1) * sizeof(unsigned char *));
|
|
undo_chain[0] = dest._ptr;
|
|
if (undo_chain_num < undo_chain_size)
|
|
undo_chain_num += 1;
|
|
dest._ptr = nullptr;
|
|
} else {
|
|
/* It didn't work. */
|
|
if (dest._ptr) {
|
|
glulx_free(dest._ptr);
|
|
dest._ptr = nullptr;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
uint Glulx::perform_restoreundo() {
|
|
dest_t dest;
|
|
uint res, val = 0;
|
|
uint heapsumlen = 0;
|
|
uint *heapsumarr = nullptr;
|
|
|
|
/* If profiling is enabled and active then fail. */
|
|
#ifdef VM_PROFILING
|
|
if (profile_profiling_active())
|
|
return 1;
|
|
#endif /* VM_PROFILING */
|
|
|
|
if (undo_chain_size == 0 || undo_chain_num == 0)
|
|
return 1;
|
|
|
|
dest._isMem = true;
|
|
dest._ptr = undo_chain[0];
|
|
|
|
res = 0;
|
|
if (res == 0) {
|
|
res = read_long(&dest, &val);
|
|
}
|
|
if (res == 0) {
|
|
res = read_memstate(&dest, val);
|
|
}
|
|
if (res == 0) {
|
|
res = read_long(&dest, &val);
|
|
}
|
|
if (res == 0) {
|
|
res = read_heapstate(&dest, val, false, &heapsumlen, &heapsumarr);
|
|
}
|
|
if (res == 0) {
|
|
res = read_long(&dest, &val);
|
|
}
|
|
if (res == 0) {
|
|
res = read_stackstate(&dest, val, false);
|
|
}
|
|
/* ### really, many of the failure modes of those calls ought to
|
|
cause fatal errors. The stack or main memory may be damaged now. */
|
|
|
|
if (res == 0) {
|
|
if (heapsumarr)
|
|
res = heap_apply_summary(heapsumlen, heapsumarr);
|
|
}
|
|
|
|
if (res == 0) {
|
|
/* It worked. */
|
|
if (undo_chain_size > 1)
|
|
memmove(undo_chain, undo_chain + 1,
|
|
(undo_chain_size - 1) * sizeof(unsigned char *));
|
|
undo_chain_num -= 1;
|
|
glulx_free(dest._ptr);
|
|
dest._ptr = nullptr;
|
|
} else {
|
|
/* It didn't work. */
|
|
dest._ptr = nullptr;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
Common::Error Glulx::readSaveData(Common::SeekableReadStream *rs) {
|
|
Common::ErrorCode errCode = Common::kNoError;
|
|
QuetzalReader r;
|
|
if (r.open(rs))
|
|
// Load in the savegame chunks
|
|
errCode = loadGameChunks(r).getCode();
|
|
|
|
return errCode;
|
|
}
|
|
|
|
Common::Error Glulx::writeGameData(Common::WriteStream *ws) {
|
|
QuetzalWriter w;
|
|
Common::ErrorCode errCode = saveGameChunks(w).getCode();
|
|
|
|
if (errCode == Common::kNoError) {
|
|
w.save(ws, _savegameDescription);
|
|
}
|
|
|
|
return errCode;
|
|
}
|
|
|
|
Common::Error Glulx::loadGameChunks(QuetzalReader &quetzal) {
|
|
uint res = 0;
|
|
uint heapsumlen = 0;
|
|
uint *heapsumarr = nullptr;
|
|
|
|
for (QuetzalReader::Iterator it = quetzal.begin();
|
|
it != quetzal.end() && !res; ++it) {
|
|
Common::SeekableReadStream *rs = it.getStream();
|
|
dest_t dest;
|
|
dest._src = rs;
|
|
|
|
switch ((*it)._id) {
|
|
case ID_IFhd:
|
|
for (int ix = 0; ix < 128 && !res; ix++) {
|
|
byte v = rs->readByte();
|
|
if (Mem1(ix) != v)
|
|
// ### non-matching header
|
|
res = 1;
|
|
}
|
|
break;
|
|
|
|
case ID_CMem:
|
|
res = read_memstate(&dest, rs->size());
|
|
break;
|
|
|
|
case MKTAG('M', 'A', 'l', 'l'):
|
|
res = read_heapstate(&dest, rs->size(), true, &heapsumlen, &heapsumarr);
|
|
break;
|
|
|
|
case ID_Stks:
|
|
res = read_stackstate(&dest, rs->size(), true);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
delete rs;
|
|
}
|
|
|
|
if (!res) {
|
|
if (heapsumarr) {
|
|
/* The summary might have come from any interpreter, so it could
|
|
be out of order. We'll sort it. */
|
|
glulx_sort(heapsumarr + 2, (heapsumlen - 2) / 2, 2 * sizeof(uint), &sort_heap_summary);
|
|
res = heap_apply_summary(heapsumlen, heapsumarr);
|
|
}
|
|
}
|
|
|
|
return res ? Common::kReadingFailed : Common::kNoError;
|
|
}
|
|
|
|
Common::Error Glulx::saveGameChunks(QuetzalWriter &quetzal) {
|
|
uint res = 0;
|
|
|
|
// IFHd
|
|
if (!res) {
|
|
Common::WriteStream &ws = quetzal.add(ID_IFhd);
|
|
for (int ix = 0; res == 0 && ix < 128; ix++)
|
|
ws.writeByte(Mem1(ix));
|
|
}
|
|
|
|
// CMem
|
|
if (!res) {
|
|
Common::WriteStream &ws = quetzal.add(ID_CMem);
|
|
dest_t dest;
|
|
dest._dest = &ws;
|
|
res = write_memstate(&dest);
|
|
}
|
|
|
|
// MAll
|
|
if (!res) {
|
|
Common::WriteStream &ws = quetzal.add(MKTAG('M', 'A', 'l', 'l'));
|
|
dest_t dest;
|
|
dest._dest = &ws;
|
|
res = write_heapstate(&dest, true);
|
|
}
|
|
|
|
// Stks
|
|
if (!res) {
|
|
Common::WriteStream &ws = quetzal.add(ID_Stks);
|
|
dest_t dest;
|
|
dest._dest = &ws;
|
|
res = write_stackstate(&dest, true);
|
|
}
|
|
|
|
// All done
|
|
return res ? Common::kUnknownError : Common::kNoError;
|
|
}
|
|
|
|
int Glulx::reposition_write(dest_t *dest, uint pos) {
|
|
if (dest->_isMem) {
|
|
dest->_pos = pos;
|
|
} else {
|
|
error("Seeking a WriteStream isn't allowed");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Glulx::write_buffer(dest_t *dest, const byte *ptr, uint len) {
|
|
if (dest->_isMem) {
|
|
if (dest->_pos + len > dest->_size) {
|
|
dest->_size = dest->_pos + len + 1024;
|
|
if (!dest->_ptr) {
|
|
dest->_ptr = (byte *)glulx_malloc(dest->_size);
|
|
} else {
|
|
dest->_ptr = (byte *)glulx_realloc(dest->_ptr, dest->_size);
|
|
}
|
|
if (!dest->_ptr)
|
|
return 1;
|
|
}
|
|
memcpy(dest->_ptr + dest->_pos, ptr, len);
|
|
} else {
|
|
dest->_dest->write(ptr, len);
|
|
}
|
|
|
|
dest->_pos += len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Glulx::read_buffer(dest_t *dest, byte *ptr, uint len) {
|
|
uint newlen;
|
|
|
|
if (dest->_isMem) {
|
|
memcpy(ptr, dest->_ptr + dest->_pos, len);
|
|
} else {
|
|
newlen = dest->_src->read(ptr, len);
|
|
if (newlen != len)
|
|
return 1;
|
|
}
|
|
|
|
dest->_pos += len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Glulx::write_long(dest_t *dest, uint val) {
|
|
unsigned char buf[4];
|
|
Write4(buf, val);
|
|
return write_buffer(dest, buf, 4);
|
|
}
|
|
|
|
int Glulx::write_short(dest_t *dest, uint16 val) {
|
|
unsigned char buf[2];
|
|
Write2(buf, val);
|
|
return write_buffer(dest, buf, 2);
|
|
}
|
|
|
|
int Glulx::write_byte(dest_t *dest, byte val) {
|
|
return write_buffer(dest, &val, 1);
|
|
}
|
|
|
|
int Glulx::read_long(dest_t *dest, uint *val) {
|
|
unsigned char buf[4];
|
|
int res = read_buffer(dest, buf, 4);
|
|
if (res)
|
|
return res;
|
|
*val = Read4(buf);
|
|
return 0;
|
|
}
|
|
|
|
int Glulx::read_short(dest_t *dest, uint16 *val) {
|
|
unsigned char buf[2];
|
|
int res = read_buffer(dest, buf, 2);
|
|
if (res)
|
|
return res;
|
|
*val = Read2(buf);
|
|
return 0;
|
|
}
|
|
|
|
int Glulx::read_byte(dest_t *dest, byte *val) {
|
|
return read_buffer(dest, val, 1);
|
|
}
|
|
|
|
uint Glulx::write_memstate(dest_t *dest) {
|
|
uint res, pos;
|
|
int val;
|
|
int runlen;
|
|
unsigned char ch;
|
|
#ifdef SERIALIZE_CACHE_RAM
|
|
uint cachepos;
|
|
#endif /* SERIALIZE_CACHE_RAM */
|
|
|
|
res = write_long(dest, endmem);
|
|
if (res)
|
|
return res;
|
|
|
|
runlen = 0;
|
|
|
|
#ifdef SERIALIZE_CACHE_RAM
|
|
cachepos = 0;
|
|
#else /* SERIALIZE_CACHE_RAM */
|
|
_gameFile.seek(gamefile_start + ramstart);
|
|
#endif /* SERIALIZE_CACHE_RAM */
|
|
|
|
for (pos = ramstart; pos < endmem; pos++) {
|
|
ch = Mem1(pos);
|
|
if (pos < endgamefile) {
|
|
#ifdef SERIALIZE_CACHE_RAM
|
|
val = ramcache[cachepos];
|
|
cachepos++;
|
|
#else /* SERIALIZE_CACHE_RAM */
|
|
val = glk_get_char_stream(gamefile);
|
|
if (val == -1) {
|
|
fatal_error("The game file ended unexpectedly while saving.");
|
|
}
|
|
#endif /* SERIALIZE_CACHE_RAM */
|
|
ch ^= (unsigned char)val;
|
|
}
|
|
if (ch == 0) {
|
|
runlen++;
|
|
} else {
|
|
/* Write any run we've got. */
|
|
while (runlen) {
|
|
if (runlen >= 0x100)
|
|
val = 0x100;
|
|
else
|
|
val = runlen;
|
|
res = write_byte(dest, 0);
|
|
if (res)
|
|
return res;
|
|
res = write_byte(dest, (val - 1));
|
|
if (res)
|
|
return res;
|
|
runlen -= val;
|
|
}
|
|
/* Write the byte we got. */
|
|
res = write_byte(dest, ch);
|
|
if (res)
|
|
return res;
|
|
}
|
|
}
|
|
/* It's possible we've got a run left over, but we don't write it. */
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint Glulx::read_memstate(dest_t *dest, uint chunklen) {
|
|
uint chunkend = dest->_pos + chunklen;
|
|
uint newlen;
|
|
uint res, pos;
|
|
int val;
|
|
int runlen;
|
|
unsigned char ch, ch2;
|
|
#ifdef SERIALIZE_CACHE_RAM
|
|
uint cachepos;
|
|
#endif /* SERIALIZE_CACHE_RAM */
|
|
|
|
heap_clear();
|
|
|
|
res = read_long(dest, &newlen);
|
|
if (res)
|
|
return res;
|
|
|
|
res = change_memsize(newlen, false);
|
|
if (res)
|
|
return res;
|
|
|
|
runlen = 0;
|
|
|
|
#ifdef SERIALIZE_CACHE_RAM
|
|
cachepos = 0;
|
|
#else /* SERIALIZE_CACHE_RAM */
|
|
_gameFile.seek(gamefile_start + ramstart);
|
|
#endif /* SERIALIZE_CACHE_RAM */
|
|
|
|
for (pos = ramstart; pos < endmem; pos++) {
|
|
if (pos < endgamefile) {
|
|
#ifdef SERIALIZE_CACHE_RAM
|
|
val = ramcache[cachepos];
|
|
cachepos++;
|
|
#else /* SERIALIZE_CACHE_RAM */
|
|
if (_gameFile.pos() >= _gameFile.size()) {
|
|
fatal_error("The game file ended unexpectedly while restoring.");
|
|
val = _gameFile.readByte();
|
|
}
|
|
#endif /* SERIALIZE_CACHE_RAM */
|
|
ch = (unsigned char)val;
|
|
} else {
|
|
ch = 0;
|
|
}
|
|
|
|
if (dest->_pos >= chunkend) {
|
|
/* we're into the final, unstored run. */
|
|
} else if (runlen) {
|
|
runlen--;
|
|
} else {
|
|
res = read_byte(dest, &ch2);
|
|
if (res)
|
|
return res;
|
|
if (ch2 == 0) {
|
|
res = read_byte(dest, &ch2);
|
|
if (res)
|
|
return res;
|
|
runlen = (uint)ch2;
|
|
} else {
|
|
ch ^= ch2;
|
|
}
|
|
}
|
|
|
|
if (pos >= protectstart && pos < protectend)
|
|
continue;
|
|
|
|
MemW1(pos, ch);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint Glulx::write_heapstate(dest_t *dest, int portable) {
|
|
uint res;
|
|
uint sumlen;
|
|
uint *sumarray;
|
|
|
|
res = heap_get_summary(&sumlen, &sumarray);
|
|
if (res)
|
|
return res;
|
|
|
|
if (!sumarray)
|
|
return 0; /* no heap */
|
|
|
|
res = write_heapstate_sub(sumlen, sumarray, dest, portable);
|
|
|
|
glulx_free(sumarray);
|
|
return res;
|
|
}
|
|
|
|
uint Glulx::write_heapstate_sub(uint sumlen, uint *sumarray, dest_t *dest, int portable) {
|
|
uint res, lx;
|
|
|
|
/* If we're storing for the purpose of undo, we don't need to do any
|
|
byte-swapping, because the result will only be used by this session. */
|
|
if (!portable) {
|
|
res = write_buffer(dest, (const byte *)sumarray, sumlen * sizeof(uint));
|
|
if (res)
|
|
return res;
|
|
return 0;
|
|
}
|
|
|
|
for (lx = 0; lx < sumlen; lx++) {
|
|
res = write_long(dest, sumarray[lx]);
|
|
if (res)
|
|
return res;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Glulx::sort_heap_summary(const void *p1, const void *p2) {
|
|
uint v1 = *(const uint *)p1;
|
|
uint v2 = *(const uint *)p2;
|
|
|
|
if (v1 < v2)
|
|
return -1;
|
|
if (v1 > v2)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
uint Glulx::read_heapstate(dest_t *dest, uint chunklen, int portable, uint *sumlen, uint **summary) {
|
|
uint res, count, lx;
|
|
uint *arr;
|
|
|
|
*sumlen = 0;
|
|
*summary = nullptr;
|
|
|
|
if (chunklen == 0)
|
|
return 0; /* no heap */
|
|
|
|
if (!portable) {
|
|
count = chunklen / sizeof(uint);
|
|
|
|
arr = (uint *)glulx_malloc(chunklen);
|
|
if (!arr)
|
|
return 1;
|
|
|
|
res = read_buffer(dest, (byte *)arr, chunklen);
|
|
if (res)
|
|
return res;
|
|
|
|
*sumlen = count;
|
|
*summary = arr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
count = chunklen / 4;
|
|
|
|
arr = (uint *)glulx_malloc(count * sizeof(uint));
|
|
if (!arr)
|
|
return 1;
|
|
|
|
for (lx = 0; lx < count; lx++) {
|
|
res = read_long(dest, arr + lx);
|
|
if (res)
|
|
return res;
|
|
}
|
|
|
|
*sumlen = count;
|
|
*summary = arr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint Glulx::write_stackstate(dest_t *dest, int portable) {
|
|
uint res;
|
|
uint lx;
|
|
uint lastframe;
|
|
|
|
/* If we're storing for the purpose of undo, we don't need to do any
|
|
byte-swapping, because the result will only be used by this session. */
|
|
if (!portable) {
|
|
res = write_buffer(dest, stack, stackptr);
|
|
if (res)
|
|
return res;
|
|
return 0;
|
|
}
|
|
|
|
/* Write a portable stack image. To do this, we have to write stack
|
|
frames in order, bottom to top. Remember that the last word of
|
|
every stack frame is a pointer to the beginning of that stack frame.
|
|
(This includes the last frame, because the save opcode pushes on
|
|
a call stub before it calls perform_save().) */
|
|
|
|
lastframe = (uint)(-1);
|
|
while (1) {
|
|
uint frameend, frm, frm2, frm3;
|
|
unsigned char loctype, loccount;
|
|
uint numlocals, frlen, locpos;
|
|
|
|
/* Find the next stack frame (after the one in lastframe). Sadly,
|
|
this requires searching the stack from the top down. We have to
|
|
do this for *every* frame, which takes N^2 time overall. But
|
|
save routines usually aren't nested very deep.
|
|
If it becomes a practical problem, we can build a stack-frame
|
|
array, which requires dynamic allocation. */
|
|
for (frm = stackptr, frameend = stackptr;
|
|
frm != 0 && (frm2 = Stk4(frm - 4)) != lastframe;
|
|
frameend = frm, frm = frm2) { };
|
|
|
|
/* Write out the frame. */
|
|
frm2 = frm;
|
|
|
|
frlen = Stk4(frm2);
|
|
frm2 += 4;
|
|
res = write_long(dest, frlen);
|
|
if (res)
|
|
return res;
|
|
locpos = Stk4(frm2);
|
|
frm2 += 4;
|
|
res = write_long(dest, locpos);
|
|
if (res)
|
|
return res;
|
|
|
|
frm3 = frm2;
|
|
|
|
numlocals = 0;
|
|
while (1) {
|
|
loctype = Stk1(frm2);
|
|
frm2 += 1;
|
|
loccount = Stk1(frm2);
|
|
frm2 += 1;
|
|
|
|
res = write_byte(dest, loctype);
|
|
if (res)
|
|
return res;
|
|
res = write_byte(dest, loccount);
|
|
if (res)
|
|
return res;
|
|
|
|
if (loctype == 0 && loccount == 0)
|
|
break;
|
|
|
|
numlocals++;
|
|
}
|
|
|
|
if ((numlocals & 1) == 0) {
|
|
res = write_byte(dest, 0);
|
|
if (res)
|
|
return res;
|
|
res = write_byte(dest, 0);
|
|
if (res)
|
|
return res;
|
|
frm2 += 2;
|
|
}
|
|
|
|
if (frm2 != frm + locpos)
|
|
fatal_error("Inconsistent stack frame during save.");
|
|
|
|
/* Write out the locals. */
|
|
for (lx = 0; lx < numlocals; lx++) {
|
|
loctype = Stk1(frm3);
|
|
frm3 += 1;
|
|
loccount = Stk1(frm3);
|
|
frm3 += 1;
|
|
|
|
if (loctype == 0 && loccount == 0)
|
|
break;
|
|
|
|
/* Put in up to 0, 1, or 3 bytes of padding, depending on loctype. */
|
|
while (frm2 & (loctype - 1)) {
|
|
res = write_byte(dest, 0);
|
|
if (res)
|
|
return res;
|
|
frm2 += 1;
|
|
}
|
|
|
|
/* Put in this set of locals. */
|
|
switch (loctype) {
|
|
|
|
case 1:
|
|
do {
|
|
res = write_byte(dest, Stk1(frm2));
|
|
if (res)
|
|
return res;
|
|
frm2 += 1;
|
|
loccount--;
|
|
} while (loccount);
|
|
break;
|
|
|
|
case 2:
|
|
do {
|
|
res = write_short(dest, Stk2(frm2));
|
|
if (res)
|
|
return res;
|
|
frm2 += 2;
|
|
loccount--;
|
|
} while (loccount);
|
|
break;
|
|
|
|
case 4:
|
|
do {
|
|
res = write_long(dest, Stk4(frm2));
|
|
if (res)
|
|
return res;
|
|
frm2 += 4;
|
|
loccount--;
|
|
} while (loccount);
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
if (frm2 != frm + frlen)
|
|
fatal_error("Inconsistent stack frame during save.");
|
|
|
|
while (frm2 < frameend) {
|
|
res = write_long(dest, Stk4(frm2));
|
|
if (res)
|
|
return res;
|
|
frm2 += 4;
|
|
}
|
|
|
|
/* Go on to the next frame. */
|
|
if (frameend == stackptr)
|
|
break; /* All done. */
|
|
lastframe = frm;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint Glulx::read_stackstate(dest_t *dest, uint chunklen, int portable) {
|
|
uint res;
|
|
uint frameend, frm, frm2, frm3, locpos, frlen, numlocals;
|
|
|
|
if (chunklen > stacksize)
|
|
return 1;
|
|
|
|
stackptr = chunklen;
|
|
frameptr = 0;
|
|
valstackbase = 0;
|
|
localsbase = 0;
|
|
|
|
if (!portable) {
|
|
res = read_buffer(dest, stack, stackptr);
|
|
if (res)
|
|
return res;
|
|
return 0;
|
|
}
|
|
|
|
/* This isn't going to be pleasant; we're going to read the data in
|
|
as a block, and then convert it in-place. */
|
|
res = read_buffer(dest, stack, stackptr);
|
|
if (res)
|
|
return res;
|
|
|
|
frameend = stackptr;
|
|
while (frameend != 0) {
|
|
/* Read the beginning-of-frame pointer. Remember, right now, the
|
|
whole frame is stored big-endian. So we have to read with the
|
|
Read*() macros, and then write with the StkW*() macros. */
|
|
frm = Read4(stack + (frameend - 4));
|
|
|
|
frm2 = frm;
|
|
|
|
frlen = Read4(stack + frm2);
|
|
StkW4(frm2, frlen);
|
|
frm2 += 4;
|
|
locpos = Read4(stack + frm2);
|
|
StkW4(frm2, locpos);
|
|
frm2 += 4;
|
|
|
|
/* The locals-format list is in bytes, so we don't have to convert it. */
|
|
frm3 = frm2;
|
|
frm2 = frm + locpos;
|
|
|
|
numlocals = 0;
|
|
|
|
while (1) {
|
|
unsigned char loctype, loccount;
|
|
loctype = Read1(stack + frm3);
|
|
frm3 += 1;
|
|
loccount = Read1(stack + frm3);
|
|
frm3 += 1;
|
|
|
|
if (loctype == 0 && loccount == 0)
|
|
break;
|
|
|
|
/* Skip up to 0, 1, or 3 bytes of padding, depending on loctype. */
|
|
while (frm2 & (loctype - 1)) {
|
|
StkW1(frm2, 0);
|
|
frm2++;
|
|
}
|
|
|
|
/* Convert this set of locals. */
|
|
switch (loctype) {
|
|
|
|
case 1:
|
|
do {
|
|
/* Don't need to convert bytes. */
|
|
frm2 += 1;
|
|
loccount--;
|
|
} while (loccount);
|
|
break;
|
|
|
|
case 2:
|
|
do {
|
|
uint16 loc = Read2(stack + frm2);
|
|
StkW2(frm2, loc);
|
|
frm2 += 2;
|
|
loccount--;
|
|
} while (loccount);
|
|
break;
|
|
|
|
case 4:
|
|
do {
|
|
uint loc = Read4(stack + frm2);
|
|
StkW4(frm2, loc);
|
|
frm2 += 4;
|
|
loccount--;
|
|
} while (loccount);
|
|
break;
|
|
|
|
}
|
|
|
|
numlocals++;
|
|
}
|
|
|
|
if ((numlocals & 1) == 0) {
|
|
StkW1(frm3, 0);
|
|
frm3++;
|
|
StkW1(frm3, 0);
|
|
frm3++;
|
|
}
|
|
|
|
if (frm3 != frm + locpos) {
|
|
return 1;
|
|
}
|
|
|
|
while (frm2 & 3) {
|
|
StkW1(frm2, 0);
|
|
frm2++;
|
|
}
|
|
|
|
if (frm2 != frm + frlen) {
|
|
return 1;
|
|
}
|
|
|
|
/* Now, the values pushed on the stack after the call frame itself.
|
|
This includes the stub. */
|
|
while (frm2 < frameend) {
|
|
uint loc = Read4(stack + frm2);
|
|
StkW4(frm2, loc);
|
|
frm2 += 4;
|
|
}
|
|
|
|
frameend = frm;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint Glulx::perform_verify() {
|
|
uint len, chksum = 0, newlen;
|
|
unsigned char buf[4];
|
|
uint val, newsum, ix;
|
|
|
|
len = gamefile_len;
|
|
|
|
if (len < 256 || (len & 0xFF) != 0)
|
|
return 1;
|
|
|
|
_gameFile.seek(gamefile_start);
|
|
newsum = 0;
|
|
|
|
/* Read the header */
|
|
for (ix = 0; ix < 9; ix++) {
|
|
newlen = _gameFile.read(buf, 4);
|
|
if (newlen != 4)
|
|
return 1;
|
|
val = Read4(buf);
|
|
if (ix == 3) {
|
|
if (len != val)
|
|
return 1;
|
|
}
|
|
if (ix == 8)
|
|
chksum = val;
|
|
else
|
|
newsum += val;
|
|
}
|
|
|
|
/* Read everything else */
|
|
for (; ix < len / 4; ix++) {
|
|
newlen = _gameFile.read(buf, 4);
|
|
if (newlen != 4)
|
|
return 1;
|
|
val = Read4(buf);
|
|
newsum += val;
|
|
}
|
|
|
|
if (newsum != chksum)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // End of namespace Glulx
|
|
} // End of namespace Glk
|