1 /* fastmem.c - Memory related functions (fast version without virtual memory)
2 * Copyright (c) 1995-1997 Stefan Jokisch
4 * This file is part of Frotz.
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.
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.
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
22 * New undo mechanism added by Jim Dunleavy <jim.dunleavy@erha.ie>
26 * Glk and Blorb support added by Tor Andersson.
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);
44 extern zword save_quetzal (FILE *, FILE *, int);
45 extern zword restore_quetzal (FILE *, FILE *, int);
47 extern void erase_window (zword);
49 extern void (*op0_opcodes[]) (void);
50 extern void (*op1_opcodes[]) (void);
51 extern void (*op2_opcodes[]) (void);
52 extern void (*var_opcodes[]) (void);
57 static FILE *story_fp = NULL;
58 static size_t blorb_ofs = 0;
59 static size_t blorb_len = 0;
62 * Data for the undo mechanism.
63 * This undo mechanism is based on the scheme used in Evin Robertson's
65 * Undo blocks are stored as differences between states.
68 typedef struct undo_struct undo_t;
77 /* undo diff and stack data follow */
80 static undo_t *first_undo = NULL, *last_undo = NULL, *curr_undo = NULL;
81 static zbyte *undo_mem = NULL, *prev_zmp, *undo_diff;
83 static int undo_count = 0;
86 * get_header_extension
88 * Read a value from the header extension (former mouse table).
92 zword get_header_extension (int entry)
97 if (h_extension_table == 0 || entry > hx_table_size)
100 addr = h_extension_table + 2 * entry;
101 LOW_WORD (addr, val);
105 }/* get_header_extension */
108 * set_header_extension
110 * Set an entry in the header extension (former mouse table).
114 void set_header_extension (int entry, zword val)
118 if (h_extension_table == 0 || entry > hx_table_size)
121 addr = h_extension_table + 2 * entry;
122 SET_WORD (addr, val);
124 }/* set_header_extension */
129 * Set all header fields which hold information about the interpreter.
133 void restart_header (void)
142 SET_BYTE (H_CONFIG, h_config);
143 SET_WORD (H_FLAGS, h_flags);
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);
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" */
155 if (h_version != V6) {
156 screen_x_size = (zword) h_screen_cols;
157 screen_y_size = (zword) h_screen_rows;
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;
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);
177 for (i = 0; i < 8; i++)
178 storeb ((zword) (H_USER_NAME + i), h_user_name[i]);
180 SET_BYTE (H_STANDARD_HIGH, h_standard_high);
181 SET_BYTE (H_STANDARD_LOW, h_standard_low);
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);
187 }/* restart_header */
192 * Allocate memory and load the story file.
196 void init_memory (void)
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, "------" }
235 /* Open story file */
238 giblorb_result_t res;
239 char magic[4] = "XXXX";
242 if ((file = glkunix_stream_open_pathname(story_name, 0, 0)) == NULL)
243 os_fatal ("Cannot open story file");
245 fread(magic, 1, 4, file);
247 if (!memcmp(magic, "FORM", 4))
249 if (giblorb_set_resource_map(file))
250 os_fatal("This Blorb file seems to be invalid.");
252 map = giblorb_get_resource_map();
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.");
261 blorb_ofs = res.data.startpos;
262 blorb_len = res.length;
268 fseek(story_fp, 0, SEEK_END);
269 blorb_len = ftell(story_fp);
275 os_fatal("This file is too small to be a Z-code file.");
277 /* Allocate memory for story header */
279 if ((zmp = (zbyte *) malloc (64)) == NULL)
280 os_fatal ("Out of memory");
282 /* Load header into memory */
284 fseek(story_fp, blorb_ofs, 0);
286 if (fread (zmp, 1, 64, story_fp) != 64)
287 os_fatal ("Story file read error");
289 /* Copy header fields to global variables */
291 LOW_BYTE (H_VERSION, h_version);
293 if (h_version < V1 || h_version > V8)
294 os_fatal ("Unknown Z-code version");
297 os_fatal ("Cannot play Z-code version 6");
299 LOW_BYTE (H_CONFIG, h_config);
301 if (h_version == V3 && (h_config & CONFIG_BYTE_SWAPPED))
302 os_fatal ("Byte swapped story file");
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);
313 for (i = 0, addr = H_SERIAL; i < 6; i++, addr++)
314 LOW_BYTE (addr, h_serial[i]);
316 /* Auto-detect buggy story files that need special fixes */
320 for (i = 0; records[i].story_id != UNKNOWN; i++) {
322 if (h_release == records[i].release) {
324 for (j = 0; j < 6; j++)
325 if (h_serial[j] != records[i].serial[j])
328 story_id = records[i].story_id;
332 no_match: ; /* null statement */
336 LOW_WORD (H_ABBREVIATIONS, h_abbreviations);
337 LOW_WORD (H_FILE_SIZE, h_file_size);
339 /* Calculate story file size in bytes */
341 if (h_file_size != 0) {
343 story_size = (long) 2 * h_file_size;
350 } else { /* some old games lack the file size entry */
352 story_size = blorb_len;
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);
362 /* Zork Zero Macintosh doesn't have the graphics flag set */
364 if (story_id == ZORK_ZERO && h_release == 296)
365 h_flags |= GRAPHICS_FLAG;
367 /* Adjust opcode tables */
369 if (h_version <= V4) {
370 op0_opcodes[0x09] = z_pop;
371 op1_opcodes[0x0f] = z_not;
373 op0_opcodes[0x09] = z_catch;
374 op1_opcodes[0x0f] = z_call_n;
377 /* Allocate memory for story data */
379 if ((zmp = (zbyte *) realloc (zmp, story_size)) == NULL)
380 os_fatal ("Out of memory");
382 /* Load story file in chunks of 32KB */
386 for (size = 64; size < story_size; size += n) {
388 if (story_size - size < 0x8000)
389 n = (unsigned) (story_size - size);
393 if (fread (pcp, 1, n, story_fp) != n)
394 os_fatal ("Story file read error");
398 /* Read header extension table */
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);
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.
415 void init_undo (void)
419 reserved = NULL; /* makes compilers shut up */
421 if (reserve_mem != 0) {
422 if ((reserved = malloc (reserve_mem)) == NULL)
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) {
431 undo_diff = undo_mem + h_dynamic_size;
432 memcpy (prev_zmp, zmp, h_dynamic_size);
434 option_undo_slots = 0;
436 if (reserve_mem != 0)
444 * Free count undo blocks from the beginning of the undo list.
448 static void free_undo (int count)
452 if (count > undo_count)
456 if (curr_undo == first_undo)
457 curr_undo = curr_undo->next;
458 first_undo = first_undo->next;
463 first_undo->prev = NULL;
471 * Close the story file and deallocate memory.
475 void reset_memory (void)
484 free_undo (undo_count);
499 * Write a byte value to the dynamic Z-machine memory.
503 void storeb (zword addr, zbyte value)
506 if (addr >= h_dynamic_size)
507 runtime_error (ERR_STORE_RANGE);
509 if (addr == H_FLAGS + 1) { /* flags register is modified */
511 h_flags &= ~(SCRIPTING_FLAG | FIXED_FONT_FLAG);
512 h_flags |= value & (SCRIPTING_FLAG | FIXED_FONT_FLAG);
514 if (value & SCRIPTING_FLAG) {
522 /* TOR - glkified / refresh_text_style (); */
526 SET_BYTE (addr, value);
533 * Write a word value to the dynamic Z-machine memory.
537 void storew (zword addr, zword value)
540 storeb ((zword) (addr + 0), hi (value));
541 storeb ((zword) (addr + 1), lo (value));
546 * z_restart, re-load dynamic area, clear the stack and set the PC.
552 void z_restart (void)
554 static bool first_restart = TRUE;
558 os_restart_game (RESTART_BEGIN);
562 if (!first_restart) {
564 fseek (story_fp, blorb_ofs, SEEK_SET);
566 if (fread (zmp, 1, h_dynamic_size, story_fp) != h_dynamic_size)
567 os_fatal ("Story file read error");
569 } else first_restart = FALSE;
574 sp = fp = stack + STACK_SIZE;
577 if (h_version != V6 && h_version != V9) {
579 long pc = (long) h_start_pc;
582 } else call (h_start_pc, 0, NULL, 0);
584 os_restart_game (RESTART_END);
589 * z_restore, restore [a part of] a Z-machine state from disk
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
597 void z_restore (void)
605 /* Get the file name */
607 /* Open auxilary file */
609 if ((gfp = frotzopenprompt(FILE_LOAD_AUX)) == NULL)
612 /* Load auxilary file */
614 success = fread (zmp + zargs[0], 1, zargs[1], gfp);
616 /* Close auxilary file */
629 if ((gfp = frotzopenprompt(FILE_RESTORE)) == NULL)
632 if (option_save_quetzal) {
633 success = restore_quetzal (gfp, story_fp, blorb_ofs);
638 release = (unsigned) fgetc (gfp) << 8;
639 release |= fgetc (gfp);
644 /* Check the release number */
646 if (release == h_release) {
648 pc = (long) fgetc (gfp) << 16;
649 pc |= (unsigned) fgetc (gfp) << 8;
654 sp = stack + (fgetc (gfp) << 8);
656 fp = stack + (fgetc (gfp) << 8);
659 for (i = (int) (sp - stack); i < STACK_SIZE; i++) {
660 stack[i] = (unsigned) fgetc (gfp) << 8;
661 stack[i] |= fgetc (gfp);
664 fseek (story_fp, blorb_ofs, SEEK_SET);
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);
674 /* Check for errors */
676 if (ferror (gfp) || ferror (story_fp) || addr != h_dynamic_size)
684 } else print_string ("Invalid save file\n");
687 if ((short) success >= 0) {
689 /* Close game file */
693 if ((short) success > 0) {
694 zbyte old_screen_rows;
695 zbyte old_screen_cols;
697 /* In V3, reset the upper window. */
701 LOW_BYTE (H_SCREEN_ROWS, old_screen_rows);
702 LOW_BYTE (H_SCREEN_COLS, old_screen_cols);
704 /* Reload cached header fields. */
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.
712 if (h_version > V3 && h_version != V6
713 && (h_screen_rows != old_screen_rows
714 || h_screen_cols != old_screen_cols))
718 os_fatal ("Error reading save file");
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.
741 static long mem_diff (zbyte *a, zbyte *b, zword mem_size, zbyte *diff)
743 unsigned size = mem_size;
749 for (j = 0; size > 0 && (c = *a++ ^ *b++) == 0; j++)
751 if (size == 0) break;
765 *p++ = (j & 0x7f) | 0x80;
766 *p++ = (j & 0x7f80) >> 7;
778 * Applies a quetzal-like diff to dest
782 static void mem_undiff (zbyte *diff, long diff_length, zbyte *dest)
786 while (diff_length) {
793 return; /* Incomplete run */
798 return; /* Incomplete extended run */
801 runlen = (runlen & 0x7f) | (((unsigned) c) << 7);
814 * This function does the dirty work for z_restore_undo.
818 int restore_undo (void)
821 if (option_undo_slots == 0) /* undo feature unavailable */
825 if (curr_undo == NULL) /* no saved game state */
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));
840 curr_undo = curr_undo->prev;
849 * z_restore_undo, restore a Z-machine state from memory.
855 void z_restore_undo (void)
858 store ((zword) restore_undo ());
860 }/* z_restore_undo */
863 * z_save, save [a part of] the Z-machine state to disk.
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
879 /* Open auxilary file */
881 if ((gfp = frotzopenprompt (FILE_SAVE_AUX)) == NULL)
884 /* Write auxilary file */
886 success = fwrite (zmp + zargs[0], zargs[1], 1, gfp);
888 /* Close auxilary file */
902 if ((gfp = frotzopenprompt (FILE_SAVE)) == NULL)
905 if (option_save_quetzal) {
906 success = save_quetzal (gfp, story_fp, blorb_ofs);
908 /* Write game file */
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);
917 fputc ((int) (pc >> 16) & 0xff, gfp);
918 fputc ((int) (pc >> 8) & 0xff, gfp);
919 fputc ((int) (pc) & 0xff, gfp);
921 nsp = (int) (sp - stack);
922 nfp = (int) (fp - stack);
924 fputc ((int) hi (nsp), gfp);
925 fputc ((int) lo (nsp), gfp);
926 fputc ((int) hi (nfp), gfp);
927 fputc ((int) lo (nfp), gfp);
929 for (i = nsp; i < STACK_SIZE; i++) {
930 fputc ((int) hi (stack[i]), gfp);
931 fputc ((int) lo (stack[i]), gfp);
934 fseek (story_fp, blorb_ofs, SEEK_SET);
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) {
939 fputc (zmp[addr], gfp);
944 /* Close game file and check for errors */
946 if (fclose (gfp) == EOF || ferror (story_fp)) {
947 print_string ("Error writing save file\n");
969 * This function does the dirty work for z_save_undo.
979 if (option_undo_slots == 0) /* undo feature unavailable */
982 /* save undo possible */
984 while (last_undo != curr_undo) {
986 last_undo = last_undo->prev;
991 last_undo->next = NULL;
995 if (undo_count == option_undo_slots)
998 diff_size = mem_diff (zmp, prev_zmp, h_dynamic_size, undo_diff);
999 stack_size = stack + STACK_SIZE - sp;
1001 p = malloc (sizeof (undo_t) + diff_size + stack_size * sizeof (*sp));
1004 } while (!p && undo_count);
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));
1019 last_undo->next = p;
1020 p->prev = last_undo;
1023 curr_undo = last_undo = p;
1030 * z_save_undo, save the current Z-machine state for a future undo.
1036 void z_save_undo (void)
1039 store ((zword) save_undo ());
1044 * z_verify, check the story file integrity.
1050 void z_verify (void)
1055 /* Sum all bytes in story file except header bytes */
1057 fseek (story_fp, blorb_ofs + 64, SEEK_SET);
1059 for (i = 64; i < story_size; i++)
1060 checksum += fgetc (story_fp);
1062 /* Branch if the checksums are equal */
1064 branch (checksum == h_checksum);