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