1 /* fastmem.c - Memory related functions (fast version without virtual memory)
2 * Copyright (c) 1995-1997 Stefan Jokisch
4 * Changes for Rockbox copyright 2009 Torne Wuff
6 * This file is part of Frotz.
8 * Frotz is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * Frotz is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
24 * New undo mechanism added by Jim Dunleavy <jim.dunleavy@erha.ie>
31 extern void seed_random (int);
32 extern void restart_screen (void);
33 extern void refresh_text_style (void);
34 extern void call (zword
, int, zword
*, int);
35 extern void split_window (zword
);
36 extern void script_open (void);
37 extern void script_close (void);
39 extern zword
save_quetzal (int, int);
40 extern zword
restore_quetzal (int, int);
42 extern void erase_window (zword
);
44 extern void (*op0_opcodes
[]) (void);
45 extern void (*op1_opcodes
[]) (void);
46 extern void (*op2_opcodes
[]) (void);
47 extern void (*var_opcodes
[]) (void);
49 char save_name
[MAX_PATH
];
50 char auxilary_name
[MAX_PATH
];
52 zbyte far
*zmp
= NULL
;
53 zbyte far
*pcp
= NULL
;
58 * Data for the undo mechanism.
59 * This undo mechanism is based on the scheme used in Evin Robertson's
61 * Undo blocks are stored as differences between states.
64 typedef struct undo_struct undo_t
;
73 /* undo diff and stack data follow */
76 static undo_t
*first_undo
= NULL
, *last_undo
= NULL
, *curr_undo
= NULL
;
77 static zbyte
*prev_zmp
, *undo_diff
;
79 static int undo_count
= 0;
81 static void *arena_start
= NULL
, *arena_end
= NULL
, *arena_next
= NULL
;
84 * get_header_extension
86 * Read a value from the header extension (former mouse table).
90 zword
get_header_extension (int entry
)
95 if (h_extension_table
== 0 || entry
> hx_table_size
)
98 addr
= h_extension_table
+ 2 * entry
;
103 }/* get_header_extension */
106 * set_header_extension
108 * Set an entry in the header extension (former mouse table).
112 void set_header_extension (int entry
, zword val
)
116 if (h_extension_table
== 0 || entry
> hx_table_size
)
119 addr
= h_extension_table
+ 2 * entry
;
122 }/* set_header_extension */
127 * Set all header fields which hold information about the interpreter.
131 void restart_header (void)
140 SET_BYTE (H_CONFIG
, h_config
)
141 SET_WORD (H_FLAGS
, h_flags
)
143 if (h_version
>= V4
) {
144 SET_BYTE (H_INTERPRETER_NUMBER
, h_interpreter_number
)
145 SET_BYTE (H_INTERPRETER_VERSION
, h_interpreter_version
)
146 SET_BYTE (H_SCREEN_ROWS
, h_screen_rows
)
147 SET_BYTE (H_SCREEN_COLS
, h_screen_cols
)
150 /* It's less trouble to use font size 1x1 for V5 games, especially
151 because of a bug in the unreleased German version of "Zork 1" */
153 if (h_version
!= V6
) {
154 screen_x_size
= (zword
) h_screen_cols
;
155 screen_y_size
= (zword
) h_screen_rows
;
159 screen_x_size
= h_screen_width
;
160 screen_y_size
= h_screen_height
;
161 font_x_size
= h_font_width
;
162 font_y_size
= h_font_height
;
165 if (h_version
>= V5
) {
166 SET_WORD (H_SCREEN_WIDTH
, screen_x_size
)
167 SET_WORD (H_SCREEN_HEIGHT
, screen_y_size
)
168 SET_BYTE (H_FONT_HEIGHT
, font_y_size
)
169 SET_BYTE (H_FONT_WIDTH
, font_x_size
)
170 SET_BYTE (H_DEFAULT_BACKGROUND
, h_default_background
)
171 SET_BYTE (H_DEFAULT_FOREGROUND
, h_default_foreground
)
175 for (i
= 0; i
< 8; i
++)
176 storeb ((zword
) (H_USER_NAME
+ i
), h_user_name
[i
]);
178 SET_BYTE (H_STANDARD_HIGH
, h_standard_high
)
179 SET_BYTE (H_STANDARD_LOW
, h_standard_low
)
181 }/* restart_header */
186 * Allocate memory and load the story file.
190 void init_memory (void)
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, "------" }
231 /* Open story file */
233 if ((story_fp
= rb
->open(story_name
, O_RDONLY
)) < 0)
234 os_fatal ("Cannot open story file");
236 /* Allocate memory for story header */
238 zmp
= rb
->plugin_get_buffer(&buf_size
);
240 /* Load header into memory */
242 if (fread (zmp
, 1, 64, story_fp
) != 64)
243 os_fatal ("Story file read error");
245 /* Copy header fields to global variables */
247 LOW_BYTE (H_VERSION
, h_version
)
249 if (h_version
< V1
|| h_version
> V8
)
250 os_fatal ("Unknown Z-code version");
252 LOW_BYTE (H_CONFIG
, h_config
)
254 if (h_version
== V3
&& (h_config
& CONFIG_BYTE_SWAPPED
))
255 os_fatal ("Byte swapped story file");
257 LOW_WORD (H_RELEASE
, h_release
)
258 LOW_WORD (H_RESIDENT_SIZE
, h_resident_size
)
259 LOW_WORD (H_START_PC
, h_start_pc
)
260 LOW_WORD (H_DICTIONARY
, h_dictionary
)
261 LOW_WORD (H_OBJECTS
, h_objects
)
262 LOW_WORD (H_GLOBALS
, h_globals
)
263 LOW_WORD (H_DYNAMIC_SIZE
, h_dynamic_size
)
264 LOW_WORD (H_FLAGS
, h_flags
)
266 for (i
= 0, addr
= H_SERIAL
; i
< 6; i
++, addr
++)
267 LOW_BYTE (addr
, h_serial
[i
])
269 /* Auto-detect buggy story files that need special fixes */
273 for (i
= 0; records
[i
].story_id
!= UNKNOWN
; i
++) {
275 if (h_release
== records
[i
].release
) {
277 for (j
= 0; j
< 6; j
++)
278 if (h_serial
[j
] != records
[i
].serial
[j
])
281 story_id
= records
[i
].story_id
;
285 no_match
: ; /* null statement */
289 LOW_WORD (H_ABBREVIATIONS
, h_abbreviations
)
290 LOW_WORD (H_FILE_SIZE
, h_file_size
)
292 /* Calculate story file size in bytes */
294 if (h_file_size
!= 0) {
296 story_size
= (long) 2 * h_file_size
;
303 } else { /* some old games lack the file size entry */
305 fseek (story_fp
, 0, SEEK_END
);
306 story_size
= ftell (story_fp
);
307 fseek (story_fp
, 64, SEEK_SET
);
311 LOW_WORD (H_CHECKSUM
, h_checksum
)
312 LOW_WORD (H_ALPHABET
, h_alphabet
)
313 LOW_WORD (H_FUNCTIONS_OFFSET
, h_functions_offset
)
314 LOW_WORD (H_STRINGS_OFFSET
, h_strings_offset
)
315 LOW_WORD (H_TERMINATING_KEYS
, h_terminating_keys
)
316 LOW_WORD (H_EXTENSION_TABLE
, h_extension_table
)
318 /* Zork Zero Macintosh doesn't have the graphics flag set */
320 if (story_id
== ZORK_ZERO
&& h_release
== 296)
321 h_flags
|= GRAPHICS_FLAG
;
323 /* Adjust opcode tables */
325 if (h_version
<= V4
) {
326 op0_opcodes
[0x09] = z_pop
;
327 op1_opcodes
[0x0f] = z_not
;
329 op0_opcodes
[0x09] = z_catch
;
330 op1_opcodes
[0x0f] = z_call_n
;
333 /* Allocate memory for story data */
335 if ((size_t)story_size
> buf_size
)
337 audiobuf
= rb
->plugin_get_audio_buffer(&buf_size
);
338 if ((size_t)story_size
> buf_size
)
339 os_fatal ("Out of memory");
340 rb
->memcpy(audiobuf
, zmp
, 64);
344 /* Assign left over memory as the arena for undo alloc */
345 arena_start
= arena_next
= (void*)((intptr_t)(zmp
+ story_size
+ 3) & ~3);
346 arena_end
= zmp
+ buf_size
;
348 /* Load story file in chunks of 32KB */
352 for (size
= 64; size
< story_size
; size
+= n
) {
354 if (story_size
- size
< 0x8000)
355 n
= (unsigned) (story_size
- size
);
359 if (fread (pcp
, 1, n
, story_fp
) != (signed)n
)
360 os_fatal ("Story file read error");
364 /* Read header extension table */
366 hx_table_size
= get_header_extension (HX_TABLE_SIZE
);
367 hx_unicode_table
= get_header_extension (HX_UNICODE_TABLE
);
374 * Allocate memory for multiple undo. It is important not to occupy
375 * all the memory available, since the IO interface may need memory
376 * during the game, e.g. for loading sounds or pictures.
380 void init_undo (void)
382 /* Allocate h_dynamic_size bytes for previous dynamic zmp state
383 + 1.5 h_dynamic_size for Quetzal diff + 2. */
384 int size
= (h_dynamic_size
* 5) / 2 + 2;
385 if ((arena_end
- arena_start
) >= size
) {
386 prev_zmp
= arena_start
;
387 undo_diff
= arena_start
+ h_dynamic_size
;
388 arena_start
= (void*)((intptr_t)(arena_start
+ size
+ 3) & ~3);
389 arena_next
= arena_start
;
390 memcpy (prev_zmp
, zmp
, h_dynamic_size
);
392 f_setup
.undo_slots
= 0;
399 * Free count undo blocks from the beginning of the undo list.
403 static void free_undo (int count
)
407 if (count
> undo_count
)
411 if (curr_undo
== first_undo
)
412 curr_undo
= curr_undo
->next
;
413 first_undo
= first_undo
->next
;
417 first_undo
->prev
= NULL
;
425 * Close the story file and deallocate memory.
429 void reset_memory (void)
435 free_undo (undo_count
);
447 * Write a byte value to the dynamic Z-machine memory.
451 void storeb (zword addr
, zbyte value
)
454 if (addr
>= h_dynamic_size
)
455 runtime_error (ERR_STORE_RANGE
);
457 if (addr
== H_FLAGS
+ 1) { /* flags register is modified */
459 h_flags
&= ~(SCRIPTING_FLAG
| FIXED_FONT_FLAG
);
460 h_flags
|= value
& (SCRIPTING_FLAG
| FIXED_FONT_FLAG
);
462 if (value
& SCRIPTING_FLAG
) {
470 refresh_text_style ();
474 SET_BYTE (addr
, value
)
481 * Write a word value to the dynamic Z-machine memory.
485 void storew (zword addr
, zword value
)
488 storeb ((zword
) (addr
+ 0), hi (value
));
489 storeb ((zword
) (addr
+ 1), lo (value
));
494 * z_restart, re-load dynamic area, clear the stack and set the PC.
500 void z_restart (void)
502 static bool first_restart
= TRUE
;
506 os_restart_game (RESTART_BEGIN
);
510 if (!first_restart
) {
512 fseek (story_fp
, 0, SEEK_SET
);
514 if (fread (zmp
, 1, h_dynamic_size
, story_fp
) != h_dynamic_size
)
515 os_fatal ("Story file read error");
517 } else first_restart
= FALSE
;
522 sp
= fp
= stack
+ STACK_SIZE
;
525 if (h_version
!= V6
) {
527 long pc
= (long) h_start_pc
;
530 } else call (h_start_pc
, 0, NULL
, 0);
532 os_restart_game (RESTART_END
);
539 * Read a default file name from the memory of the Z-machine and
540 * copy it to a string.
544 static void get_default_name (char *default_name
, zword addr
)
555 for (i
= 0; i
< len
; i
++) {
562 if (c
>= 'A' && c
<= 'Z')
571 if (strchr (default_name
, '.') == NULL
)
572 strcpy (default_name
+ i
, ".AUX");
574 } else strcpy (default_name
, auxilary_name
);
576 }/* get_default_name */
579 * z_restore, restore [a part of] a Z-machine state from disk
581 * zargs[0] = address of area to restore (optional)
582 * zargs[1] = number of bytes to restore
583 * zargs[2] = address of suggested file name
587 void z_restore (void)
589 char new_name
[MAX_PATH
];
590 char default_name
[MAX_PATH
];
597 /* Get the file name */
599 get_default_name (default_name
, (zargc
>= 3) ? zargs
[2] : 0);
601 if (os_read_file_name (new_name
, default_name
, FILE_LOAD_AUX
) == 0)
604 strcpy (auxilary_name
, default_name
);
606 /* Open auxilary file */
608 if ((gfp
= rb
->open (new_name
, O_RDONLY
)) < 0)
611 /* Load auxilary file */
613 success
= fread (zmp
+ zargs
[0], 1, zargs
[1], gfp
);
615 /* Close auxilary file */
621 /* Get the file name */
623 if (os_read_file_name (new_name
, save_name
, FILE_RESTORE
) == 0)
626 strcpy (save_name
, new_name
);
630 if ((gfp
= rb
->open (new_name
, O_RDONLY
)) < 0)
633 success
= restore_quetzal (gfp
, story_fp
);
635 /* Close game file */
639 if ((short) success
>= 0) {
641 if ((short) success
> 0) {
642 zbyte old_screen_rows
;
643 zbyte old_screen_cols
;
645 /* In V3, reset the upper window. */
649 LOW_BYTE (H_SCREEN_ROWS
, old_screen_rows
);
650 LOW_BYTE (H_SCREEN_COLS
, old_screen_cols
);
652 /* Reload cached header fields. */
656 * Since QUETZAL files may be saved on many different machines,
657 * the screen sizes may vary a lot. Erasing the status window
658 * seems to cover up most of the resulting badness.
660 if (h_version
> V3
&& h_version
!= V6
661 && (h_screen_rows
!= old_screen_rows
662 || h_screen_cols
!= old_screen_cols
))
666 os_fatal ("Error reading save file");
681 * Set diff to a Quetzal-like difference between a and b,
682 * copying a to b as we go. It is assumed that diff points to a
683 * buffer which is large enough to hold the diff.
684 * mem_size is the number of bytes to compare.
685 * Returns the number of bytes copied to diff.
689 static long mem_diff (zbyte
*a
, zbyte
*b
, zword mem_size
, zbyte
*diff
)
691 unsigned size
= mem_size
;
697 for (j
= 0; size
> 0 && (c
= *a
++ ^ *b
++) == 0; j
++)
699 if (size
== 0) break;
713 *p
++ = (j
& 0x7f) | 0x80;
714 *p
++ = (j
& 0x7f80) >> 7;
726 * Applies a quetzal-like diff to dest
730 static void mem_undiff (zbyte
*diff
, long diff_length
, zbyte
*dest
)
734 while (diff_length
) {
741 return; /* Incomplete run */
746 return; /* Incomplete extended run */
749 runlen
= (runlen
& 0x7f) | (((unsigned) c
) << 7);
762 * This function does the dirty work for z_restore_undo.
766 int restore_undo (void)
769 if (f_setup
.undo_slots
== 0) /* undo feature unavailable */
773 if (curr_undo
== NULL
) /* no saved game state */
779 memcpy (zmp
, prev_zmp
, h_dynamic_size
);
780 SET_PC (curr_undo
->pc
)
781 sp
= stack
+ STACK_SIZE
- curr_undo
->stack_size
;
782 fp
= stack
+ curr_undo
->frame_offset
;
783 frame_count
= curr_undo
->frame_count
;
784 mem_undiff ((zbyte
*) (curr_undo
+ 1), curr_undo
->diff_size
, prev_zmp
);
785 memcpy (sp
, (zbyte
*)(curr_undo
+ 1) + curr_undo
->diff_size
,
786 curr_undo
->stack_size
* sizeof (*sp
));
788 curr_undo
= curr_undo
->prev
;
797 * z_restore_undo, restore a Z-machine state from memory.
803 void z_restore_undo (void)
806 store ((zword
) restore_undo ());
808 }/* z_restore_undo */
811 * z_save, save [a part of] the Z-machine state to disk.
813 * zargs[0] = address of memory area to save (optional)
814 * zargs[1] = number of bytes to save
815 * zargs[2] = address of suggested file name
821 char new_name
[MAX_PATH
];
822 char default_name
[MAX_PATH
];
829 /* Get the file name */
831 get_default_name (default_name
, (zargc
>= 3) ? zargs
[2] : 0);
833 if (os_read_file_name (new_name
, default_name
, FILE_SAVE_AUX
) == 0)
836 strcpy (auxilary_name
, default_name
);
838 /* Open auxilary file */
840 if ((gfp
= rb
->open (new_name
, O_WRONLY
|O_CREAT
|O_TRUNC
, 0666)) < 0)
843 /* Write auxilary file */
845 success
= fwrite (zmp
+ zargs
[0], zargs
[1], 1, gfp
);
847 /* Close auxilary file */
853 /* Get the file name */
855 if (os_read_file_name (new_name
, save_name
, FILE_SAVE
) == 0)
858 strcpy (save_name
, new_name
);
862 if ((gfp
= rb
->open (new_name
, O_WRONLY
|O_CREAT
|O_TRUNC
, 0666)) < 0)
865 success
= save_quetzal (gfp
, story_fp
);
867 /* Close game file and check for errors */
869 if (fclose (gfp
) != 0 || ferror (story_fp
)) {
870 print_string ("Error writing save file\n");
892 * This function does the dirty work for z_save_undo.
903 if (f_setup
.undo_slots
== 0) /* undo feature unavailable */
906 /* save undo possible */
908 while (last_undo
!= curr_undo
) {
910 last_undo
= last_undo
->prev
;
915 last_undo
->next
= NULL
;
919 if (undo_count
== f_setup
.undo_slots
)
922 diff_size
= mem_diff (zmp
, prev_zmp
, h_dynamic_size
, undo_diff
);
923 stack_size
= stack
+ STACK_SIZE
- sp
;
925 size
= sizeof (undo_t
) + diff_size
+ stack_size
* sizeof (*sp
);
926 if (arena_next
> (void*)first_undo
) {
927 /* Free space is all at the end */
928 if ((arena_end
- arena_next
) >= size
) {
929 /* Trivial: enough room at the end */
931 arena_next
= (void*)((intptr_t)(arena_next
+ size
+ 3) & ~3);
934 arena_next
= arena_start
;
938 /* Free space is somewhere else */
939 if (((void*)first_undo
- arena_next
) >= size
) {
940 /* There is room before the "first" undo */
942 arena_next
= (void*)((intptr_t)(arena_next
+ size
+ 3) & ~3);
944 /* Not enough room, just need to free some */
951 } while (!p
&& undo_count
);
955 p
->frame_count
= frame_count
;
956 p
->diff_size
= diff_size
;
957 p
->stack_size
= stack_size
;
958 p
->frame_offset
= fp
- stack
;
959 memcpy (p
+ 1, undo_diff
, diff_size
);
960 memcpy ((zbyte
*)(p
+ 1) + diff_size
, sp
, stack_size
* sizeof (*sp
));
970 curr_undo
= last_undo
= p
;
977 * z_save_undo, save the current Z-machine state for a future undo.
983 void z_save_undo (void)
986 store ((zword
) save_undo ());
991 * z_verify, check the story file integrity.
1002 /* Sum all bytes in story file except header bytes */
1004 fseek (story_fp
, 64, SEEK_SET
);
1006 for (i
= 64; i
< story_size
; i
++)
1007 checksum
+= fgetc (story_fp
);
1009 /* Branch if the checksums are equal */
1011 branch (checksum
== h_checksum
);