1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/glulx/glulx.h"
24 
25 namespace Glk {
26 namespace Glulx {
27 
28 #define IFFID(c1, c2, c3, c4) MKTAG(c1, c2, c3, c4)
29 
init_serial()30 bool Glulx::init_serial() {
31 	undo_chain_num = 0;
32 	undo_chain_size = max_undo_level;
33 	undo_chain = (unsigned char **)glulx_malloc(sizeof(unsigned char *) * undo_chain_size);
34 	if (!undo_chain)
35 		return false;
36 
37 #ifdef SERIALIZE_CACHE_RAM
38 	{
39 		uint len = (endmem - ramstart);
40 		uint res;
41 		ramcache = (unsigned char *)glulx_malloc(sizeof(unsigned char *) * len);
42 		if (!ramcache)
43 			return false;
44 
45 		_gameFile.seek(gamefile_start + ramstart);
46 		res = _gameFile.read(ramcache, len);
47 		if (res != len)
48 			return false;
49 	}
50 #endif /* SERIALIZE_CACHE_RAM */
51 
52 	return true;
53 }
54 
final_serial()55 void Glulx::final_serial() {
56 	if (undo_chain) {
57 		int ix;
58 		for (ix = 0; ix < undo_chain_num; ix++) {
59 			glulx_free(undo_chain[ix]);
60 		}
61 		glulx_free(undo_chain);
62 	}
63 	undo_chain = nullptr;
64 	undo_chain_size = 0;
65 	undo_chain_num = 0;
66 
67 #ifdef SERIALIZE_CACHE_RAM
68 	if (ramcache) {
69 		glulx_free(ramcache);
70 		ramcache = nullptr;
71 	}
72 #endif /* SERIALIZE_CACHE_RAM */
73 }
74 
perform_saveundo()75 uint Glulx::perform_saveundo() {
76 	dest_t dest;
77 	uint res;
78 	uint memstart = 0, memlen = 0, heapstart = 0, heaplen = 0;
79 	uint stackstart = 0, stacklen = 0;
80 
81 	/* The format for undo-saves is simpler than for saves on disk. We
82 	   just have a memory chunk, a heap chunk, and a stack chunk, in
83 	   that order. We skip the IFF chunk headers (although the size
84 	   fields are still there.) We also don't bother with IFF's 16-bit
85 	   alignment. */
86 
87 	if (undo_chain_size == 0)
88 		return 1;
89 
90 	dest._isMem = true;
91 
92 	res = 0;
93 	if (res == 0) {
94 		res = write_long(&dest, 0); /* space for chunk length */
95 	}
96 	if (res == 0) {
97 		memstart = dest._pos;
98 		res = write_memstate(&dest);
99 		memlen = dest._pos - memstart;
100 	}
101 	if (res == 0) {
102 		res = write_long(&dest, 0); /* space for chunk length */
103 	}
104 	if (res == 0) {
105 		heapstart = dest._pos;
106 		res = write_heapstate(&dest, false);
107 		heaplen = dest._pos - heapstart;
108 	}
109 	if (res == 0) {
110 		res = write_long(&dest, 0); /* space for chunk length */
111 	}
112 	if (res == 0) {
113 		stackstart = dest._pos;
114 		res = write_stackstate(&dest, false);
115 		stacklen = dest._pos - stackstart;
116 	}
117 
118 	if (res == 0) {
119 		/* Trim it down to the perfect size. */
120 		dest._ptr = (byte *)glulx_realloc(dest._ptr, dest._pos);
121 		if (!dest._ptr)
122 			res = 1;
123 	}
124 	if (res == 0) {
125 		res = reposition_write(&dest, memstart - 4);
126 	}
127 	if (res == 0) {
128 		res = write_long(&dest, memlen);
129 	}
130 	if (res == 0) {
131 		res = reposition_write(&dest, heapstart - 4);
132 	}
133 	if (res == 0) {
134 		res = write_long(&dest, heaplen);
135 	}
136 	if (res == 0) {
137 		res = reposition_write(&dest, stackstart - 4);
138 	}
139 	if (res == 0) {
140 		res = write_long(&dest, stacklen);
141 	}
142 
143 	if (res == 0) {
144 		/* It worked. */
145 		if (undo_chain_num >= undo_chain_size) {
146 			glulx_free(undo_chain[undo_chain_num - 1]);
147 			undo_chain[undo_chain_num - 1] = nullptr;
148 		}
149 		if (undo_chain_size > 1)
150 			memmove(undo_chain + 1, undo_chain,
151 			        (undo_chain_size - 1) * sizeof(unsigned char *));
152 		undo_chain[0] = dest._ptr;
153 		if (undo_chain_num < undo_chain_size)
154 			undo_chain_num += 1;
155 		dest._ptr = nullptr;
156 	} else {
157 		/* It didn't work. */
158 		if (dest._ptr) {
159 			glulx_free(dest._ptr);
160 			dest._ptr = nullptr;
161 		}
162 	}
163 
164 	return res;
165 }
166 
perform_restoreundo()167 uint Glulx::perform_restoreundo() {
168 	dest_t dest;
169 	uint res, val = 0;
170 	uint heapsumlen = 0;
171 	uint *heapsumarr = nullptr;
172 
173 	/* If profiling is enabled and active then fail. */
174 #if VM_PROFILING
175 	if (profile_profiling_active())
176 		return 1;
177 #endif /* VM_PROFILING */
178 
179 	if (undo_chain_size == 0 || undo_chain_num == 0)
180 		return 1;
181 
182 	dest._isMem = true;
183 	dest._ptr = undo_chain[0];
184 
185 	res = 0;
186 	if (res == 0) {
187 		res = read_long(&dest, &val);
188 	}
189 	if (res == 0) {
190 		res = read_memstate(&dest, val);
191 	}
192 	if (res == 0) {
193 		res = read_long(&dest, &val);
194 	}
195 	if (res == 0) {
196 		res = read_heapstate(&dest, val, false, &heapsumlen, &heapsumarr);
197 	}
198 	if (res == 0) {
199 		res = read_long(&dest, &val);
200 	}
201 	if (res == 0) {
202 		res = read_stackstate(&dest, val, false);
203 	}
204 	/* ### really, many of the failure modes of those calls ought to
205 	   cause fatal errors. The stack or main memory may be damaged now. */
206 
207 	if (res == 0) {
208 		if (heapsumarr)
209 			res = heap_apply_summary(heapsumlen, heapsumarr);
210 	}
211 
212 	if (res == 0) {
213 		/* It worked. */
214 		if (undo_chain_size > 1)
215 			memmove(undo_chain, undo_chain + 1,
216 			        (undo_chain_size - 1) * sizeof(unsigned char *));
217 		undo_chain_num -= 1;
218 		glulx_free(dest._ptr);
219 		dest._ptr = nullptr;
220 	} else {
221 		/* It didn't work. */
222 		dest._ptr = nullptr;
223 	}
224 
225 	return res;
226 }
227 
readSaveData(Common::SeekableReadStream * rs)228 Common::Error Glulx::readSaveData(Common::SeekableReadStream *rs) {
229 	Common::ErrorCode errCode = Common::kNoError;
230 	QuetzalReader r;
231 	if (r.open(rs))
232 		// Load in the savegame chunks
233 		errCode = loadGameChunks(r).getCode();
234 
235 	return errCode;
236 }
237 
writeGameData(Common::WriteStream * ws)238 Common::Error Glulx::writeGameData(Common::WriteStream *ws) {
239 	QuetzalWriter w;
240 	Common::ErrorCode errCode = saveGameChunks(w).getCode();
241 
242 	if (errCode == Common::kNoError) {
243 		w.save(ws, _savegameDescription);
244 	}
245 
246 	return errCode;
247 }
248 
loadGameChunks(QuetzalReader & quetzal)249 Common::Error Glulx::loadGameChunks(QuetzalReader &quetzal) {
250 	uint res = 0;
251 	uint heapsumlen = 0;
252 	uint *heapsumarr = nullptr;
253 
254 	for (QuetzalReader::Iterator it = quetzal.begin();
255 			it != quetzal.end() && !res; ++it) {
256 		Common::SeekableReadStream *rs = it.getStream();
257 		dest_t dest;
258 		dest._src = rs;
259 
260 		switch ((*it)._id) {
261 		case ID_IFhd:
262 			for (int ix = 0; ix < 128 && !res; ix++) {
263 				byte v = rs->readByte();
264 				if (Mem1(ix) != v)
265 					// ### non-matching header
266 					res = 1;
267 			}
268 			break;
269 
270 		case ID_CMem:
271 			res = read_memstate(&dest, rs->size());
272 			break;
273 
274 		case MKTAG('M', 'A', 'l', 'l'):
275 			res = read_heapstate(&dest, rs->size(), true, &heapsumlen, &heapsumarr);
276 			break;
277 
278 		case ID_Stks:
279 			res = read_stackstate(&dest, rs->size(), true);
280 			break;
281 
282 		default:
283 			break;
284 		}
285 
286 		delete rs;
287 	}
288 
289 	if (!res) {
290 		if (heapsumarr) {
291 			/* The summary might have come from any interpreter, so it could
292 			   be out of order. We'll sort it. */
293 			glulx_sort(heapsumarr + 2, (heapsumlen - 2) / 2, 2 * sizeof(uint), &sort_heap_summary);
294 			res = heap_apply_summary(heapsumlen, heapsumarr);
295 		}
296 	}
297 
298 	return res ? Common::kReadingFailed : Common::kNoError;
299 }
300 
saveGameChunks(QuetzalWriter & quetzal)301 Common::Error Glulx::saveGameChunks(QuetzalWriter &quetzal) {
302 	uint res = 0;
303 
304 	// IFHd
305 	if (!res) {
306 		Common::WriteStream &ws = quetzal.add(ID_IFhd);
307 		for (int ix = 0; res == 0 && ix < 128; ix++)
308 			ws.writeByte(Mem1(ix));
309 	}
310 
311 	// CMem
312 	if (!res) {
313 		Common::WriteStream &ws = quetzal.add(ID_CMem);
314 		dest_t dest;
315 		dest._dest = &ws;
316 		res = write_memstate(&dest);
317 	}
318 
319 	// MAll
320 	if (!res) {
321 		Common::WriteStream &ws = quetzal.add(MKTAG('M', 'A', 'l', 'l'));
322 		dest_t dest;
323 		dest._dest = &ws;
324 		res = write_heapstate(&dest, true);
325 	}
326 
327 	// Stks
328 	if (!res) {
329 		Common::WriteStream &ws = quetzal.add(ID_Stks);
330 		dest_t dest;
331 		dest._dest = &ws;
332 		res = write_stackstate(&dest, true);
333 	}
334 
335 	// All done
336 	return res ? Common::kUnknownError : Common::kNoError;
337 }
338 
reposition_write(dest_t * dest,uint pos)339 int Glulx::reposition_write(dest_t *dest, uint pos) {
340 	if (dest->_isMem) {
341 		dest->_pos = pos;
342 	} else {
343 		error("Seeking a WriteStream isn't allowed");
344 	}
345 
346 	return 0;
347 }
348 
write_buffer(dest_t * dest,const byte * ptr,uint len)349 int Glulx::write_buffer(dest_t *dest, const byte *ptr, uint len) {
350 	if (dest->_isMem) {
351 		if (dest->_pos + len > dest->_size) {
352 			dest->_size = dest->_pos + len + 1024;
353 			if (!dest->_ptr) {
354 				dest->_ptr = (byte *)glulx_malloc(dest->_size);
355 			} else {
356 				dest->_ptr = (byte *)glulx_realloc(dest->_ptr, dest->_size);
357 			}
358 			if (!dest->_ptr)
359 				return 1;
360 		}
361 		memcpy(dest->_ptr + dest->_pos, ptr, len);
362 	} else {
363 		dest->_dest->write(ptr, len);
364 	}
365 
366 	dest->_pos += len;
367 
368 	return 0;
369 }
370 
read_buffer(dest_t * dest,byte * ptr,uint len)371 int Glulx::read_buffer(dest_t *dest, byte *ptr, uint len) {
372 	uint newlen;
373 
374 	if (dest->_isMem) {
375 		memcpy(ptr, dest->_ptr + dest->_pos, len);
376 	} else {
377 		newlen = dest->_src->read(ptr, len);
378 		if (newlen != len)
379 			return 1;
380 	}
381 
382 	dest->_pos += len;
383 
384 	return 0;
385 }
386 
write_long(dest_t * dest,uint val)387 int Glulx::write_long(dest_t *dest, uint val) {
388 	unsigned char buf[4];
389 	Write4(buf, val);
390 	return write_buffer(dest, buf, 4);
391 }
392 
write_short(dest_t * dest,uint16 val)393 int Glulx::write_short(dest_t *dest, uint16 val) {
394 	unsigned char buf[2];
395 	Write2(buf, val);
396 	return write_buffer(dest, buf, 2);
397 }
398 
write_byte(dest_t * dest,byte val)399 int Glulx::write_byte(dest_t *dest, byte val) {
400 	return write_buffer(dest, &val, 1);
401 }
402 
read_long(dest_t * dest,uint * val)403 int Glulx::read_long(dest_t *dest, uint *val) {
404 	unsigned char buf[4];
405 	int res = read_buffer(dest, buf, 4);
406 	if (res)
407 		return res;
408 	*val = Read4(buf);
409 	return 0;
410 }
411 
read_short(dest_t * dest,uint16 * val)412 int Glulx::read_short(dest_t *dest, uint16 *val) {
413 	unsigned char buf[2];
414 	int res = read_buffer(dest, buf, 2);
415 	if (res)
416 		return res;
417 	*val = Read2(buf);
418 	return 0;
419 }
420 
read_byte(dest_t * dest,byte * val)421 int Glulx::read_byte(dest_t *dest, byte *val) {
422 	return read_buffer(dest, val, 1);
423 }
424 
write_memstate(dest_t * dest)425 uint Glulx::write_memstate(dest_t *dest) {
426 	uint res, pos;
427 	int val;
428 	int runlen;
429 	unsigned char ch;
430 #ifdef SERIALIZE_CACHE_RAM
431 	uint cachepos;
432 #endif /* SERIALIZE_CACHE_RAM */
433 
434 	res = write_long(dest, endmem);
435 	if (res)
436 		return res;
437 
438 	runlen = 0;
439 
440 #ifdef SERIALIZE_CACHE_RAM
441 	cachepos = 0;
442 #else /* SERIALIZE_CACHE_RAM */
443 	_gameFile.seek(gamefile_start + ramstart);
444 #endif /* SERIALIZE_CACHE_RAM */
445 
446 	for (pos = ramstart; pos < endmem; pos++) {
447 		ch = Mem1(pos);
448 		if (pos < endgamefile) {
449 #ifdef SERIALIZE_CACHE_RAM
450 			val = ramcache[cachepos];
451 			cachepos++;
452 #else /* SERIALIZE_CACHE_RAM */
453 			val = glk_get_char_stream(gamefile);
454 			if (val == -1) {
455 				fatal_error("The game file ended unexpectedly while saving.");
456 			}
457 #endif /* SERIALIZE_CACHE_RAM */
458 			ch ^= (unsigned char)val;
459 		}
460 		if (ch == 0) {
461 			runlen++;
462 		} else {
463 			/* Write any run we've got. */
464 			while (runlen) {
465 				if (runlen >= 0x100)
466 					val = 0x100;
467 				else
468 					val = runlen;
469 				res = write_byte(dest, 0);
470 				if (res)
471 					return res;
472 				res = write_byte(dest, (val - 1));
473 				if (res)
474 					return res;
475 				runlen -= val;
476 			}
477 			/* Write the byte we got. */
478 			res = write_byte(dest, ch);
479 			if (res)
480 				return res;
481 		}
482 	}
483 	/* It's possible we've got a run left over, but we don't write it. */
484 
485 	return 0;
486 }
487 
read_memstate(dest_t * dest,uint chunklen)488 uint Glulx::read_memstate(dest_t *dest, uint chunklen) {
489 	uint chunkend = dest->_pos + chunklen;
490 	uint newlen;
491 	uint res, pos;
492 	int val;
493 	int runlen;
494 	unsigned char ch, ch2;
495 #ifdef SERIALIZE_CACHE_RAM
496 	uint cachepos;
497 #endif /* SERIALIZE_CACHE_RAM */
498 
499 	heap_clear();
500 
501 	res = read_long(dest, &newlen);
502 	if (res)
503 		return res;
504 
505 	res = change_memsize(newlen, false);
506 	if (res)
507 		return res;
508 
509 	runlen = 0;
510 
511 #ifdef SERIALIZE_CACHE_RAM
512 	cachepos = 0;
513 #else /* SERIALIZE_CACHE_RAM */
514 	_gameFile.seek(gamefile_start + ramstart);
515 #endif /* SERIALIZE_CACHE_RAM */
516 
517 	for (pos = ramstart; pos < endmem; pos++) {
518 		if (pos < endgamefile) {
519 #ifdef SERIALIZE_CACHE_RAM
520 			val = ramcache[cachepos];
521 			cachepos++;
522 #else /* SERIALIZE_CACHE_RAM */
523 			if (_gameFile.pos() >= _gameFile.size()) {
524 				fatal_error("The game file ended unexpectedly while restoring.");
525 				val = _gameFile.readByte();
526 			}
527 #endif /* SERIALIZE_CACHE_RAM */
528 			ch = (unsigned char)val;
529 		} else {
530 			ch = 0;
531 		}
532 
533 		if (dest->_pos >= chunkend) {
534 			/* we're into the final, unstored run. */
535 		} else if (runlen) {
536 			runlen--;
537 		} else {
538 			res = read_byte(dest, &ch2);
539 			if (res)
540 				return res;
541 			if (ch2 == 0) {
542 				res = read_byte(dest, &ch2);
543 				if (res)
544 					return res;
545 				runlen = (uint)ch2;
546 			} else {
547 				ch ^= ch2;
548 			}
549 		}
550 
551 		if (pos >= protectstart && pos < protectend)
552 			continue;
553 
554 		MemW1(pos, ch);
555 	}
556 
557 	return 0;
558 }
559 
write_heapstate(dest_t * dest,int portable)560 uint Glulx::write_heapstate(dest_t *dest, int portable) {
561 	uint res;
562 	uint sumlen;
563 	uint *sumarray;
564 
565 	res = heap_get_summary(&sumlen, &sumarray);
566 	if (res)
567 		return res;
568 
569 	if (!sumarray)
570 		return 0; /* no heap */
571 
572 	res = write_heapstate_sub(sumlen, sumarray, dest, portable);
573 
574 	glulx_free(sumarray);
575 	return res;
576 }
577 
write_heapstate_sub(uint sumlen,uint * sumarray,dest_t * dest,int portable)578 uint Glulx::write_heapstate_sub(uint sumlen, uint *sumarray, dest_t *dest, int portable)  {
579 	uint res, lx;
580 
581 	/* If we're storing for the purpose of undo, we don't need to do any
582 	   byte-swapping, because the result will only be used by this session. */
583 	if (!portable) {
584 		res = write_buffer(dest, (const byte *)sumarray, sumlen * sizeof(uint));
585 		if (res)
586 			return res;
587 		return 0;
588 	}
589 
590 	for (lx = 0; lx < sumlen; lx++) {
591 		res = write_long(dest, sumarray[lx]);
592 		if (res)
593 			return res;
594 	}
595 
596 	return 0;
597 }
598 
sort_heap_summary(const void * p1,const void * p2)599 int Glulx::sort_heap_summary(const void *p1, const void *p2) {
600 	uint v1 = *(const uint *)p1;
601 	uint v2 = *(const uint *)p2;
602 
603 	if (v1 < v2)
604 		return -1;
605 	if (v1 > v2)
606 		return 1;
607 	return 0;
608 }
609 
read_heapstate(dest_t * dest,uint chunklen,int portable,uint * sumlen,uint ** summary)610 uint Glulx::read_heapstate(dest_t *dest, uint chunklen, int portable, uint *sumlen, uint **summary) {
611 	uint res, count, lx;
612 	uint *arr;
613 
614 	*sumlen = 0;
615 	*summary = nullptr;
616 
617 	if (chunklen == 0)
618 		return 0; /* no heap */
619 
620 	if (!portable) {
621 		count = chunklen / sizeof(uint);
622 
623 		arr = (uint *)glulx_malloc(chunklen);
624 		if (!arr)
625 			return 1;
626 
627 		res = read_buffer(dest, (byte *)arr, chunklen);
628 		if (res)
629 			return res;
630 
631 		*sumlen = count;
632 		*summary = arr;
633 
634 		return 0;
635 	}
636 
637 	count = chunklen / 4;
638 
639 	arr = (uint *)glulx_malloc(count * sizeof(uint));
640 	if (!arr)
641 		return 1;
642 
643 	for (lx = 0; lx < count; lx++) {
644 		res = read_long(dest, arr + lx);
645 		if (res)
646 			return res;
647 	}
648 
649 	*sumlen = count;
650 	*summary = arr;
651 
652 	return 0;
653 }
654 
write_stackstate(dest_t * dest,int portable)655 uint Glulx::write_stackstate(dest_t *dest, int portable) {
656 	uint res;
657 	uint lx;
658 	uint lastframe;
659 
660 	/* If we're storing for the purpose of undo, we don't need to do any
661 	   byte-swapping, because the result will only be used by this session. */
662 	if (!portable) {
663 		res = write_buffer(dest, stack, stackptr);
664 		if (res)
665 			return res;
666 		return 0;
667 	}
668 
669 	/* Write a portable stack image. To do this, we have to write stack
670 	   frames in order, bottom to top. Remember that the last word of
671 	   every stack frame is a pointer to the beginning of that stack frame.
672 	   (This includes the last frame, because the save opcode pushes on
673 	   a call stub before it calls perform_save().) */
674 
675 	lastframe = (uint)(-1);
676 	while (1) {
677 		uint frameend, frm, frm2, frm3;
678 		unsigned char loctype, loccount;
679 		uint numlocals, frlen, locpos;
680 
681 		/* Find the next stack frame (after the one in lastframe). Sadly,
682 		   this requires searching the stack from the top down. We have to
683 		   do this for *every* frame, which takes N^2 time overall. But
684 		   save routines usually aren't nested very deep.
685 		   If it becomes a practical problem, we can build a stack-frame
686 		   array, which requires dynamic allocation. */
687 		for (frm = stackptr, frameend = stackptr;
688 		        frm != 0 && (frm2 = Stk4(frm - 4)) != lastframe;
689 		        frameend = frm, frm = frm2) { };
690 
691 		/* Write out the frame. */
692 		frm2 = frm;
693 
694 		frlen = Stk4(frm2);
695 		frm2 += 4;
696 		res = write_long(dest, frlen);
697 		if (res)
698 			return res;
699 		locpos = Stk4(frm2);
700 		frm2 += 4;
701 		res = write_long(dest, locpos);
702 		if (res)
703 			return res;
704 
705 		frm3 = frm2;
706 
707 		numlocals = 0;
708 		while (1) {
709 			loctype = Stk1(frm2);
710 			frm2 += 1;
711 			loccount = Stk1(frm2);
712 			frm2 += 1;
713 
714 			res = write_byte(dest, loctype);
715 			if (res)
716 				return res;
717 			res = write_byte(dest, loccount);
718 			if (res)
719 				return res;
720 
721 			if (loctype == 0 && loccount == 0)
722 				break;
723 
724 			numlocals++;
725 		}
726 
727 		if ((numlocals & 1) == 0) {
728 			res = write_byte(dest, 0);
729 			if (res)
730 				return res;
731 			res = write_byte(dest, 0);
732 			if (res)
733 				return res;
734 			frm2 += 2;
735 		}
736 
737 		if (frm2 != frm + locpos)
738 			fatal_error("Inconsistent stack frame during save.");
739 
740 		/* Write out the locals. */
741 		for (lx = 0; lx < numlocals; lx++) {
742 			loctype = Stk1(frm3);
743 			frm3 += 1;
744 			loccount = Stk1(frm3);
745 			frm3 += 1;
746 
747 			if (loctype == 0 && loccount == 0)
748 				break;
749 
750 			/* Put in up to 0, 1, or 3 bytes of padding, depending on loctype. */
751 			while (frm2 & (loctype - 1)) {
752 				res = write_byte(dest, 0);
753 				if (res)
754 					return res;
755 				frm2 += 1;
756 			}
757 
758 			/* Put in this set of locals. */
759 			switch (loctype) {
760 
761 			case 1:
762 				do {
763 					res = write_byte(dest, Stk1(frm2));
764 					if (res)
765 						return res;
766 					frm2 += 1;
767 					loccount--;
768 				} while (loccount);
769 				break;
770 
771 			case 2:
772 				do {
773 					res = write_short(dest, Stk2(frm2));
774 					if (res)
775 						return res;
776 					frm2 += 2;
777 					loccount--;
778 				} while (loccount);
779 				break;
780 
781 			case 4:
782 				do {
783 					res = write_long(dest, Stk4(frm2));
784 					if (res)
785 						return res;
786 					frm2 += 4;
787 					loccount--;
788 				} while (loccount);
789 				break;
790 
791 			}
792 		}
793 
794 		if (frm2 != frm + frlen)
795 			fatal_error("Inconsistent stack frame during save.");
796 
797 		while (frm2 < frameend) {
798 			res = write_long(dest, Stk4(frm2));
799 			if (res)
800 				return res;
801 			frm2 += 4;
802 		}
803 
804 		/* Go on to the next frame. */
805 		if (frameend == stackptr)
806 			break; /* All done. */
807 		lastframe = frm;
808 	}
809 
810 	return 0;
811 }
812 
read_stackstate(dest_t * dest,uint chunklen,int portable)813 uint Glulx::read_stackstate(dest_t *dest, uint chunklen, int portable) {
814 	uint res;
815 	uint frameend, frm, frm2, frm3, locpos, frlen, numlocals;
816 
817 	if (chunklen > stacksize)
818 		return 1;
819 
820 	stackptr = chunklen;
821 	frameptr = 0;
822 	valstackbase = 0;
823 	localsbase = 0;
824 
825 	if (!portable) {
826 		res = read_buffer(dest, stack, stackptr);
827 		if (res)
828 			return res;
829 		return 0;
830 	}
831 
832 	/* This isn't going to be pleasant; we're going to read the data in
833 	   as a block, and then convert it in-place. */
834 	res = read_buffer(dest, stack, stackptr);
835 	if (res)
836 		return res;
837 
838 	frameend = stackptr;
839 	while (frameend != 0) {
840 		/* Read the beginning-of-frame pointer. Remember, right now, the
841 		   whole frame is stored big-endian. So we have to read with the
842 		   Read*() macros, and then write with the StkW*() macros. */
843 		frm = Read4(stack + (frameend - 4));
844 
845 		frm2 = frm;
846 
847 		frlen = Read4(stack + frm2);
848 		StkW4(frm2, frlen);
849 		frm2 += 4;
850 		locpos = Read4(stack + frm2);
851 		StkW4(frm2, locpos);
852 		frm2 += 4;
853 
854 		/* The locals-format list is in bytes, so we don't have to convert it. */
855 		frm3 = frm2;
856 		frm2 = frm + locpos;
857 
858 		numlocals = 0;
859 
860 		while (1) {
861 			unsigned char loctype, loccount;
862 			loctype = Read1(stack + frm3);
863 			frm3 += 1;
864 			loccount = Read1(stack + frm3);
865 			frm3 += 1;
866 
867 			if (loctype == 0 && loccount == 0)
868 				break;
869 
870 			/* Skip up to 0, 1, or 3 bytes of padding, depending on loctype. */
871 			while (frm2 & (loctype - 1)) {
872 				StkW1(frm2, 0);
873 				frm2++;
874 			}
875 
876 			/* Convert this set of locals. */
877 			switch (loctype) {
878 
879 			case 1:
880 				do {
881 					/* Don't need to convert bytes. */
882 					frm2 += 1;
883 					loccount--;
884 				} while (loccount);
885 				break;
886 
887 			case 2:
888 				do {
889 					uint16 loc = Read2(stack + frm2);
890 					StkW2(frm2, loc);
891 					frm2 += 2;
892 					loccount--;
893 				} while (loccount);
894 				break;
895 
896 			case 4:
897 				do {
898 					uint loc = Read4(stack + frm2);
899 					StkW4(frm2, loc);
900 					frm2 += 4;
901 					loccount--;
902 				} while (loccount);
903 				break;
904 
905 			}
906 
907 			numlocals++;
908 		}
909 
910 		if ((numlocals & 1) == 0) {
911 			StkW1(frm3, 0);
912 			frm3++;
913 			StkW1(frm3, 0);
914 			frm3++;
915 		}
916 
917 		if (frm3 != frm + locpos) {
918 			return 1;
919 		}
920 
921 		while (frm2 & 3) {
922 			StkW1(frm2, 0);
923 			frm2++;
924 		}
925 
926 		if (frm2 != frm + frlen) {
927 			return 1;
928 		}
929 
930 		/* Now, the values pushed on the stack after the call frame itself.
931 		   This includes the stub. */
932 		while (frm2 < frameend) {
933 			uint loc = Read4(stack + frm2);
934 			StkW4(frm2, loc);
935 			frm2 += 4;
936 		}
937 
938 		frameend = frm;
939 	}
940 
941 	return 0;
942 }
943 
perform_verify()944 uint Glulx::perform_verify() {
945 	uint len, chksum = 0, newlen;
946 	unsigned char buf[4];
947 	uint val, newsum, ix;
948 
949 	len = gamefile_len;
950 
951 	if (len < 256 || (len & 0xFF) != 0)
952 		return 1;
953 
954 	_gameFile.seek(gamefile_start);
955 	newsum = 0;
956 
957 	/* Read the header */
958 	for (ix = 0; ix < 9; ix++) {
959 		newlen = _gameFile.read(buf, 4);
960 		if (newlen != 4)
961 			return 1;
962 		val = Read4(buf);
963 		if (ix == 3) {
964 			if (len != val)
965 				return 1;
966 		}
967 		if (ix == 8)
968 			chksum = val;
969 		else
970 			newsum += val;
971 	}
972 
973 	/* Read everything else */
974 	for (; ix < len / 4; ix++) {
975 		newlen = _gameFile.read(buf, 4);
976 		if (newlen != 4)
977 			return 1;
978 		val = Read4(buf);
979 		newsum += val;
980 	}
981 
982 	if (newsum != chksum)
983 		return 1;
984 
985 	return 0;
986 }
987 
988 } // End of namespace Glulx
989 } // End of namespace Glk
990