1 /* Nitfol - z-machine interpreter using Glk for output.
2 Copyright (C) 1999 Evin Robertson
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
18 The author can be reached at nitfol@deja.com
22 enum z_caps { HAS_STATUSLINE, HAS_COLOR, HAS_CHARGRAPH, HAS_BOLD, HAS_ITALIC,
23 HAS_FIXED, HAS_SOUND, HAS_GRAPHICS, HAS_TIMER, HAS_MOUSE,
24 HAS_DEFVAR, IS_TRANS, FORCE_FIXED, HAS_UNDO, HAS_MENU,
30 int flagnum; /* 1 or 2 */
37 static const struct z_cap_entry z_cap_table[] = {
38 { DO_TANDY, 1, 3, 1, 3, FALSE },
39 { HAS_STATUSLINE, 1, 4, 1, 3, TRUE },
40 { HAS_STATUSLINE, 1, 5, 1, 3, FALSE },
41 { HAS_DEFVAR, 1, 6, 1, 3, FALSE },
42 { HAS_COLOR, 1, 0, 5, 99, FALSE },
43 { HAS_GRAPHICS, 1, 1, 6, 6, FALSE },
44 { HAS_BOLD, 1, 2, 4, 99, FALSE },
45 { HAS_ITALIC, 1, 3, 4, 99, FALSE },
46 { HAS_FIXED, 1, 4, 4, 99, FALSE },
47 { HAS_SOUND, 1, 5, 6, 99, FALSE },
48 { HAS_TIMER, 1, 7, 4, 99, FALSE },
49 { IS_TRANS, 2, 0, 1, 99, FALSE },
50 { FORCE_FIXED, 2, 1, 3, 99, FALSE },
51 { HAS_CHARGRAPH, 2, 3, 5, 99, FALSE },
52 { HAS_UNDO, 2, 4, 5, 99, FALSE },
53 { HAS_MOUSE, 2, 5, 5, 99, FALSE },
54 { HAS_SOUND, 2, 7, 5, 99, FALSE },
55 { HAS_MENU, 2, 8, 6, 6, FALSE }
58 static BOOL cur_cap[HAS_ENDNUM];
60 static void investigate_suckage(glui32 *wid, glui32 *hei)
64 *wid = 70; *hei = 24; /* sensible defaults if we can't tell */
66 glk_stylehint_set(wintype_AllTypes, style_User2,
67 stylehint_TextColor, 0x00ff0000);
68 glk_stylehint_set(wintype_AllTypes, style_Subheader,
71 w1 = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
73 glk_exit(); /* Run screaming! */
74 w2 = glk_window_open(w1, winmethod_Above | winmethod_Fixed, 1,
79 o = glk_window_get_parent(w2);
80 glk_window_set_arrangement(o, winmethod_Above | winmethod_Proportional,
82 glk_window_get_size(w2, wid, hei);
83 glk_window_set_arrangement(o, winmethod_Above | winmethod_Fixed,
87 cur_cap[HAS_STATUSLINE] = (w2 != 0);
88 cur_cap[HAS_COLOR] = glk_style_distinguish(w1, style_Normal, style_User2);
89 cur_cap[HAS_CHARGRAPH] = 0;
90 cur_cap[HAS_BOLD] = glk_style_distinguish(w1, style_Normal, style_Subheader);
91 cur_cap[HAS_ITALIC] = glk_style_distinguish(w1, style_Normal, style_Emphasized);
92 cur_cap[HAS_FIXED] = glk_style_distinguish(w1, style_Normal, style_Preformatted);
93 #ifdef GLK_MODULE_SOUND
94 cur_cap[HAS_SOUND] = glk_gestalt(gestalt_Sound, 0);
96 cur_cap[HAS_SOUND] = FALSE;
98 #ifdef GLK_MODULE_IMAGE
99 cur_cap[HAS_GRAPHICS] = glk_gestalt(gestalt_Graphics, 0);
101 cur_cap[HAS_GRAPHICS] = FALSE;
103 cur_cap[HAS_TIMER] = glk_gestalt(gestalt_Timer, 0);
104 cur_cap[HAS_MOUSE] = glk_gestalt(gestalt_MouseInput, wintype_TextGrid);
106 cur_cap[HAS_DEFVAR] = TRUE;
108 cur_cap[HAS_UNDO] = TRUE;
109 cur_cap[HAS_MENU] = FALSE;
112 glk_window_close(w1, NULL);
114 glk_window_close(w2, NULL);
118 void set_header(glui32 width, glui32 height)
122 flags[1] = LOBYTE(HD_FLAGS1);
123 flags[2] = LOWORD(HD_FLAGS2);
125 cur_cap[DO_TANDY] = do_tandy;
127 cur_cap[IS_TRANS] = is_transcripting();
128 cur_cap[FORCE_FIXED] = is_fixed;
130 for(i = 0; i < sizeof(z_cap_table) / sizeof(*z_cap_table); i++) {
131 if(zversion >= z_cap_table[i].min_zversion
132 && zversion <= z_cap_table[i].max_zversion) {
133 if(cur_cap[z_cap_table[i].c])
134 flags[z_cap_table[i].flagnum] |= 1 << z_cap_table[i].bit;
136 flags[z_cap_table[i].flagnum] &= ~(1 << z_cap_table[i].bit);
140 LOBYTEwrite(HD_FLAGS1, flags[1]);
141 LOWORDwrite(HD_FLAGS2, flags[2]);
143 LOBYTEwrite(HD_TERPNUM, interp_num);
144 LOBYTEwrite(HD_TERPVER, interp_ver);
146 /* We should be allowed 8 bytes here, but inform 6 (incorrectly) expects
147 this space not to be used, so only use zeroed bytes */
149 for(i = 0; i < 8 && username[i] && !LOBYTE(HD_USERID + i); i++)
150 LOBYTEwrite(HD_USERID + i, username[i]);
160 LOBYTEwrite(HD_SCR_HEIGHT, height); /* screen height (255 = infinite) */
161 LOBYTEwrite(HD_SCR_WIDTH, width); /* screen width */
163 LOWORDwrite(HD_SCR_WUNIT, width); /* screen width in units */
164 LOWORDwrite(HD_SCR_HUNIT, height); /* screen height in units */
168 LOBYTEwrite(HD_FNT_WIDTH, 1); /* font width/height in units */
169 LOBYTEwrite(HD_FNT_HEIGHT, 1); /* font height/width in units */
171 LOBYTEwrite(HD_FNT_WIDTH, 8);
172 LOBYTEwrite(HD_FNT_HEIGHT, 8);
175 LOBYTEwrite(HD_DEF_BACK, 1); /* default background color */
176 LOBYTEwrite(HD_DEF_FORE, 1); /* default foreground color */
178 /* Well, we're close enough given the limitations of Glk, so go ahead
179 and lie about the impossible stuff and claim spec 1.0 compliance */
180 LOBYTEwrite(HD_STD_REV, 1);
181 LOBYTEwrite(HD_STD_REV + 1, 0);
184 static void check_ascii_mode(void)
188 BOOL hascrnolf = FALSE;
189 BOOL haslfnocr = FALSE;
190 BOOL has8bit = FALSE;
192 for(i = 0; i < total_size; i++) {
193 if(z_memory[i] & 0x80)
195 else if(z_memory[i] == 0x0a) {
197 if(i && z_memory[i-1] != 0x0d)
200 else if(z_memory[i] == 0x0d) {
202 if(i+1 < total_size && z_memory[i+1] != 0x0a)
207 n_show_error(E_CORRUPT, "All bytes are 7 bit; top bits were likely stripped in transfer", 0);
209 n_show_error(E_CORRUPT, "No CR bytes; likely transfered in ASCII mode", 0);
211 n_show_error(E_CORRUPT, "No LF bytes; likely transfered in ASCII mode", 0);
213 n_show_error(E_CORRUPT, "All CR are followed by LFs; likely transfered in ASCII mode", 0);
215 n_show_error(E_CORRUPT, "All LFs are preceded by CRs; likely transfered in ASCII mode", 0);
219 /* Loads header into global values. Returns true if could be a valid header */
220 BOOL load_header(strid_t zfile, offset filesize, BOOL report_errors)
224 if(glk_get_buffer_stream(zfile, (char *) header, 64) != 64) {
226 n_show_error(E_SYSTEM, "file too small", 0);
230 zversion = header[HD_ZVERSION];
232 case 1: case 2: case 3:
233 granularity = 2; break;
234 case 4: case 5: case 6: case 7:
235 granularity = 4; break;
237 granularity = 8; break;
240 n_show_error(E_VERSION, "unknown version number or not zcode", zversion);
244 high_mem_mark = MSBdecodeZ(header + HD_HIMEM);
245 PC = MSBdecodeZ(header + HD_INITPC);
246 z_dictionary = MSBdecodeZ(header + HD_DICT);
247 z_propdefaults = MSBdecodeZ(header + HD_OBJTABLE);
248 z_globaltable = MSBdecodeZ(header + HD_GLOBVAR);
249 dynamic_size = MSBdecodeZ(header + HD_STATMEM);
250 z_synonymtable = MSBdecodeZ(header + HD_ABBREV);
254 game_size = filesize;
257 game_size = ((offset) MSBdecodeZ(header + HD_LENGTH)) * 2;
260 game_size = ((offset) MSBdecodeZ(header + HD_LENGTH)) * 4;
262 case 6: case 7: case 8:
263 game_size = ((offset) MSBdecodeZ(header + HD_LENGTH)) * 8;
266 if(zversion == 6 || zversion == 7) {
267 rstart = ((offset) MSBdecodeZ(header + HD_RTN_OFFSET)) * 8;
268 sstart = ((offset) MSBdecodeZ(header + HD_STR_OFFSET)) * 8;
274 /* Check consistency of stuff */
276 if(filesize < game_size) {
278 n_show_error(E_CORRUPT, "file on a diet", filesize);
282 if(dynamic_size > game_size) {
284 n_show_error(E_CORRUPT, "dynamic memory length > game size", dynamic_size);
288 if(high_mem_mark < dynamic_size) {
290 n_show_error(E_CORRUPT, "dynamic memory overlaps high memory", dynamic_size);
295 n_show_error(E_CORRUPT, "initial PC greater than game size", PC);
301 n_show_error(E_CORRUPT, "initial PC in header", PC);
309 void z_init(strid_t zfile)
313 glui32 width, height;
315 z_random(0); /* Initialize random number generator */
317 init_windows(is_fixed, 80, 24);
319 glk_stream_set_position(zfile, zfile_offset, seekmode_Start);
320 if(!load_header(zfile, total_size, TRUE))
321 n_show_fatal(E_CORRUPT, "couldn't load file", 0);
324 if(game_size <= 65535)
325 z_memory = (zbyte *) n_malloc(65535);
327 z_memory = (zbyte *) n_malloc(game_size);
329 glk_stream_set_position(zfile, zfile_offset, seekmode_Start);
330 bytes_read = glk_get_buffer_stream(zfile, (char *) z_memory, game_size);
331 if(bytes_read != game_size)
332 n_show_fatal(E_SYSTEM, "unexpected number of bytes read", bytes_read);
336 for(i = 0x40; i < game_size; i++)
337 z_checksum += HIBYTE(i);
338 z_checksum = ARITHMASK(z_checksum);
340 if(LOWORD(HD_CHECKSUM) != 0 && z_checksum != LOWORD(HD_CHECKSUM)) {
341 n_show_error(E_CORRUPT, "Checksum does not match", z_checksum);
348 init_stack(1024, 128);
351 zword dummylocals[15];
352 mop_call(PC, 0, dummylocals, 0);
356 investigate_suckage(&width, &height);
361 set_header(width, height);
362 init_windows(is_fixed, width, height);
365 opcodetable[OFFSET_0OP + 5] = op_save1;
366 opcodetable[OFFSET_0OP + 6] = op_restore1;
368 opcodetable[OFFSET_0OP + 5] = op_save4;
369 opcodetable[OFFSET_0OP + 6] = op_restore4;
372 opcodetable[OFFSET_0OP + 9] = op_pop;
373 opcodetable[OFFSET_1OP + 15] = op_not;
374 opcodetable[OFFSET_VAR + 4] = op_sread;
376 opcodetable[OFFSET_0OP + 9] = op_catch;
377 opcodetable[OFFSET_1OP + 15] = op_call_n;
378 opcodetable[OFFSET_VAR + 4] = op_aread;
381 opcodetable[OFFSET_VAR + 11] = op_set_window6;
383 opcodetable[OFFSET_VAR + 11] = op_set_window;
390 exit_decoder = FALSE;
398 output_string("Nitfol 0.5 Copyright 1999 Evin Robertson.\r");
400 output_string("Nitfol comes with ABSOLUTELY NO WARRANTY; for details type \"/show w\". This is free software, and you are welcome to change and distribute it under certain conditions; type \"/show copying\" for details.\r");
402 output_string("Nitfol is free software which comes with ABSOLUTELY NO WARRANTY. It is covered by the GNU General Public License, and you are welcome to change and distribute it under certain conditions. Read the file COPYING which should have been included in this distribution for details.\r");