Implemented garglk_set_program_name(), garglk_set_program_info(), garglk_set_story_name()
[rodin/chimara.git] / interpreters / frotz / fastmem.c
1 /* fastmem.c - Memory related functions (fast version without virtual memory)
2  *      Copyright (c) 1995-1997 Stefan Jokisch
3  *
4  * This file is part of Frotz.
5  *
6  * Frotz is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Frotz is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 /*
22  * New undo mechanism added by Jim Dunleavy <jim.dunleavy@erha.ie>
23  */
24
25 /*
26  * Glk and Blorb support added by Tor Andersson.
27  */
28
29 #include "frotz.h"
30
31 #include "glk.h"
32 #include "glkio.h"
33 #include "glkstart.h"
34 #include "gi_blorb.h"
35
36 extern void seed_random (int);
37 extern void restart_screen (void);
38 extern void refresh_text_style (void);
39 extern void call (zword, int, zword *, int);
40 extern void split_window (zword);
41 extern void script_open (void);
42 extern void script_close (void);
43
44 extern zword save_quetzal (FILE *, FILE *, int);
45 extern zword restore_quetzal (FILE *, FILE *, int);
46
47 extern void erase_window (zword);
48
49 extern void (*op0_opcodes[]) (void);
50 extern void (*op1_opcodes[]) (void);
51 extern void (*op2_opcodes[]) (void);
52 extern void (*var_opcodes[]) (void);
53
54 zbyte *zmp = NULL;
55 zbyte *pcp = NULL;
56
57 static FILE *story_fp = NULL;
58 static size_t blorb_ofs = 0;
59 static size_t blorb_len = 0;
60
61 /*
62  * Data for the undo mechanism.
63  * This undo mechanism is based on the scheme used in Evin Robertson's
64  * Nitfol interpreter.
65  * Undo blocks are stored as differences between states.
66  */
67
68 typedef struct undo_struct undo_t;
69 struct undo_struct {
70         undo_t *next;
71         undo_t *prev;
72         long pc;
73         long diff_size;
74         zword frame_count;
75         zword stack_size;
76         zword frame_offset;
77         /* undo diff and stack data follow */
78 };
79
80 static undo_t *first_undo = NULL, *last_undo = NULL, *curr_undo = NULL;
81 static zbyte *undo_mem = NULL, *prev_zmp, *undo_diff;
82
83 static int undo_count = 0;
84
85 /*
86  * get_header_extension
87  *
88  * Read a value from the header extension (former mouse table).
89  *
90  */
91
92 zword get_header_extension (int entry)
93 {
94         zword addr;
95         zword val;
96
97         if (h_extension_table == 0 || entry > hx_table_size)
98                 return 0;
99
100         addr = h_extension_table + 2 * entry;
101         LOW_WORD (addr, val);
102
103         return val;
104
105 }/* get_header_extension */
106
107 /*
108  * set_header_extension
109  *
110  * Set an entry in the header extension (former mouse table).
111  *
112  */
113
114 void set_header_extension (int entry, zword val)
115 {
116         zword addr;
117
118         if (h_extension_table == 0 || entry > hx_table_size)
119                 return;
120
121         addr = h_extension_table + 2 * entry;
122         SET_WORD (addr, val);
123
124 }/* set_header_extension */
125
126 /*
127  * restart_header
128  *
129  * Set all header fields which hold information about the interpreter.
130  *
131  */
132
133 void restart_header (void)
134 {
135         zword screen_x_size;
136         zword screen_y_size;
137         zbyte font_x_size;
138         zbyte font_y_size;
139
140         int i;
141
142         SET_BYTE (H_CONFIG, h_config);
143         SET_WORD (H_FLAGS, h_flags);
144
145         if (h_version >= V4) {
146                 SET_BYTE (H_INTERPRETER_NUMBER, h_interpreter_number);
147                 SET_BYTE (H_INTERPRETER_VERSION, h_interpreter_version);
148                 SET_BYTE (H_SCREEN_ROWS, h_screen_rows);
149                 SET_BYTE (H_SCREEN_COLS, h_screen_cols);
150         }
151
152         /* It's less trouble to use font size 1x1 for V5 games, especially
153            because of a bug in the unreleased German version of "Zork 1" */
154
155         if (h_version != V6) {
156                 screen_x_size = (zword) h_screen_cols;
157                 screen_y_size = (zword) h_screen_rows;
158                 font_x_size = 1;
159                 font_y_size = 1;
160         } else {
161                 screen_x_size = h_screen_width;
162                 screen_y_size = h_screen_height;
163                 font_x_size = h_font_width;
164                 font_y_size = h_font_height;
165         }
166
167         if (h_version >= V5) {
168                 SET_WORD (H_SCREEN_WIDTH, screen_x_size);
169                 SET_WORD (H_SCREEN_HEIGHT, screen_y_size);
170                 SET_BYTE (H_FONT_HEIGHT, font_y_size);
171                 SET_BYTE (H_FONT_WIDTH, font_x_size);
172                 SET_BYTE (H_DEFAULT_BACKGROUND, h_default_background);
173                 SET_BYTE (H_DEFAULT_FOREGROUND, h_default_foreground);
174         }
175
176         if (h_version == V6)
177                 for (i = 0; i < 8; i++)
178                         storeb ((zword) (H_USER_NAME + i), h_user_name[i]);
179
180         SET_BYTE (H_STANDARD_HIGH, h_standard_high);
181         SET_BYTE (H_STANDARD_LOW, h_standard_low);
182
183 }/* restart_header */
184
185 /*
186  * init_memory
187  *
188  * Allocate memory and load the story file.
189  *
190  */
191
192 void init_memory (void)
193 {
194         long size;
195         zword addr;
196         unsigned n;
197         int i, j;
198
199         static struct {
200                 enum story story_id;
201                 zword release;
202                 zbyte serial[6];
203         } records[] = {
204                 {       SHERLOCK,  21, "871214" },
205                 {       SHERLOCK,  26, "880127" },
206                 {    BEYOND_ZORK,  47, "870915" },
207                 {    BEYOND_ZORK,  49, "870917" },
208                 {    BEYOND_ZORK,  51, "870923" },
209                 {    BEYOND_ZORK,  57, "871221" },
210                 {      ZORK_ZERO, 296, "881019" },
211                 {      ZORK_ZERO, 366, "890323" },
212                 {      ZORK_ZERO, 383, "890602" },
213                 {      ZORK_ZERO, 393, "890714" },
214                 {         SHOGUN, 292, "890314" },
215                 {         SHOGUN, 295, "890321" },
216                 {         SHOGUN, 311, "890510" },
217                 {         SHOGUN, 322, "890706" },
218                 {         ARTHUR,  54, "890606" },
219                 {         ARTHUR,  63, "890622" },
220                 {         ARTHUR,  74, "890714" },
221                 {        JOURNEY,  26, "890316" },
222                 {        JOURNEY,  30, "890322" },
223                 {        JOURNEY,  77, "890616" },
224                 {        JOURNEY,  83, "890706" },
225                 { LURKING_HORROR, 203, "870506" },
226                 { LURKING_HORROR, 219, "870912" },
227                 { LURKING_HORROR, 221, "870918" },
228                 {        UNKNOWN,   0, "------" }
229         };
230
231         /* Open story file */
232         {
233                 giblorb_map_t *map;
234                 giblorb_result_t res;
235                 char magic[4] = "XXXX";
236                 strid_t file;
237
238                 if ((file = glkunix_stream_open_pathname(story_name, 0, 0)) == NULL)
239                         os_fatal ("Cannot open story file");
240
241                 fread(magic, 1, 4, file);
242
243                 if (!memcmp(magic, "FORM", 4))
244                 {
245                         if (giblorb_set_resource_map(file))
246                                 os_fatal("This Blorb file seems to be invalid.");
247
248                         map = giblorb_get_resource_map();
249
250                         if (giblorb_load_resource(map, giblorb_method_FilePos,
251                                                 &res, giblorb_ID_Exec, 0))
252                                 os_fatal("This Blorb file does not contain an executable chunk.");
253                         if (res.chunktype != giblorb_make_id('Z','C','O','D'))
254                                 os_fatal("This Blorb file contains an executable chunk, but it is not a Z-code file.");
255
256                         story_fp = file;
257                         blorb_ofs = res.data.startpos;
258                         blorb_len = res.length;
259                 }
260                 else
261                 {
262                         story_fp = file;        
263                         blorb_ofs = 0;
264                         fseek(story_fp, 0, SEEK_END);
265                         blorb_len = ftell(story_fp);
266                 }
267
268         }
269
270         if (blorb_len < 64)
271                 os_fatal("This file is too small to be a Z-code file.");
272
273         /* Allocate memory for story header */
274
275         if ((zmp = (zbyte *) malloc (64)) == NULL)
276                 os_fatal ("Out of memory");
277
278         /* Load header into memory */
279
280         fseek(story_fp, blorb_ofs, 0);
281
282         if (fread (zmp, 1, 64, story_fp) != 64)
283                 os_fatal ("Story file read error");
284
285         /* Copy header fields to global variables */
286
287         LOW_BYTE (H_VERSION, h_version);
288
289         if (h_version < V1 || h_version > V8)
290                 os_fatal ("Unknown Z-code version");
291
292         if (h_version == V6)
293                 os_fatal ("Cannot play Z-code version 6");
294
295         LOW_BYTE (H_CONFIG, h_config);
296
297         if (h_version == V3 && (h_config & CONFIG_BYTE_SWAPPED))
298                 os_fatal ("Byte swapped story file");
299
300         LOW_WORD (H_RELEASE, h_release);
301         LOW_WORD (H_RESIDENT_SIZE, h_resident_size);
302         LOW_WORD (H_START_PC, h_start_pc);
303         LOW_WORD (H_DICTIONARY, h_dictionary);
304         LOW_WORD (H_OBJECTS, h_objects);
305         LOW_WORD (H_GLOBALS, h_globals);
306         LOW_WORD (H_DYNAMIC_SIZE, h_dynamic_size);
307         LOW_WORD (H_FLAGS, h_flags);
308
309         for (i = 0, addr = H_SERIAL; i < 6; i++, addr++)
310                 LOW_BYTE (addr, h_serial[i]);
311
312         /* Auto-detect buggy story files that need special fixes */
313
314         story_id = UNKNOWN;
315
316         for (i = 0; records[i].story_id != UNKNOWN; i++) {
317
318                 if (h_release == records[i].release) {
319
320                         for (j = 0; j < 6; j++)
321                                 if (h_serial[j] != records[i].serial[j])
322                                         goto no_match;
323
324                         story_id = records[i].story_id;
325
326                 }
327
328 no_match: ; /* null statement */
329
330         }
331
332         LOW_WORD (H_ABBREVIATIONS, h_abbreviations);
333         LOW_WORD (H_FILE_SIZE, h_file_size);
334
335         /* Calculate story file size in bytes */
336
337         if (h_file_size != 0) {
338
339                 story_size = (long) 2 * h_file_size;
340
341                 if (h_version >= V4)
342                         story_size *= 2;
343                 if (h_version >= V6)
344                         story_size *= 2;
345
346         } else {                /* some old games lack the file size entry */
347
348                 story_size = blorb_len;
349         }
350
351         LOW_WORD (H_CHECKSUM, h_checksum);
352         LOW_WORD (H_ALPHABET, h_alphabet);
353         LOW_WORD (H_FUNCTIONS_OFFSET, h_functions_offset);
354         LOW_WORD (H_STRINGS_OFFSET, h_strings_offset);
355         LOW_WORD (H_TERMINATING_KEYS, h_terminating_keys);
356         LOW_WORD (H_EXTENSION_TABLE, h_extension_table);
357
358         /* Zork Zero Macintosh doesn't have the graphics flag set */
359
360         if (story_id == ZORK_ZERO && h_release == 296)
361                 h_flags |= GRAPHICS_FLAG;
362
363         /* Adjust opcode tables */
364
365         if (h_version <= V4) {
366                 op0_opcodes[0x09] = z_pop;
367                 op1_opcodes[0x0f] = z_not;
368         } else {
369                 op0_opcodes[0x09] = z_catch;
370                 op1_opcodes[0x0f] = z_call_n;
371         }
372
373         /* Allocate memory for story data */
374
375         if ((zmp = (zbyte *) realloc (zmp, story_size)) == NULL)
376                 os_fatal ("Out of memory");
377
378         /* Load story file in chunks of 32KB */
379
380         n = 0x8000;
381
382         for (size = 64; size < story_size; size += n) {
383
384                 if (story_size - size < 0x8000)
385                         n = (unsigned) (story_size - size);
386
387                 SET_PC (size);
388
389                 if (fread (pcp, 1, n, story_fp) != n)
390                         os_fatal ("Story file read error");
391
392         }
393
394         /* Read header extension table */
395
396         hx_table_size = get_header_extension (HX_TABLE_SIZE);
397         hx_unicode_table = get_header_extension (HX_UNICODE_TABLE);
398
399 }/* init_memory */
400
401 /*
402  * init_undo
403  *
404  * Allocate memory for multiple undo. It is important not to occupy
405  * all the memory available, since the IO interface may need memory
406  * during the game, e.g. for loading sounds or pictures.
407  *
408  */
409
410 void init_undo (void)
411 {
412         void *reserved;
413
414         reserved = NULL;        /* makes compilers shut up */
415
416         if (reserve_mem != 0) {
417                 if ((reserved = malloc (reserve_mem)) == NULL)
418                         return;
419         }
420
421         /* Allocate h_dynamic_size bytes for previous dynamic zmp state
422            + 1.5 h_dynamic_size for Quetzal diff + 2. */
423         undo_mem = malloc ((h_dynamic_size * 5) / 2 + 2);
424         if (undo_mem != NULL) {
425                 prev_zmp = undo_mem;
426                 undo_diff = undo_mem + h_dynamic_size;
427                 memcpy (prev_zmp, zmp, h_dynamic_size);
428         } else
429                 f_setup.undo_slots = 0;
430
431         if (reserve_mem != 0)
432                 free (reserved);
433
434 }/* init_undo */
435
436 /*
437  * free_undo
438  *
439  * Free count undo blocks from the beginning of the undo list.
440  *
441  */
442
443 static void free_undo (int count)
444 {
445         undo_t *p;
446
447         if (count > undo_count)
448                 count = undo_count;
449         while (count--) {
450                 p = first_undo;
451                 if (curr_undo == first_undo)
452                         curr_undo = curr_undo->next;
453                 first_undo = first_undo->next;
454                 free (p);
455                 undo_count--;
456         }
457         if (first_undo)
458                 first_undo->prev = NULL;
459         else
460                 last_undo = NULL;
461 }/* free_undo */
462
463 /*
464  * reset_memory
465  *
466  * Close the story file and deallocate memory.
467  *
468  */
469
470 void reset_memory (void)
471 {
472         if (story_fp) 
473                 fclose (story_fp);
474         story_fp = NULL;
475         blorb_ofs = 0;
476         blorb_len = 0;
477
478         if (undo_mem) {
479                 free_undo (undo_count);
480                 free (undo_mem);
481         }
482
483         undo_mem = NULL;
484         undo_count = 0;
485
486         if (zmp)
487                 free (zmp);
488         zmp = NULL;
489 }/* reset_memory */
490
491 /*
492  * storeb
493  *
494  * Write a byte value to the dynamic Z-machine memory.
495  *
496  */
497
498 void storeb (zword addr, zbyte value)
499 {
500
501         if (addr >= h_dynamic_size)
502                 runtime_error (ERR_STORE_RANGE);
503
504         if (addr == H_FLAGS + 1) {      /* flags register is modified */
505
506                 h_flags &= ~(SCRIPTING_FLAG | FIXED_FONT_FLAG);
507                 h_flags |= value & (SCRIPTING_FLAG | FIXED_FONT_FLAG);
508
509                 if (value & SCRIPTING_FLAG) {
510                         if (!ostream_script)
511                                 script_open ();
512                 } else {
513                         if (ostream_script)
514                                 script_close ();
515                 }
516
517                 /* TOR - glkified / refresh_text_style (); */
518
519         }
520
521         SET_BYTE (addr, value);
522
523 }/* storeb */
524
525 /*
526  * storew
527  *
528  * Write a word value to the dynamic Z-machine memory.
529  *
530  */
531
532 void storew (zword addr, zword value)
533 {
534
535         storeb ((zword) (addr + 0), hi (value));
536         storeb ((zword) (addr + 1), lo (value));
537
538 }/* storew */
539
540 /*
541  * z_restart, re-load dynamic area, clear the stack and set the PC.
542  *
543  *      no zargs used
544  *
545  */
546
547 void z_restart (void)
548 {
549         static bool first_restart = TRUE;
550
551         flush_buffer ();
552
553         os_restart_game (RESTART_BEGIN);
554
555         seed_random (0);
556
557         if (!first_restart) {
558
559                 fseek (story_fp, blorb_ofs, SEEK_SET);
560
561                 if (fread (zmp, 1, h_dynamic_size, story_fp) != h_dynamic_size)
562                         os_fatal ("Story file read error");
563
564         } else first_restart = FALSE;
565
566         restart_header ();
567         restart_screen ();
568
569         sp = fp = stack + STACK_SIZE;
570         frame_count = 0;
571
572         if (h_version != V6) {
573
574                 long pc = (long) h_start_pc;
575                 SET_PC (pc);
576
577         } else call (h_start_pc, 0, NULL, 0);
578
579         os_restart_game (RESTART_END);
580
581 }/* z_restart */
582
583 /*
584  * z_restore, restore [a part of] a Z-machine state from disk
585  *
586  *      zargs[0] = address of area to restore (optional)
587  *      zargs[1] = number of bytes to restore
588  *      zargs[2] = address of suggested file name
589  *
590  */
591
592 void z_restore (void)
593 {
594         FILE *gfp;
595
596         zword success = 0;
597
598         if (zargc != 0) {
599
600                 /* Get the file name */
601
602                 /* Open auxilary file */
603
604                 if ((gfp = frotzopenprompt(FILE_LOAD_AUX)) == NULL)
605                         goto finished;
606
607                 /* Load auxilary file */
608
609                 success = fread (zmp + zargs[0], 1, zargs[1], gfp);
610
611                 /* Close auxilary file */
612
613                 fclose (gfp);
614
615         } else {
616
617                 long pc;
618                 zword release;
619                 zword addr;
620                 int i;
621
622                 /* Open game file */
623
624                 if ((gfp = frotzopenprompt(FILE_RESTORE)) == NULL)
625                         goto finished;
626
627                 if (f_setup.save_quetzal) {
628                         success = restore_quetzal (gfp, story_fp, blorb_ofs);
629
630                 } else {
631                         /* Load game file */
632
633                         release = (unsigned) fgetc (gfp) << 8;
634                         release |= fgetc (gfp);
635
636                         (void) fgetc (gfp);
637                         (void) fgetc (gfp);
638
639                         /* Check the release number */
640
641                         if (release == h_release) {
642
643                                 pc = (long) fgetc (gfp) << 16;
644                                 pc |= (unsigned) fgetc (gfp) << 8;
645                                 pc |= fgetc (gfp);
646
647                                 SET_PC (pc);
648
649                                 sp = stack + (fgetc (gfp) << 8);
650                                 sp += fgetc (gfp);
651                                 fp = stack + (fgetc (gfp) << 8);
652                                 fp += fgetc (gfp);
653
654                                 for (i = (int) (sp - stack); i < STACK_SIZE; i++) {
655                                         stack[i] = (unsigned) fgetc (gfp) << 8;
656                                         stack[i] |= fgetc (gfp);
657                                 }
658
659                                 fseek (story_fp, blorb_ofs, SEEK_SET);
660
661                                 for (addr = 0; addr < h_dynamic_size; addr++) {
662                                         int skip = fgetc (gfp);
663                                         for (i = 0; i < skip; i++)
664                                                 zmp[addr++] = fgetc (story_fp);
665                                         zmp[addr] = fgetc (gfp);
666                                         (void) fgetc (story_fp);
667                                 }
668
669                                 /* Check for errors */
670
671                                 if (ferror (gfp) || ferror (story_fp) || addr != h_dynamic_size)
672                                         success = -1;
673                                 else
674
675                                         /* Success */
676
677                                         success = 2;
678
679                         } else print_string ("Invalid save file\n");
680                 }
681
682                 if ((short) success >= 0) {
683
684                         /* Close game file */
685
686                         fclose (gfp);
687
688                         if ((short) success > 0) {
689                                 zbyte old_screen_rows;
690                                 zbyte old_screen_cols;
691
692                                 /* In V3, reset the upper window. */
693                                 if (h_version == V3)
694                                         split_window (0);
695
696                                 LOW_BYTE (H_SCREEN_ROWS, old_screen_rows);
697                                 LOW_BYTE (H_SCREEN_COLS, old_screen_cols);
698
699                                 /* Reload cached header fields. */
700                                 restart_header ();
701
702                                 /*
703                                  * Since QUETZAL files may be saved on many different machines,
704                                  * the screen sizes may vary a lot. Erasing the status window
705                                  * seems to cover up most of the resulting badness.
706                                  */
707                                 if (h_version > V3 && h_version != V6
708                                                 && (h_screen_rows != old_screen_rows
709                                                         || h_screen_cols != old_screen_cols))
710                                         erase_window (1);
711                         }
712                 } else
713                         os_fatal ("Error reading save file");
714         }
715
716 finished:
717
718         if (h_version <= V3)
719                 branch (success);
720         else
721                 store (success);
722
723 }/* z_restore */
724
725 /*
726  * mem_diff
727  *
728  * Set diff to a Quetzal-like difference between a and b,
729  * copying a to b as we go.  It is assumed that diff points to a
730  * buffer which is large enough to hold the diff.
731  * mem_size is the number of bytes to compare.
732  * Returns the number of bytes copied to diff.
733  *
734  */
735
736 static long mem_diff (zbyte *a, zbyte *b, zword mem_size, zbyte *diff)
737 {
738         unsigned size = mem_size;
739         zbyte *p = diff;
740         unsigned j;
741         zbyte c;
742
743         for (;;) {
744                 for (j = 0; size > 0 && (c = *a++ ^ *b++) == 0; j++)
745                         size--;
746                 if (size == 0) break;
747                 size--;
748                 if (j > 0x8000) {
749                         *p++ = 0;
750                         *p++ = 0xff;
751                         *p++ = 0xff;
752                         j -= 0x8000;
753                 }
754                 if (j > 0) {
755                         *p++ = 0;
756                         j--;
757                         if (j <= 0x7f) {
758                                 *p++ = j;
759                         } else {
760                                 *p++ = (j & 0x7f) | 0x80;
761                                 *p++ = (j & 0x7f80) >> 7;
762                         }
763                 }
764                 *p++ = c;
765                 *(b - 1) ^= c;
766         }
767         return p - diff;
768 }/* mem_diff */
769
770 /*
771  * mem_undiff
772  *
773  * Applies a quetzal-like diff to dest
774  *
775  */
776
777 static void mem_undiff (zbyte *diff, long diff_length, zbyte *dest)
778 {
779         zbyte c;
780
781         while (diff_length) {
782                 c = *diff++;
783                 diff_length--;
784                 if (c == 0) {
785                         unsigned runlen;
786
787                         if (!diff_length)
788                                 return;  /* Incomplete run */
789                         runlen = *diff++;
790                         diff_length--;
791                         if (runlen & 0x80) {
792                                 if (!diff_length)
793                                         return; /* Incomplete extended run */
794                                 c = *diff++;
795                                 diff_length--;
796                                 runlen = (runlen & 0x7f) | (((unsigned) c) << 7);
797                         }
798
799                         dest += runlen + 1;
800                 } else {
801                         *dest++ ^= c;
802                 }
803         }
804 }/* mem_undiff */
805
806 /*
807  * restore_undo
808  *
809  * This function does the dirty work for z_restore_undo.
810  *
811  */
812
813 int restore_undo (void)
814 {
815
816         if (f_setup.undo_slots == 0)    /* undo feature unavailable */
817
818                 return -1;
819
820         if (curr_undo == NULL)          /* no saved game state */
821
822                 return 0;
823
824         /* undo possible */
825
826         memcpy (zmp, prev_zmp, h_dynamic_size);
827         SET_PC (curr_undo->pc);
828         sp = stack + STACK_SIZE - curr_undo->stack_size;
829         fp = stack + curr_undo->frame_offset;
830         frame_count = curr_undo->frame_count;
831         mem_undiff ((zbyte *) (curr_undo + 1), curr_undo->diff_size, prev_zmp);
832         memcpy (sp, (zbyte *)(curr_undo + 1) + curr_undo->diff_size,
833                         curr_undo->stack_size * sizeof (*sp));
834
835         curr_undo = curr_undo->prev;
836
837         restart_header ();
838
839         return 2;
840
841 }/* restore_undo */
842
843 /*
844  * z_restore_undo, restore a Z-machine state from memory.
845  *
846  *      no zargs used
847  *
848  */
849
850 void z_restore_undo (void)
851 {
852
853         store ((zword) restore_undo ());
854
855 }/* z_restore_undo */
856
857 /*
858  * z_save, save [a part of] the Z-machine state to disk.
859  *
860  *      zargs[0] = address of memory area to save (optional)
861  *      zargs[1] = number of bytes to save
862  *      zargs[2] = address of suggested file name
863  *
864  */
865
866 void z_save (void)
867 {
868         FILE *gfp;
869
870         zword success = 0;
871
872         if (zargc != 0) {
873
874                 /* Open auxilary file */
875
876                 if ((gfp = frotzopenprompt (FILE_SAVE_AUX)) == NULL)
877                         goto finished;
878
879                 /* Write auxilary file */
880
881                 success = fwrite (zmp + zargs[0], zargs[1], 1, gfp);
882
883                 /* Close auxilary file */
884
885                 fclose (gfp);
886
887         } else {
888
889                 long pc;
890                 zword addr;
891                 zword nsp, nfp;
892                 int skip;
893                 int i;
894
895                 /* Open game file */
896
897                 if ((gfp = frotzopenprompt (FILE_SAVE)) == NULL)
898                         goto finished;
899
900                 if (f_setup.save_quetzal) {
901                         success = save_quetzal (gfp, story_fp, blorb_ofs);
902                 } else {
903                         /* Write game file */
904
905                         fputc ((int) hi (h_release), gfp);
906                         fputc ((int) lo (h_release), gfp);
907                         fputc ((int) hi (h_checksum), gfp);
908                         fputc ((int) lo (h_checksum), gfp);
909
910                         GET_PC (pc)
911
912                                 fputc ((int) (pc >> 16) & 0xff, gfp);
913                         fputc ((int) (pc >> 8) & 0xff, gfp);
914                         fputc ((int) (pc) & 0xff, gfp);
915
916                         nsp = (int) (sp - stack);
917                         nfp = (int) (fp - stack);
918
919                         fputc ((int) hi (nsp), gfp);
920                         fputc ((int) lo (nsp), gfp);
921                         fputc ((int) hi (nfp), gfp);
922                         fputc ((int) lo (nfp), gfp);
923
924                         for (i = nsp; i < STACK_SIZE; i++) {
925                                 fputc ((int) hi (stack[i]), gfp);
926                                 fputc ((int) lo (stack[i]), gfp);
927                         }
928
929                         fseek (story_fp, blorb_ofs, SEEK_SET);
930
931                         for (addr = 0, skip = 0; addr < h_dynamic_size; addr++)
932                                 if (zmp[addr] != fgetc (story_fp) || skip == 255 || addr + 1 == h_dynamic_size) {
933                                         fputc (skip, gfp);
934                                         fputc (zmp[addr], gfp);
935                                         skip = 0;
936                                 } else skip++;
937                 }
938
939                 /* Close game file and check for errors */
940
941                 if (fclose (gfp) == EOF || ferror (story_fp)) {
942                         print_string ("Error writing save file\n");
943                         goto finished;
944                 }
945
946                 /* Success */
947
948                 success = 1;
949
950         }
951
952 finished:
953
954         if (h_version <= V3)
955                 branch (success);
956         else
957                 store (success);
958
959 }/* z_save */
960
961 /*
962  * save_undo
963  *
964  * This function does the dirty work for z_save_undo.
965  *
966  */
967
968 int save_undo (void)
969 {
970         long diff_size;
971         zword stack_size;
972         undo_t *p;
973
974         if (f_setup.undo_slots == 0)    /* undo feature unavailable */
975                 return -1;
976
977         /* save undo possible */
978
979         while (last_undo != curr_undo) {
980                 p = last_undo;
981                 last_undo = last_undo->prev;
982                 free (p);
983                 undo_count--;
984         }
985         if (last_undo)
986                 last_undo->next = NULL;
987         else
988                 first_undo = NULL;
989
990         if (undo_count == f_setup.undo_slots)
991                 free_undo (1);
992
993         diff_size = mem_diff (zmp, prev_zmp, h_dynamic_size, undo_diff);
994         stack_size = stack + STACK_SIZE - sp;
995         do {
996                 p = malloc (sizeof (undo_t) + diff_size + stack_size * sizeof (*sp));
997                 if (p == NULL)
998                         free_undo (1);
999         } while (!p && undo_count);
1000         if (p == NULL)
1001                 return -1;
1002         GET_PC (p->pc)
1003                 p->frame_count = frame_count;
1004         p->diff_size = diff_size;
1005         p->stack_size = stack_size;
1006         p->frame_offset = fp - stack;
1007         memcpy (p + 1, undo_diff, diff_size);
1008         memcpy ((zbyte *)(p + 1) + diff_size, sp, stack_size * sizeof (*sp));
1009
1010         if (!first_undo) {
1011                 p->prev = NULL;
1012                 first_undo = p;
1013         } else {
1014                 last_undo->next = p;
1015                 p->prev = last_undo;
1016         }
1017         p->next = NULL;
1018         curr_undo = last_undo = p;
1019         undo_count++;
1020         return 1;
1021
1022 }/* save_undo */
1023
1024 /*
1025  * z_save_undo, save the current Z-machine state for a future undo.
1026  *
1027  *      no zargs used
1028  *
1029  */
1030
1031 void z_save_undo (void)
1032 {
1033
1034         store ((zword) save_undo ());
1035
1036 }/* z_save_undo */
1037
1038 /*
1039  * z_verify, check the story file integrity.
1040  *
1041  *      no zargs used
1042  *
1043  */
1044
1045 void z_verify (void)
1046 {
1047         zword checksum = 0;
1048         long i;
1049
1050         /* Sum all bytes in story file except header bytes */
1051
1052         fseek (story_fp, blorb_ofs + 64, SEEK_SET);
1053
1054         for (i = 64; i < story_size; i++)
1055                 checksum += fgetc (story_fp);
1056
1057         /* Branch if the checksums are equal */
1058
1059         branch (checksum == h_checksum);
1060
1061 }/* z_verify */