diff options
author | Torne Wuff <torne@wolfpuppy.org.uk> | 2010-01-17 22:15:13 +0000 |
---|---|---|
committer | Torne Wuff <torne@wolfpuppy.org.uk> | 2010-01-17 22:15:13 +0000 |
commit | 7f28c94eda576e3f972fc05468188986f2e45885 (patch) | |
tree | e03b94613028d16855a5d3df0f4853e077931214 /apps/plugins/frotz/fastmem.c | |
parent | 563f2602f471208cb8544a36539a79dcceaad643 (diff) | |
download | rockbox-7f28c94eda576e3f972fc05468188986f2e45885.tar.gz rockbox-7f28c94eda576e3f972fc05468188986f2e45885.zip |
New plugin: frotz, a Z-machine interpreter, for playing interactive fiction.
The interpreter more or less passes all the tests in the z-machine test suite.
It should build for every target except Archos (for which it is disabled).
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@24267 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/plugins/frotz/fastmem.c')
-rw-r--r-- | apps/plugins/frotz/fastmem.c | 1013 |
1 files changed, 1013 insertions, 0 deletions
diff --git a/apps/plugins/frotz/fastmem.c b/apps/plugins/frotz/fastmem.c new file mode 100644 index 0000000000..cdb4bce602 --- /dev/null +++ b/apps/plugins/frotz/fastmem.c | |||
@@ -0,0 +1,1013 @@ | |||
1 | /* fastmem.c - Memory related functions (fast version without virtual memory) | ||
2 | * Copyright (c) 1995-1997 Stefan Jokisch | ||
3 | * | ||
4 | * Changes for Rockbox copyright 2009 Torne Wuff | ||
5 | * | ||
6 | * This file is part of Frotz. | ||
7 | * | ||
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. | ||
12 | * | ||
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. | ||
17 | * | ||
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 | ||
21 | */ | ||
22 | |||
23 | /* | ||
24 | * New undo mechanism added by Jim Dunleavy <jim.dunleavy@erha.ie> | ||
25 | */ | ||
26 | |||
27 | #include "frotz.h" | ||
28 | |||
29 | #define far | ||
30 | |||
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); | ||
38 | |||
39 | extern zword save_quetzal (int, int); | ||
40 | extern zword restore_quetzal (int, int); | ||
41 | |||
42 | extern void erase_window (zword); | ||
43 | |||
44 | extern void (*op0_opcodes[]) (void); | ||
45 | extern void (*op1_opcodes[]) (void); | ||
46 | extern void (*op2_opcodes[]) (void); | ||
47 | extern void (*var_opcodes[]) (void); | ||
48 | |||
49 | char save_name[MAX_PATH]; | ||
50 | char auxilary_name[MAX_PATH]; | ||
51 | |||
52 | zbyte far *zmp = NULL; | ||
53 | zbyte far *pcp = NULL; | ||
54 | |||
55 | int story_fp = -1; | ||
56 | |||
57 | /* | ||
58 | * Data for the undo mechanism. | ||
59 | * This undo mechanism is based on the scheme used in Evin Robertson's | ||
60 | * Nitfol interpreter. | ||
61 | * Undo blocks are stored as differences between states. | ||
62 | */ | ||
63 | |||
64 | typedef struct undo_struct undo_t; | ||
65 | struct undo_struct { | ||
66 | undo_t *next; | ||
67 | undo_t *prev; | ||
68 | long pc; | ||
69 | long diff_size; | ||
70 | zword frame_count; | ||
71 | zword stack_size; | ||
72 | zword frame_offset; | ||
73 | /* undo diff and stack data follow */ | ||
74 | }; | ||
75 | |||
76 | static undo_t *first_undo = NULL, *last_undo = NULL, *curr_undo = NULL; | ||
77 | static zbyte *prev_zmp, *undo_diff; | ||
78 | |||
79 | static int undo_count = 0; | ||
80 | |||
81 | static void *arena_start = NULL, *arena_end = NULL, *arena_next = NULL; | ||
82 | |||
83 | /* | ||
84 | * get_header_extension | ||
85 | * | ||
86 | * Read a value from the header extension (former mouse table). | ||
87 | * | ||
88 | */ | ||
89 | |||
90 | zword get_header_extension (int entry) | ||
91 | { | ||
92 | zword addr; | ||
93 | zword val; | ||
94 | |||
95 | if (h_extension_table == 0 || entry > hx_table_size) | ||
96 | return 0; | ||
97 | |||
98 | addr = h_extension_table + 2 * entry; | ||
99 | LOW_WORD (addr, val) | ||
100 | |||
101 | return val; | ||
102 | |||
103 | }/* get_header_extension */ | ||
104 | |||
105 | /* | ||
106 | * set_header_extension | ||
107 | * | ||
108 | * Set an entry in the header extension (former mouse table). | ||
109 | * | ||
110 | */ | ||
111 | |||
112 | void set_header_extension (int entry, zword val) | ||
113 | { | ||
114 | zword addr; | ||
115 | |||
116 | if (h_extension_table == 0 || entry > hx_table_size) | ||
117 | return; | ||
118 | |||
119 | addr = h_extension_table + 2 * entry; | ||
120 | SET_WORD (addr, val) | ||
121 | |||
122 | }/* set_header_extension */ | ||
123 | |||
124 | /* | ||
125 | * restart_header | ||
126 | * | ||
127 | * Set all header fields which hold information about the interpreter. | ||
128 | * | ||
129 | */ | ||
130 | |||
131 | void restart_header (void) | ||
132 | { | ||
133 | zword screen_x_size; | ||
134 | zword screen_y_size; | ||
135 | zbyte font_x_size; | ||
136 | zbyte font_y_size; | ||
137 | |||
138 | int i; | ||
139 | |||
140 | SET_BYTE (H_CONFIG, h_config) | ||
141 | SET_WORD (H_FLAGS, h_flags) | ||
142 | |||
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) | ||
148 | } | ||
149 | |||
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" */ | ||
152 | |||
153 | if (h_version != V6) { | ||
154 | screen_x_size = (zword) h_screen_cols; | ||
155 | screen_y_size = (zword) h_screen_rows; | ||
156 | font_x_size = 1; | ||
157 | font_y_size = 1; | ||
158 | } else { | ||
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; | ||
163 | } | ||
164 | |||
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) | ||
172 | } | ||
173 | |||
174 | if (h_version == V6) | ||
175 | for (i = 0; i < 8; i++) | ||
176 | storeb ((zword) (H_USER_NAME + i), h_user_name[i]); | ||
177 | |||
178 | SET_BYTE (H_STANDARD_HIGH, h_standard_high) | ||
179 | SET_BYTE (H_STANDARD_LOW, h_standard_low) | ||
180 | |||
181 | }/* restart_header */ | ||
182 | |||
183 | /* | ||
184 | * init_memory | ||
185 | * | ||
186 | * Allocate memory and load the story file. | ||
187 | * | ||
188 | */ | ||
189 | |||
190 | void init_memory (void) | ||
191 | { | ||
192 | long size; | ||
193 | zword addr; | ||
194 | unsigned n; | ||
195 | int i, j; | ||
196 | zbyte *audiobuf; | ||
197 | size_t buf_size; | ||
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 | if ((story_fp = rb->open(story_name, O_RDONLY)) < 0) | ||
234 | os_fatal ("Cannot open story file"); | ||
235 | |||
236 | /* Allocate memory for story header */ | ||
237 | |||
238 | zmp = rb->plugin_get_buffer(&buf_size); | ||
239 | |||
240 | /* Load header into memory */ | ||
241 | |||
242 | if (fread (zmp, 1, 64, story_fp) != 64) | ||
243 | os_fatal ("Story file read error"); | ||
244 | |||
245 | /* Copy header fields to global variables */ | ||
246 | |||
247 | LOW_BYTE (H_VERSION, h_version) | ||
248 | |||
249 | if (h_version < V1 || h_version > V8) | ||
250 | os_fatal ("Unknown Z-code version"); | ||
251 | |||
252 | LOW_BYTE (H_CONFIG, h_config) | ||
253 | |||
254 | if (h_version == V3 && (h_config & CONFIG_BYTE_SWAPPED)) | ||
255 | os_fatal ("Byte swapped story file"); | ||
256 | |||
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) | ||
265 | |||
266 | for (i = 0, addr = H_SERIAL; i < 6; i++, addr++) | ||
267 | LOW_BYTE (addr, h_serial[i]) | ||
268 | |||
269 | /* Auto-detect buggy story files that need special fixes */ | ||
270 | |||
271 | story_id = UNKNOWN; | ||
272 | |||
273 | for (i = 0; records[i].story_id != UNKNOWN; i++) { | ||
274 | |||
275 | if (h_release == records[i].release) { | ||
276 | |||
277 | for (j = 0; j < 6; j++) | ||
278 | if (h_serial[j] != records[i].serial[j]) | ||
279 | goto no_match; | ||
280 | |||
281 | story_id = records[i].story_id; | ||
282 | |||
283 | } | ||
284 | |||
285 | no_match: ; /* null statement */ | ||
286 | |||
287 | } | ||
288 | |||
289 | LOW_WORD (H_ABBREVIATIONS, h_abbreviations) | ||
290 | LOW_WORD (H_FILE_SIZE, h_file_size) | ||
291 | |||
292 | /* Calculate story file size in bytes */ | ||
293 | |||
294 | if (h_file_size != 0) { | ||
295 | |||
296 | story_size = (long) 2 * h_file_size; | ||
297 | |||
298 | if (h_version >= V4) | ||
299 | story_size *= 2; | ||
300 | if (h_version >= V6) | ||
301 | story_size *= 2; | ||
302 | |||
303 | } else { /* some old games lack the file size entry */ | ||
304 | |||
305 | fseek (story_fp, 0, SEEK_END); | ||
306 | story_size = ftell (story_fp); | ||
307 | fseek (story_fp, 64, SEEK_SET); | ||
308 | |||
309 | } | ||
310 | |||
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) | ||
317 | |||
318 | /* Zork Zero Macintosh doesn't have the graphics flag set */ | ||
319 | |||
320 | if (story_id == ZORK_ZERO && h_release == 296) | ||
321 | h_flags |= GRAPHICS_FLAG; | ||
322 | |||
323 | /* Adjust opcode tables */ | ||
324 | |||
325 | if (h_version <= V4) { | ||
326 | op0_opcodes[0x09] = z_pop; | ||
327 | op1_opcodes[0x0f] = z_not; | ||
328 | } else { | ||
329 | op0_opcodes[0x09] = z_catch; | ||
330 | op1_opcodes[0x0f] = z_call_n; | ||
331 | } | ||
332 | |||
333 | /* Allocate memory for story data */ | ||
334 | |||
335 | if ((size_t)story_size > buf_size) | ||
336 | { | ||
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); | ||
341 | zmp = audiobuf; | ||
342 | } | ||
343 | |||
344 | /* Assign left over memory as the arena for undo alloc */ | ||
345 | arena_start = arena_next = (void*)((int)(zmp + story_size + 3) & ~3); | ||
346 | arena_end = zmp + buf_size; | ||
347 | |||
348 | /* Load story file in chunks of 32KB */ | ||
349 | |||
350 | n = 0x8000; | ||
351 | |||
352 | for (size = 64; size < story_size; size += n) { | ||
353 | |||
354 | if (story_size - size < 0x8000) | ||
355 | n = (unsigned) (story_size - size); | ||
356 | |||
357 | SET_PC (size) | ||
358 | |||
359 | if (fread (pcp, 1, n, story_fp) != (signed)n) | ||
360 | os_fatal ("Story file read error"); | ||
361 | |||
362 | } | ||
363 | |||
364 | /* Read header extension table */ | ||
365 | |||
366 | hx_table_size = get_header_extension (HX_TABLE_SIZE); | ||
367 | hx_unicode_table = get_header_extension (HX_UNICODE_TABLE); | ||
368 | |||
369 | }/* init_memory */ | ||
370 | |||
371 | /* | ||
372 | * init_undo | ||
373 | * | ||
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. | ||
377 | * | ||
378 | */ | ||
379 | |||
380 | void init_undo (void) | ||
381 | { | ||
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*)((int)(arena_start + size + 3) & ~3); | ||
389 | arena_next = arena_start; | ||
390 | memcpy (prev_zmp, zmp, h_dynamic_size); | ||
391 | } else | ||
392 | f_setup.undo_slots = 0; | ||
393 | |||
394 | }/* init_undo */ | ||
395 | |||
396 | /* | ||
397 | * free_undo | ||
398 | * | ||
399 | * Free count undo blocks from the beginning of the undo list. | ||
400 | * | ||
401 | */ | ||
402 | |||
403 | static void free_undo (int count) | ||
404 | { | ||
405 | undo_t *p; | ||
406 | |||
407 | if (count > undo_count) | ||
408 | count = undo_count; | ||
409 | while (count--) { | ||
410 | p = first_undo; | ||
411 | if (curr_undo == first_undo) | ||
412 | curr_undo = curr_undo->next; | ||
413 | first_undo = first_undo->next; | ||
414 | undo_count--; | ||
415 | } | ||
416 | if (first_undo) | ||
417 | first_undo->prev = NULL; | ||
418 | else | ||
419 | last_undo = NULL; | ||
420 | }/* free_undo */ | ||
421 | |||
422 | /* | ||
423 | * reset_memory | ||
424 | * | ||
425 | * Close the story file and deallocate memory. | ||
426 | * | ||
427 | */ | ||
428 | |||
429 | void reset_memory (void) | ||
430 | { | ||
431 | if (story_fp != -1) | ||
432 | fclose (story_fp); | ||
433 | story_fp = -1; | ||
434 | |||
435 | free_undo (undo_count); | ||
436 | undo_count = 0; | ||
437 | |||
438 | arena_start = NULL; | ||
439 | arena_end = NULL; | ||
440 | arena_next = NULL; | ||
441 | zmp = NULL; | ||
442 | }/* reset_memory */ | ||
443 | |||
444 | /* | ||
445 | * storeb | ||
446 | * | ||
447 | * Write a byte value to the dynamic Z-machine memory. | ||
448 | * | ||
449 | */ | ||
450 | |||
451 | void storeb (zword addr, zbyte value) | ||
452 | { | ||
453 | |||
454 | if (addr >= h_dynamic_size) | ||
455 | runtime_error (ERR_STORE_RANGE); | ||
456 | |||
457 | if (addr == H_FLAGS + 1) { /* flags register is modified */ | ||
458 | |||
459 | h_flags &= ~(SCRIPTING_FLAG | FIXED_FONT_FLAG); | ||
460 | h_flags |= value & (SCRIPTING_FLAG | FIXED_FONT_FLAG); | ||
461 | |||
462 | if (value & SCRIPTING_FLAG) { | ||
463 | if (!ostream_script) | ||
464 | script_open (); | ||
465 | } else { | ||
466 | if (ostream_script) | ||
467 | script_close (); | ||
468 | } | ||
469 | |||
470 | refresh_text_style (); | ||
471 | |||
472 | } | ||
473 | |||
474 | SET_BYTE (addr, value) | ||
475 | |||
476 | }/* storeb */ | ||
477 | |||
478 | /* | ||
479 | * storew | ||
480 | * | ||
481 | * Write a word value to the dynamic Z-machine memory. | ||
482 | * | ||
483 | */ | ||
484 | |||
485 | void storew (zword addr, zword value) | ||
486 | { | ||
487 | |||
488 | storeb ((zword) (addr + 0), hi (value)); | ||
489 | storeb ((zword) (addr + 1), lo (value)); | ||
490 | |||
491 | }/* storew */ | ||
492 | |||
493 | /* | ||
494 | * z_restart, re-load dynamic area, clear the stack and set the PC. | ||
495 | * | ||
496 | * no zargs used | ||
497 | * | ||
498 | */ | ||
499 | |||
500 | void z_restart (void) | ||
501 | { | ||
502 | static bool first_restart = TRUE; | ||
503 | |||
504 | flush_buffer (); | ||
505 | |||
506 | os_restart_game (RESTART_BEGIN); | ||
507 | |||
508 | seed_random (0); | ||
509 | |||
510 | if (!first_restart) { | ||
511 | |||
512 | fseek (story_fp, 0, SEEK_SET); | ||
513 | |||
514 | if (fread (zmp, 1, h_dynamic_size, story_fp) != h_dynamic_size) | ||
515 | os_fatal ("Story file read error"); | ||
516 | |||
517 | } else first_restart = FALSE; | ||
518 | |||
519 | restart_header (); | ||
520 | restart_screen (); | ||
521 | |||
522 | sp = fp = stack + STACK_SIZE; | ||
523 | frame_count = 0; | ||
524 | |||
525 | if (h_version != V6) { | ||
526 | |||
527 | long pc = (long) h_start_pc; | ||
528 | SET_PC (pc) | ||
529 | |||
530 | } else call (h_start_pc, 0, NULL, 0); | ||
531 | |||
532 | os_restart_game (RESTART_END); | ||
533 | |||
534 | }/* z_restart */ | ||
535 | |||
536 | /* | ||
537 | * get_default_name | ||
538 | * | ||
539 | * Read a default file name from the memory of the Z-machine and | ||
540 | * copy it to a string. | ||
541 | * | ||
542 | */ | ||
543 | |||
544 | static void get_default_name (char *default_name, zword addr) | ||
545 | { | ||
546 | |||
547 | if (addr != 0) { | ||
548 | |||
549 | zbyte len; | ||
550 | int i; | ||
551 | |||
552 | LOW_BYTE (addr, len) | ||
553 | addr++; | ||
554 | |||
555 | for (i = 0; i < len; i++) { | ||
556 | |||
557 | zbyte c; | ||
558 | |||
559 | LOW_BYTE (addr, c) | ||
560 | addr++; | ||
561 | |||
562 | if (c >= 'A' && c <= 'Z') | ||
563 | c += 'a' - 'A'; | ||
564 | |||
565 | default_name[i] = c; | ||
566 | |||
567 | } | ||
568 | |||
569 | default_name[i] = 0; | ||
570 | |||
571 | if (strchr (default_name, '.') == NULL) | ||
572 | strcpy (default_name + i, ".AUX"); | ||
573 | |||
574 | } else strcpy (default_name, auxilary_name); | ||
575 | |||
576 | }/* get_default_name */ | ||
577 | |||
578 | /* | ||
579 | * z_restore, restore [a part of] a Z-machine state from disk | ||
580 | * | ||
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 | ||
584 | * | ||
585 | */ | ||
586 | |||
587 | void z_restore (void) | ||
588 | { | ||
589 | char new_name[MAX_PATH]; | ||
590 | char default_name[MAX_PATH]; | ||
591 | int gfp; | ||
592 | |||
593 | zword success = 0; | ||
594 | |||
595 | if (zargc != 0) { | ||
596 | |||
597 | /* Get the file name */ | ||
598 | |||
599 | get_default_name (default_name, (zargc >= 3) ? zargs[2] : 0); | ||
600 | |||
601 | if (os_read_file_name (new_name, default_name, FILE_LOAD_AUX) == 0) | ||
602 | goto finished; | ||
603 | |||
604 | strcpy (auxilary_name, default_name); | ||
605 | |||
606 | /* Open auxilary file */ | ||
607 | |||
608 | if ((gfp = rb->open (new_name, O_RDONLY)) < 0) | ||
609 | goto finished; | ||
610 | |||
611 | /* Load auxilary file */ | ||
612 | |||
613 | success = fread (zmp + zargs[0], 1, zargs[1], gfp); | ||
614 | |||
615 | /* Close auxilary file */ | ||
616 | |||
617 | fclose (gfp); | ||
618 | |||
619 | } else { | ||
620 | |||
621 | /* Get the file name */ | ||
622 | |||
623 | if (os_read_file_name (new_name, save_name, FILE_RESTORE) == 0) | ||
624 | goto finished; | ||
625 | |||
626 | strcpy (save_name, new_name); | ||
627 | |||
628 | /* Open game file */ | ||
629 | |||
630 | if ((gfp = rb->open (new_name, O_RDONLY)) < 0) | ||
631 | goto finished; | ||
632 | |||
633 | success = restore_quetzal (gfp, story_fp); | ||
634 | |||
635 | /* Close game file */ | ||
636 | |||
637 | fclose (gfp); | ||
638 | |||
639 | if ((short) success >= 0) { | ||
640 | |||
641 | if ((short) success > 0) { | ||
642 | zbyte old_screen_rows; | ||
643 | zbyte old_screen_cols; | ||
644 | |||
645 | /* In V3, reset the upper window. */ | ||
646 | if (h_version == V3) | ||
647 | split_window (0); | ||
648 | |||
649 | LOW_BYTE (H_SCREEN_ROWS, old_screen_rows); | ||
650 | LOW_BYTE (H_SCREEN_COLS, old_screen_cols); | ||
651 | |||
652 | /* Reload cached header fields. */ | ||
653 | restart_header (); | ||
654 | |||
655 | /* | ||
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. | ||
659 | */ | ||
660 | if (h_version > V3 && h_version != V6 | ||
661 | && (h_screen_rows != old_screen_rows | ||
662 | || h_screen_cols != old_screen_cols)) | ||
663 | erase_window (1); | ||
664 | } | ||
665 | } else | ||
666 | os_fatal ("Error reading save file"); | ||
667 | } | ||
668 | |||
669 | finished: | ||
670 | |||
671 | if (h_version <= V3) | ||
672 | branch (success); | ||
673 | else | ||
674 | store (success); | ||
675 | |||
676 | }/* z_restore */ | ||
677 | |||
678 | /* | ||
679 | * mem_diff | ||
680 | * | ||
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. | ||
686 | * | ||
687 | */ | ||
688 | |||
689 | static long mem_diff (zbyte *a, zbyte *b, zword mem_size, zbyte *diff) | ||
690 | { | ||
691 | unsigned size = mem_size; | ||
692 | zbyte *p = diff; | ||
693 | unsigned j; | ||
694 | zbyte c; | ||
695 | |||
696 | for (;;) { | ||
697 | for (j = 0; size > 0 && (c = *a++ ^ *b++) == 0; j++) | ||
698 | size--; | ||
699 | if (size == 0) break; | ||
700 | size--; | ||
701 | if (j > 0x8000) { | ||
702 | *p++ = 0; | ||
703 | *p++ = 0xff; | ||
704 | *p++ = 0xff; | ||
705 | j -= 0x8000; | ||
706 | } | ||
707 | if (j > 0) { | ||
708 | *p++ = 0; | ||
709 | j--; | ||
710 | if (j <= 0x7f) { | ||
711 | *p++ = j; | ||
712 | } else { | ||
713 | *p++ = (j & 0x7f) | 0x80; | ||
714 | *p++ = (j & 0x7f80) >> 7; | ||
715 | } | ||
716 | } | ||
717 | *p++ = c; | ||
718 | *(b - 1) ^= c; | ||
719 | } | ||
720 | return p - diff; | ||
721 | }/* mem_diff */ | ||
722 | |||
723 | /* | ||
724 | * mem_undiff | ||
725 | * | ||
726 | * Applies a quetzal-like diff to dest | ||
727 | * | ||
728 | */ | ||
729 | |||
730 | static void mem_undiff (zbyte *diff, long diff_length, zbyte *dest) | ||
731 | { | ||
732 | zbyte c; | ||
733 | |||
734 | while (diff_length) { | ||
735 | c = *diff++; | ||
736 | diff_length--; | ||
737 | if (c == 0) { | ||
738 | unsigned runlen; | ||
739 | |||
740 | if (!diff_length) | ||
741 | return; /* Incomplete run */ | ||
742 | runlen = *diff++; | ||
743 | diff_length--; | ||
744 | if (runlen & 0x80) { | ||
745 | if (!diff_length) | ||
746 | return; /* Incomplete extended run */ | ||
747 | c = *diff++; | ||
748 | diff_length--; | ||
749 | runlen = (runlen & 0x7f) | (((unsigned) c) << 7); | ||
750 | } | ||
751 | |||
752 | dest += runlen + 1; | ||
753 | } else { | ||
754 | *dest++ ^= c; | ||
755 | } | ||
756 | } | ||
757 | }/* mem_undiff */ | ||
758 | |||
759 | /* | ||
760 | * restore_undo | ||
761 | * | ||
762 | * This function does the dirty work for z_restore_undo. | ||
763 | * | ||
764 | */ | ||
765 | |||
766 | int restore_undo (void) | ||
767 | { | ||
768 | |||
769 | if (f_setup.undo_slots == 0) /* undo feature unavailable */ | ||
770 | |||
771 | return -1; | ||
772 | |||
773 | if (curr_undo == NULL) /* no saved game state */ | ||
774 | |||
775 | return 0; | ||
776 | |||
777 | /* undo possible */ | ||
778 | |||
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)); | ||
787 | |||
788 | curr_undo = curr_undo->prev; | ||
789 | |||
790 | restart_header (); | ||
791 | |||
792 | return 2; | ||
793 | |||
794 | }/* restore_undo */ | ||
795 | |||
796 | /* | ||
797 | * z_restore_undo, restore a Z-machine state from memory. | ||
798 | * | ||
799 | * no zargs used | ||
800 | * | ||
801 | */ | ||
802 | |||
803 | void z_restore_undo (void) | ||
804 | { | ||
805 | |||
806 | store ((zword) restore_undo ()); | ||
807 | |||
808 | }/* z_restore_undo */ | ||
809 | |||
810 | /* | ||
811 | * z_save, save [a part of] the Z-machine state to disk. | ||
812 | * | ||
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 | ||
816 | * | ||
817 | */ | ||
818 | |||
819 | void z_save (void) | ||
820 | { | ||
821 | char new_name[MAX_PATH]; | ||
822 | char default_name[MAX_PATH]; | ||
823 | int gfp; | ||
824 | |||
825 | zword success = 0; | ||
826 | |||
827 | if (zargc != 0) { | ||
828 | |||
829 | /* Get the file name */ | ||
830 | |||
831 | get_default_name (default_name, (zargc >= 3) ? zargs[2] : 0); | ||
832 | |||
833 | if (os_read_file_name (new_name, default_name, FILE_SAVE_AUX) == 0) | ||
834 | goto finished; | ||
835 | |||
836 | strcpy (auxilary_name, default_name); | ||
837 | |||
838 | /* Open auxilary file */ | ||
839 | |||
840 | if ((gfp = rb->open (new_name, O_WRONLY|O_CREAT|O_TRUNC)) < 0) | ||
841 | goto finished; | ||
842 | |||
843 | /* Write auxilary file */ | ||
844 | |||
845 | success = fwrite (zmp + zargs[0], zargs[1], 1, gfp); | ||
846 | |||
847 | /* Close auxilary file */ | ||
848 | |||
849 | fclose (gfp); | ||
850 | |||
851 | } else { | ||
852 | |||
853 | /* Get the file name */ | ||
854 | |||
855 | if (os_read_file_name (new_name, save_name, FILE_SAVE) == 0) | ||
856 | goto finished; | ||
857 | |||
858 | strcpy (save_name, new_name); | ||
859 | |||
860 | /* Open game file */ | ||
861 | |||
862 | if ((gfp = rb->open (new_name, O_WRONLY|O_CREAT|O_TRUNC)) < 0) | ||
863 | goto finished; | ||
864 | |||
865 | success = save_quetzal (gfp, story_fp); | ||
866 | |||
867 | /* Close game file and check for errors */ | ||
868 | |||
869 | if (fclose (gfp) != 0 || ferror (story_fp)) { | ||
870 | print_string ("Error writing save file\n"); | ||
871 | goto finished; | ||
872 | } | ||
873 | |||
874 | /* Success */ | ||
875 | |||
876 | success = 1; | ||
877 | |||
878 | } | ||
879 | |||
880 | finished: | ||
881 | |||
882 | if (h_version <= V3) | ||
883 | branch (success); | ||
884 | else | ||
885 | store (success); | ||
886 | |||
887 | }/* z_save */ | ||
888 | |||
889 | /* | ||
890 | * save_undo | ||
891 | * | ||
892 | * This function does the dirty work for z_save_undo. | ||
893 | * | ||
894 | */ | ||
895 | |||
896 | int save_undo (void) | ||
897 | { | ||
898 | long diff_size; | ||
899 | zword stack_size; | ||
900 | int size; | ||
901 | undo_t *p; | ||
902 | |||
903 | if (f_setup.undo_slots == 0) /* undo feature unavailable */ | ||
904 | return -1; | ||
905 | |||
906 | /* save undo possible */ | ||
907 | |||
908 | while (last_undo != curr_undo) { | ||
909 | p = last_undo; | ||
910 | last_undo = last_undo->prev; | ||
911 | arena_next = p; | ||
912 | undo_count--; | ||
913 | } | ||
914 | if (last_undo) | ||
915 | last_undo->next = NULL; | ||
916 | else | ||
917 | first_undo = NULL; | ||
918 | |||
919 | if (undo_count == f_setup.undo_slots) | ||
920 | free_undo (1); | ||
921 | |||
922 | diff_size = mem_diff (zmp, prev_zmp, h_dynamic_size, undo_diff); | ||
923 | stack_size = stack + STACK_SIZE - sp; | ||
924 | do { | ||
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 */ | ||
930 | p = arena_next; | ||
931 | arena_next = (void*)((int)(arena_next + size + 3) & ~3); | ||
932 | } else { | ||
933 | /* Need to wrap */ | ||
934 | arena_next = arena_start; | ||
935 | p = NULL; | ||
936 | } | ||
937 | } else { | ||
938 | /* Free space is somewhere else */ | ||
939 | if (((void*)first_undo - arena_next) >= size) { | ||
940 | /* There is room before the "first" undo */ | ||
941 | p = arena_next; | ||
942 | arena_next = (void*)((int)(arena_next + size + 3) & ~3); | ||
943 | } else { | ||
944 | /* Not enough room, just need to free some */ | ||
945 | p = NULL; | ||
946 | } | ||
947 | } | ||
948 | |||
949 | if (p == NULL) | ||
950 | free_undo (1); | ||
951 | } while (!p && undo_count); | ||
952 | if (p == NULL) | ||
953 | return -1; | ||
954 | GET_PC (p->pc) | ||
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)); | ||
961 | |||
962 | if (!first_undo) { | ||
963 | p->prev = NULL; | ||
964 | first_undo = p; | ||
965 | } else { | ||
966 | last_undo->next = p; | ||
967 | p->prev = last_undo; | ||
968 | } | ||
969 | p->next = NULL; | ||
970 | curr_undo = last_undo = p; | ||
971 | undo_count++; | ||
972 | return 1; | ||
973 | |||
974 | }/* save_undo */ | ||
975 | |||
976 | /* | ||
977 | * z_save_undo, save the current Z-machine state for a future undo. | ||
978 | * | ||
979 | * no zargs used | ||
980 | * | ||
981 | */ | ||
982 | |||
983 | void z_save_undo (void) | ||
984 | { | ||
985 | |||
986 | store ((zword) save_undo ()); | ||
987 | |||
988 | }/* z_save_undo */ | ||
989 | |||
990 | /* | ||
991 | * z_verify, check the story file integrity. | ||
992 | * | ||
993 | * no zargs used | ||
994 | * | ||
995 | */ | ||
996 | |||
997 | void z_verify (void) | ||
998 | { | ||
999 | zword checksum = 0; | ||
1000 | long i; | ||
1001 | |||
1002 | /* Sum all bytes in story file except header bytes */ | ||
1003 | |||
1004 | fseek (story_fp, 64, SEEK_SET); | ||
1005 | |||
1006 | for (i = 64; i < story_size; i++) | ||
1007 | checksum += fgetc (story_fp); | ||
1008 | |||
1009 | /* Branch if the checksums are equal */ | ||
1010 | |||
1011 | branch (checksum == h_checksum); | ||
1012 | |||
1013 | }/* z_verify */ | ||