Merge commit 'cbb35cca'
[projects/chimara/chimara.git] / interpreters / glulxe / serial.c
1 /* serial.c: Glulxe code for saving and restoring the VM state.
2     Designed by Andrew Plotkin <erkyrath@eblong.com>
3     http://eblong.com/zarf/glulx/index.html
4 */
5
6 #include <string.h>
7 #include "glk.h"
8 #include "glulxe.h"
9
10 /* This structure allows us to write either to a Glk stream or to
11    a dynamically-allocated memory chunk. */
12 typedef struct dest_struct {
13   int ismem;
14   
15   /* If it's a Glk stream: */
16   strid_t str;
17
18   /* If it's a block of memory: */
19   unsigned char *ptr;
20   glui32 pos;
21   glui32 size;
22 } dest_t;
23
24 #define IFFID(c1, c2, c3, c4)  \
25   ( (((glui32)c1) << 24)    \
26   | (((glui32)c2) << 16)    \
27   | (((glui32)c3) << 8)     \
28   | (((glui32)c4)) )
29
30 /* This can be adjusted before startup by platform-specific startup
31    code -- that is, preference code. */
32 int max_undo_level = 8;
33
34 static int undo_chain_size = 0;
35 static int undo_chain_num = 0;
36 unsigned char **undo_chain = NULL;
37
38 static glui32 write_memstate(dest_t *dest);
39 static glui32 write_heapstate(dest_t *dest, int portable);
40 static glui32 write_stackstate(dest_t *dest, int portable);
41 static glui32 read_memstate(dest_t *dest, glui32 chunklen);
42 static glui32 read_heapstate(dest_t *dest, glui32 chunklen, int portable,
43   glui32 *sumlen, glui32 **summary);
44 static glui32 read_stackstate(dest_t *dest, glui32 chunklen, int portable);
45 static glui32 write_heapstate_sub(glui32 sumlen, glui32 *sumarray,
46   dest_t *dest, int portable);
47 static int sort_heap_summary(void *p1, void *p2);
48 static int write_long(dest_t *dest, glui32 val);
49 static int read_long(dest_t *dest, glui32 *val);
50 static int write_byte(dest_t *dest, unsigned char val);
51 static int read_byte(dest_t *dest, unsigned char *val);
52 static int reposition_write(dest_t *dest, glui32 pos);
53
54 /* init_serial():
55    Set up the undo chain and anything else that needs to be set up.
56 */
57 int init_serial()
58 {
59   undo_chain_num = 0;
60   undo_chain_size = max_undo_level;
61   undo_chain = (unsigned char **)glulx_malloc(sizeof(unsigned char *) * undo_chain_size);
62   if (!undo_chain)
63     return FALSE;
64
65   return TRUE;
66 }
67
68 /* perform_saveundo():
69    Add a state pointer to the undo chain. This returns 0 on success,
70    1 on failure.
71 */
72 glui32 perform_saveundo()
73 {
74   dest_t dest;
75   glui32 res;
76   glui32 memstart, memlen, heapstart, heaplen, stackstart, stacklen;
77
78   /* The format for undo-saves is simpler than for saves on disk. We
79      just have a memory chunk, a heap chunk, and a stack chunk, in
80      that order. We skip the IFF chunk headers (although the size
81      fields are still there.) We also don't bother with IFF's 16-bit
82      alignment. */
83
84   if (undo_chain_size == 0)
85     return 1;
86
87   dest.ismem = TRUE;
88   dest.size = 0;
89   dest.pos = 0;
90   dest.ptr = NULL;
91   dest.str = NULL;
92
93   res = 0;
94   if (res == 0) {
95     res = write_long(&dest, 0); /* space for chunk length */
96   }
97   if (res == 0) {
98     memstart = dest.pos;
99     res = write_memstate(&dest);
100     memlen = dest.pos - memstart;
101   }
102   if (res == 0) {
103     res = write_long(&dest, 0); /* space for chunk length */
104   }
105   if (res == 0) {
106     heapstart = dest.pos;
107     res = write_heapstate(&dest, FALSE);
108     heaplen = dest.pos - heapstart;
109   }
110   if (res == 0) {
111     res = write_long(&dest, 0); /* space for chunk length */
112   }
113   if (res == 0) {
114     stackstart = dest.pos;
115     res = write_stackstate(&dest, FALSE);
116     stacklen = dest.pos - stackstart;
117   }
118
119   if (res == 0) {
120     /* Trim it down to the perfect size. */
121     dest.ptr = glulx_realloc(dest.ptr, dest.pos);
122     if (!dest.ptr)
123       res = 1;
124   }
125   if (res == 0) {
126     res = reposition_write(&dest, memstart-4);
127   }
128   if (res == 0) {
129     res = write_long(&dest, memlen);
130   }
131   if (res == 0) {
132     res = reposition_write(&dest, heapstart-4);
133   }
134   if (res == 0) {
135     res = write_long(&dest, heaplen);
136   }
137   if (res == 0) {
138     res = reposition_write(&dest, stackstart-4);
139   }
140   if (res == 0) {
141     res = write_long(&dest, stacklen);
142   }
143
144   if (res == 0) {
145     /* It worked. */
146     if (undo_chain_num >= undo_chain_size) {
147       glulx_free(undo_chain[undo_chain_num-1]);
148       undo_chain[undo_chain_num-1] = NULL;
149     }
150     if (undo_chain_size > 1)
151       memmove(undo_chain+1, undo_chain, 
152         (undo_chain_size-1) * sizeof(unsigned char *));
153     undo_chain[0] = dest.ptr;
154     if (undo_chain_num < undo_chain_size)
155       undo_chain_num += 1;
156     dest.ptr = NULL;
157   }
158   else {
159     /* It didn't work. */
160     if (dest.ptr) {
161       glulx_free(dest.ptr);
162       dest.ptr = NULL;
163     }
164   }
165     
166   return res;
167 }
168
169 /* perform_restoreundo():
170    Pull a state pointer from the undo chain. This returns 0 on success,
171    1 on failure. Note that if it succeeds, the frameptr, localsbase,
172    and valstackbase registers are invalid; they must be rebuilt from
173    the stack.
174 */
175 glui32 perform_restoreundo()
176 {
177   dest_t dest;
178   glui32 res, val;
179   glui32 heapsumlen = 0;
180   glui32 *heapsumarr = NULL;
181
182   if (undo_chain_size == 0 || undo_chain_num == 0)
183     return 1;
184
185   dest.ismem = TRUE;
186   dest.size = 0;
187   dest.pos = 0;
188   dest.ptr = undo_chain[0];
189   dest.str = NULL;
190
191   res = 0;
192   if (res == 0) {
193     res = read_long(&dest, &val);
194   }
195   if (res == 0) {
196     res = read_memstate(&dest, val);
197   }
198   if (res == 0) {
199     res = read_long(&dest, &val);
200   }
201   if (res == 0) {
202     res = read_heapstate(&dest, val, FALSE, &heapsumlen, &heapsumarr);
203   }
204   if (res == 0) {
205     res = read_long(&dest, &val);
206   }
207   if (res == 0) {
208     res = read_stackstate(&dest, val, FALSE);
209   }
210   /* ### really, many of the failure modes of those calls ought to
211      cause fatal errors. The stack or main memory may be damaged now. */
212
213   if (res == 0) {
214     if (heapsumarr)
215       res = heap_apply_summary(heapsumlen, heapsumarr);
216   }
217
218   if (res == 0) {
219     /* It worked. */
220     if (undo_chain_size > 1)
221       memmove(undo_chain, undo_chain+1,
222         (undo_chain_size-1) * sizeof(unsigned char *));
223     undo_chain_num -= 1;
224     glulx_free(dest.ptr);
225     dest.ptr = NULL;
226   }
227   else {
228     /* It didn't work. */
229     dest.ptr = NULL;
230   }
231
232   return res;
233 }
234
235 /* perform_save():
236    Write the state to the output stream. This returns 0 on success,
237    1 on failure.
238 */
239 glui32 perform_save(strid_t str)
240 {
241   dest_t dest;
242   int ix;
243   glui32 res, lx, val;
244   glui32 memstart, memlen, stackstart, stacklen, heapstart, heaplen;
245   glui32 filestart, filelen;
246
247   stream_get_iosys(&val, &lx);
248   if (val != 2) {
249     /* Not using the Glk I/O system, so bail. This function only
250        knows how to write to a Glk stream. */
251     fatal_error("Streams are only available in Glk I/O system.");
252   }
253
254   if (str == 0)
255     return 1;
256
257   dest.ismem = FALSE;
258   dest.size = 0;
259   dest.pos = 0;
260   dest.ptr = NULL;
261   dest.str = str;
262
263   res = 0;
264
265   /* Quetzal header. */
266   if (res == 0) {
267     res = write_long(&dest, IFFID('F', 'O', 'R', 'M'));
268   }
269   if (res == 0) {
270     res = write_long(&dest, 0); /* space for file length */
271     filestart = dest.pos;
272   }
273
274   if (res == 0) {
275     res = write_long(&dest, IFFID('I', 'F', 'Z', 'S')); /* ### ? */
276   }
277
278   /* Header chunk. This is the first 128 bytes of memory. */
279   if (res == 0) {
280     res = write_long(&dest, IFFID('I', 'F', 'h', 'd'));
281   }
282   if (res == 0) {
283     res = write_long(&dest, 128);
284   }
285   for (ix=0; res==0 && ix<128; ix++) {
286     res = write_byte(&dest, Mem1(ix));
287   }
288   /* Always even, so no padding necessary. */
289   
290   /* Memory chunk. */
291   if (res == 0) {
292     res = write_long(&dest, IFFID('C', 'M', 'e', 'm'));
293   }
294   if (res == 0) {
295     res = write_long(&dest, 0); /* space for chunk length */
296   }
297   if (res == 0) {
298     memstart = dest.pos;
299     res = write_memstate(&dest);
300     memlen = dest.pos - memstart;
301   }
302   if (res == 0 && (memlen & 1) != 0) {
303     res = write_byte(&dest, 0);
304   }
305
306   /* Heap chunk. */
307   if (res == 0) {
308     res = write_long(&dest, IFFID('M', 'A', 'l', 'l'));
309   }
310   if (res == 0) {
311     res = write_long(&dest, 0); /* space for chunk length */
312   }
313   if (res == 0) {
314     heapstart = dest.pos;
315     res = write_heapstate(&dest, TRUE);
316     heaplen = dest.pos - heapstart;
317   }
318   /* Always even, so no padding necessary. */
319
320   /* Stack chunk. */
321   if (res == 0) {
322     res = write_long(&dest, IFFID('S', 't', 'k', 's'));
323   }
324   if (res == 0) {
325     res = write_long(&dest, 0); /* space for chunk length */
326   }
327   if (res == 0) {
328     stackstart = dest.pos;
329     res = write_stackstate(&dest, TRUE);
330     stacklen = dest.pos - stackstart;
331   }
332   if (res == 0 && (stacklen & 1) != 0) {
333     res = write_byte(&dest, 0);
334   }
335
336   filelen = dest.pos - filestart;
337
338   /* Okay, fill in all the lengths. */
339   if (res == 0) {
340     res = reposition_write(&dest, memstart-4);
341   }
342   if (res == 0) {
343     res = write_long(&dest, memlen);
344   }
345   if (res == 0) {
346     res = reposition_write(&dest, heapstart-4);
347   }
348   if (res == 0) {
349     res = write_long(&dest, heaplen);
350   }
351   if (res == 0) {
352     res = reposition_write(&dest, stackstart-4);
353   }
354   if (res == 0) {
355     res = write_long(&dest, stacklen);
356   }
357   if (res == 0) {
358     res = reposition_write(&dest, filestart-4);
359   }
360   if (res == 0) {
361     res = write_long(&dest, filelen);
362   }
363
364   /* All done. */
365     
366   return res;
367 }
368
369 /* perform_restore():
370    Pull a state pointer from a stream. This returns 0 on success,
371    1 on failure. Note that if it succeeds, the frameptr, localsbase,
372    and valstackbase registers are invalid; they must be rebuilt from
373    the stack.
374 */
375 glui32 perform_restore(strid_t str)
376 {
377   dest_t dest;
378   int ix;
379   glui32 lx, res, val;
380   glui32 filestart, filelen;
381   glui32 heapsumlen = 0;
382   glui32 *heapsumarr = NULL;
383
384   stream_get_iosys(&val, &lx);
385   if (val != 2) {
386     /* Not using the Glk I/O system, so bail. This function only
387        knows how to read from a Glk stream. */
388     fatal_error("Streams are only available in Glk I/O system.");
389   }
390
391   if (str == 0)
392     return 1;
393
394   dest.ismem = FALSE;
395   dest.size = 0;
396   dest.pos = 0;
397   dest.ptr = NULL;
398   dest.str = str;
399
400   res = 0;
401
402   /* ### the format errors checked below should send error messages to
403      the current stream. */
404
405   if (res == 0) {
406     res = read_long(&dest, &val);
407   }
408   if (res == 0 && val != IFFID('F', 'O', 'R', 'M')) {
409     /* ### bad header */
410     return 1;
411   }
412   if (res == 0) {
413     res = read_long(&dest, &filelen);
414   }
415   filestart = dest.pos;
416
417   if (res == 0) {
418     res = read_long(&dest, &val);
419   }
420   if (res == 0 && val != IFFID('I', 'F', 'Z', 'S')) { /* ### ? */
421     /* ### bad header */
422     return 1;
423   }
424
425   while (res == 0 && dest.pos < filestart+filelen) {
426     /* Read a chunk and deal with it. */
427     glui32 chunktype, chunkstart, chunklen;
428     unsigned char dummy;
429
430     if (res == 0) {
431       res = read_long(&dest, &chunktype);
432     }
433     if (res == 0) {
434       res = read_long(&dest, &chunklen);
435     }
436     chunkstart = dest.pos;
437
438     if (chunktype == IFFID('I', 'F', 'h', 'd')) {
439       for (ix=0; res==0 && ix<128; ix++) {
440         res = read_byte(&dest, &dummy);
441         if (res == 0 && Mem1(ix) != dummy) {
442           /* ### non-matching header */
443           return 1;
444         }
445       }
446     }
447     else if (chunktype == IFFID('C', 'M', 'e', 'm')) {
448       res = read_memstate(&dest, chunklen);
449     }
450     else if (chunktype == IFFID('M', 'A', 'l', 'l')) {
451       res = read_heapstate(&dest, chunklen, TRUE, &heapsumlen, &heapsumarr);
452     }
453     else if (chunktype == IFFID('S', 't', 'k', 's')) {
454       res = read_stackstate(&dest, chunklen, TRUE);
455     }
456     else {
457       /* Unknown chunk type. Skip it. */
458       for (lx=0; res==0 && lx<chunklen; lx++) {
459         res = read_byte(&dest, &dummy);
460       }
461     }
462
463     if (chunkstart+chunklen != dest.pos) {
464       /* ### funny chunk length */
465       return 1;
466     }
467
468     if ((chunklen & 1) != 0) {
469       if (res == 0) {
470         res = read_byte(&dest, &dummy);
471       }
472     }
473   }
474
475   if (res == 0) {
476     if (heapsumarr) {
477       /* The summary might have come from any interpreter, so it could
478          be out of order. We'll sort it. */
479       glulx_sort(heapsumarr+2, (heapsumlen-2)/2, 2*sizeof(glui32),
480         &sort_heap_summary);
481       res = heap_apply_summary(heapsumlen, heapsumarr);
482     }
483   }
484
485   if (res)
486     return 1;
487
488   return 0;
489 }
490
491 static int reposition_write(dest_t *dest, glui32 pos)
492 {
493   if (dest->ismem) {
494     dest->pos = pos;
495   }
496   else {
497     glk_stream_set_position(dest->str, pos, seekmode_Start);
498     dest->pos = pos;
499   }
500
501   return 0;
502 }
503
504 static int write_buffer(dest_t *dest, unsigned char *ptr, glui32 len)
505 {
506   if (dest->ismem) {
507     if (dest->pos+len > dest->size) {
508       dest->size = dest->pos+len+1024;
509       if (!dest->ptr) {
510         dest->ptr = glulx_malloc(dest->size);
511       }
512       else {
513         dest->ptr = glulx_realloc(dest->ptr, dest->size);
514       }
515       if (!dest->ptr)
516         return 1;
517     }
518     memcpy(dest->ptr+dest->pos, ptr, len);
519   }
520   else {
521     glk_put_buffer_stream(dest->str, (char *)ptr, len);
522   }
523
524   dest->pos += len;
525
526   return 0;
527 }
528
529 static int read_buffer(dest_t *dest, unsigned char *ptr, glui32 len)
530 {
531   glui32 newlen;
532
533   if (dest->ismem) {
534     memcpy(ptr, dest->ptr+dest->pos, len);
535   }
536   else {
537     newlen = glk_get_buffer_stream(dest->str, (char *)ptr, len);
538     if (newlen != len)
539       return 1;
540   }
541
542   dest->pos += len;
543
544   return 0;
545 }
546
547 static int write_long(dest_t *dest, glui32 val)
548 {
549   unsigned char buf[4];
550   Write4(buf, val);
551   return write_buffer(dest, buf, 4);
552 }
553
554 static int write_short(dest_t *dest, glui16 val)
555 {
556   unsigned char buf[2];
557   Write2(buf, val);
558   return write_buffer(dest, buf, 2);
559 }
560
561 static int write_byte(dest_t *dest, unsigned char val)
562 {
563   return write_buffer(dest, &val, 1);
564 }
565
566 static int read_long(dest_t *dest, glui32 *val)
567 {
568   unsigned char buf[4];
569   int res = read_buffer(dest, buf, 4);
570   if (res)
571     return res;
572   *val = Read4(buf);
573   return 0;
574 }
575
576 static int read_short(dest_t *dest, glui16 *val)
577 {
578   unsigned char buf[2];
579   int res = read_buffer(dest, buf, 2);
580   if (res)
581     return res;
582   *val = Read2(buf);
583   return 0;
584 }
585
586 static int read_byte(dest_t *dest, unsigned char *val)
587 {
588   return read_buffer(dest, val, 1);
589 }
590
591 static glui32 write_memstate(dest_t *dest)
592 {
593   glui32 res, pos;
594   int val;
595   int runlen;
596   unsigned char ch;
597
598   res = write_long(dest, endmem);
599   if (res)
600     return res;
601
602   runlen = 0;
603   glk_stream_set_position(gamefile, gamefile_start+ramstart, seekmode_Start);
604
605   for (pos=ramstart; pos<endmem; pos++) {
606     ch = Mem1(pos);
607     if (pos < endgamefile) {
608       val = glk_get_char_stream(gamefile);
609       if (val == -1) {
610         fatal_error("The game file ended unexpectedly while saving.");
611       }
612       ch ^= (unsigned char)val;
613     }
614     if (ch == 0) {
615       runlen++;
616     }
617     else {
618       /* Write any run we've got. */
619       while (runlen) {
620         if (runlen >= 0x100)
621           val = 0x100;
622         else
623           val = runlen;
624         res = write_byte(dest, 0);
625         if (res)
626           return res;
627         res = write_byte(dest, (val-1));
628         if (res)
629           return res;
630         runlen -= val;
631       }
632       /* Write the byte we got. */
633       res = write_byte(dest, ch);
634       if (res)
635         return res;
636     }
637   }
638   /* It's possible we've got a run left over, but we don't write it. */
639
640   return 0;
641 }
642
643 static glui32 read_memstate(dest_t *dest, glui32 chunklen)
644 {
645   glui32 chunkend = dest->pos + chunklen;
646   glui32 newlen;
647   glui32 res, pos;
648   int val;
649   int runlen;
650   unsigned char ch, ch2;
651
652   heap_clear();
653
654   res = read_long(dest, &newlen);
655   if (res)
656     return res;
657
658   res = change_memsize(newlen, FALSE);
659   if (res)
660     return res;
661
662   runlen = 0;
663   glk_stream_set_position(gamefile, gamefile_start+ramstart, seekmode_Start);
664
665   for (pos=ramstart; pos<endmem; pos++) {
666     if (pos < endgamefile) {
667       val = glk_get_char_stream(gamefile);
668       if (val == -1) {
669         fatal_error("The game file ended unexpectedly while restoring.");
670       }
671       ch = (unsigned char)val;
672     }
673     else {
674       ch = 0;
675     }
676
677     if (dest->pos >= chunkend) {
678       /* we're into the final, unstored run. */
679     }
680     else if (runlen) {
681       runlen--;
682     }
683     else {
684       res = read_byte(dest, &ch2);
685       if (res)
686         return res;
687       if (ch2 == 0) {
688         res = read_byte(dest, &ch2);
689         if (res)
690           return res;
691         runlen = (glui32)ch2;
692       }
693       else {
694         ch ^= ch2;
695       }
696     }
697
698     if (pos >= protectstart && pos < protectend)
699       continue;
700
701     MemW1(pos, ch);
702   }
703
704   return 0;
705 }
706
707 static glui32 write_heapstate(dest_t *dest, int portable)
708 {
709   glui32 res;
710   glui32 sumlen;
711   glui32 *sumarray;
712
713   res = heap_get_summary(&sumlen, &sumarray);
714   if (res)
715     return res;
716
717   if (!sumarray)
718     return 0; /* no heap */
719
720   res = write_heapstate_sub(sumlen, sumarray, dest, portable);
721
722   glulx_free(sumarray);
723   return res;
724 }
725
726 static glui32 write_heapstate_sub(glui32 sumlen, glui32 *sumarray,
727   dest_t *dest, int portable) 
728 {
729   glui32 res, lx;
730
731   /* If we're storing for the purpose of undo, we don't need to do any
732      byte-swapping, because the result will only be used by this session. */
733   if (!portable) {
734     res = write_buffer(dest, (void *)sumarray, sumlen*sizeof(glui32));
735     if (res)
736       return res;
737     return 0;
738   }
739
740   for (lx=0; lx<sumlen; lx++) {
741     res = write_long(dest, sumarray[lx]);
742     if (res)
743       return res;
744   }
745
746   return 0;
747 }
748
749 static int sort_heap_summary(void *p1, void *p2)
750 {
751   glui32 *v1 = (glui32 *)p1;
752   glui32 *v2 = (glui32 *)p2;
753
754   if (v1 < v2)
755     return -1;
756   if (v1 > v2)
757     return 1;
758   return 0;
759 }
760
761 static glui32 read_heapstate(dest_t *dest, glui32 chunklen, int portable,
762   glui32 *sumlen, glui32 **summary)
763 {
764   glui32 res, count, lx;
765   glui32 *arr;
766
767   *sumlen = 0;
768   *summary = NULL;
769
770   if (chunklen == 0)
771     return 0; /* no heap */
772
773   if (!portable) {
774     count = chunklen / sizeof(glui32);
775
776     arr = glulx_malloc(chunklen);
777     if (!arr)
778       return 1;
779
780     res = read_buffer(dest, (void *)arr, chunklen);
781     if (res)
782       return res;
783
784     *sumlen = count;
785     *summary = arr;
786
787     return 0;
788   }
789
790   count = chunklen / 4;
791
792   arr = glulx_malloc(count * sizeof(glui32));
793   if (!arr)
794     return 1;
795   
796   for (lx=0; lx<count; lx++) {
797     res = read_long(dest, arr+lx);
798     if (res)
799       return res;
800   }
801
802   *sumlen = count;
803   *summary = arr;
804
805   return 0;
806 }
807
808 static glui32 write_stackstate(dest_t *dest, int portable)
809 {
810   glui32 res;
811   glui32 lx;
812   glui32 lastframe;
813
814   /* If we're storing for the purpose of undo, we don't need to do any
815      byte-swapping, because the result will only be used by this session. */
816   if (!portable) {
817     res = write_buffer(dest, stack, stackptr);
818     if (res)
819       return res;
820     return 0;
821   }
822
823   /* Write a portable stack image. To do this, we have to write stack
824      frames in order, bottom to top. Remember that the last word of
825      every stack frame is a pointer to the beginning of that stack frame.
826      (This includes the last frame, because the save opcode pushes on
827      a call stub before it calls perform_save().) */
828
829   lastframe = (glui32)(-1);
830   while (1) {
831     glui32 frameend, frm, frm2, frm3;
832     unsigned char loctype, loccount;
833     glui32 numlocals, frlen, locpos;
834
835     /* Find the next stack frame (after the one in lastframe). Sadly,
836        this requires searching the stack from the top down. We have to
837        do this for *every* frame, which takes N^2 time overall. But
838        save routines usually aren't nested very deep. 
839        If it becomes a practical problem, we can build a stack-frame 
840        array, which requires dynamic allocation. */
841     for (frm = stackptr, frameend = stackptr;
842          frm != 0 && (frm2 = Stk4(frm-4)) != lastframe;
843          frameend = frm, frm = frm2) { };
844
845     /* Write out the frame. */
846     frm2 = frm;
847
848     frlen = Stk4(frm2);
849     frm2 += 4;
850     res = write_long(dest, frlen);
851     if (res)
852       return res;
853     locpos = Stk4(frm2);
854     frm2 += 4;
855     res = write_long(dest, locpos);
856     if (res)
857       return res;
858
859     frm3 = frm2;
860
861     numlocals = 0;
862     while (1) {
863       loctype = Stk1(frm2);
864       frm2 += 1;
865       loccount = Stk1(frm2);
866       frm2 += 1;
867
868       res = write_byte(dest, loctype);
869       if (res)
870         return res;
871       res = write_byte(dest, loccount);
872       if (res)
873         return res;
874
875       if (loctype == 0 && loccount == 0)
876         break;
877
878       numlocals++;
879     }
880
881     if ((numlocals & 1) == 0) {
882       res = write_byte(dest, 0);
883       if (res)
884         return res;
885       res = write_byte(dest, 0);
886       if (res)
887         return res;
888       frm2 += 2;
889     }
890
891     if (frm2 != frm+locpos)
892       fatal_error("Inconsistent stack frame during save.");
893
894     /* Write out the locals. */
895     for (lx=0; lx<numlocals; lx++) {
896       loctype = Stk1(frm3);
897       frm3 += 1;
898       loccount = Stk1(frm3);
899       frm3 += 1;
900       
901       if (loctype == 0 && loccount == 0)
902         break;
903
904       /* Put in up to 0, 1, or 3 bytes of padding, depending on loctype. */
905       while (frm2 & (loctype-1)) {
906         res = write_byte(dest, 0);
907         if (res)
908           return res;
909         frm2 += 1;
910       }
911
912       /* Put in this set of locals. */
913       switch (loctype) {
914
915       case 1:
916         do {
917           res = write_byte(dest, Stk1(frm2));
918           if (res)
919             return res;
920           frm2 += 1;
921           loccount--;
922         } while (loccount);
923         break;
924
925       case 2:
926         do {
927           res = write_short(dest, Stk2(frm2));
928           if (res)
929             return res;
930           frm2 += 2;
931           loccount--;
932         } while (loccount);
933         break;
934
935       case 4:
936         do {
937           res = write_long(dest, Stk4(frm2));
938           if (res)
939             return res;
940           frm2 += 4;
941           loccount--;
942         } while (loccount);
943         break;
944
945       }
946     }
947
948     if (frm2 != frm+frlen)
949       fatal_error("Inconsistent stack frame during save.");
950
951     while (frm2 < frameend) {
952       res = write_long(dest, Stk4(frm2));
953       if (res)
954         return res;
955       frm2 += 4;
956     }
957
958     /* Go on to the next frame. */
959     if (frameend == stackptr)
960       break; /* All done. */
961     lastframe = frm;
962   }
963
964   return 0;
965 }
966
967 static glui32 read_stackstate(dest_t *dest, glui32 chunklen, int portable)
968 {
969   glui32 res;
970   glui32 frameend, frm, frm2, frm3, locpos, frlen, numlocals;
971
972   if (chunklen > stacksize)
973     return 1;
974
975   stackptr = chunklen;
976   frameptr = 0;
977   valstackbase = 0;
978   localsbase = 0;
979
980   if (!portable) {
981     res = read_buffer(dest, stack, stackptr);
982     if (res)
983       return res;
984     return 0;
985   }
986
987   /* This isn't going to be pleasant; we're going to read the data in
988      as a block, and then convert it in-place. */
989   res = read_buffer(dest, stack, stackptr);
990   if (res)
991     return res;
992
993   frameend = stackptr;
994   while (frameend != 0) {
995     /* Read the beginning-of-frame pointer. Remember, right now, the
996        whole frame is stored big-endian. So we have to read with the
997        Read*() macros, and then write with the StkW*() macros. */
998     frm = Read4(stack+(frameend-4));
999
1000     frm2 = frm;
1001
1002     frlen = Read4(stack+frm2);
1003     StkW4(frm2, frlen);
1004     frm2 += 4;
1005     locpos = Read4(stack+frm2);
1006     StkW4(frm2, locpos);
1007     frm2 += 4;
1008
1009     /* The locals-format list is in bytes, so we don't have to convert it. */
1010     frm3 = frm2;
1011     frm2 = frm+locpos;
1012
1013     numlocals = 0;
1014
1015     while (1) {
1016       unsigned char loctype, loccount;
1017       loctype = Read1(stack+frm3);
1018       frm3 += 1;
1019       loccount = Read1(stack+frm3);
1020       frm3 += 1;
1021
1022       if (loctype == 0 && loccount == 0)
1023         break;
1024
1025       /* Skip up to 0, 1, or 3 bytes of padding, depending on loctype. */
1026       while (frm2 & (loctype-1)) {
1027         StkW1(frm2, 0);
1028         frm2++;
1029       }
1030       
1031       /* Convert this set of locals. */
1032       switch (loctype) {
1033         
1034       case 1:
1035         do {
1036           /* Don't need to convert bytes. */
1037           frm2 += 1;
1038           loccount--;
1039         } while (loccount);
1040         break;
1041
1042       case 2:
1043         do {
1044           glui16 loc = Read2(stack+frm2);
1045           StkW2(frm2, loc);
1046           frm2 += 2;
1047           loccount--;
1048         } while (loccount);
1049         break;
1050
1051       case 4:
1052         do {
1053           glui32 loc = Read4(stack+frm2);
1054           StkW4(frm2, loc);
1055           frm2 += 4;
1056           loccount--;
1057         } while (loccount);
1058         break;
1059
1060       }
1061
1062       numlocals++;
1063     }
1064
1065     if ((numlocals & 1) == 0) {
1066       StkW1(frm3, 0);
1067       frm3++;
1068       StkW1(frm3, 0);
1069       frm3++;
1070     }
1071
1072     if (frm3 != frm+locpos) {
1073       return 1;
1074     }
1075
1076     while (frm2 & 3) {
1077       StkW1(frm2, 0);
1078       frm2++;
1079     }
1080
1081     if (frm2 != frm+frlen) {
1082       return 1;
1083     }
1084
1085     /* Now, the values pushed on the stack after the call frame itself.
1086        This includes the stub. */
1087     while (frm2 < frameend) {
1088       glui32 loc = Read4(stack+frm2);
1089       StkW4(frm2, loc);
1090       frm2 += 4;
1091     }
1092
1093     frameend = frm;
1094   }
1095
1096   return 0;
1097 }
1098
1099 glui32 perform_verify()
1100 {
1101   glui32 len, checksum, newlen;
1102   unsigned char buf[4];
1103   glui32 val, newsum, ix;
1104
1105   len = gamefile_len;
1106
1107   if (len < 256 || (len & 0xFF) != 0)
1108     return 1;
1109
1110   glk_stream_set_position(gamefile, gamefile_start, seekmode_Start);
1111   newsum = 0;
1112
1113   /* Read the header */
1114   for (ix=0; ix<9; ix++) {
1115     newlen = glk_get_buffer_stream(gamefile, (char *)buf, 4);
1116     if (newlen != 4)
1117       return 1;
1118     val = Read4(buf);
1119     if (ix == 3) {
1120       if (len != val)
1121         return 1;
1122     }
1123     if (ix == 8)
1124       checksum = val;
1125     else
1126       newsum += val;
1127   }
1128
1129   /* Read everything else */
1130   for (; ix < len/4; ix++) {
1131     newlen = glk_get_buffer_stream(gamefile, (char *)buf, 4);
1132     if (newlen != 4)
1133       return 1;
1134     val = Read4(buf);
1135     newsum += val;
1136   }
1137
1138   if (newsum != checksum)
1139     return 1;
1140
1141   return 0;  
1142 }