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