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