Bug fixes
[rodin/chimara.git] / interpreters / nitfol / init.c
1 /*  Nitfol - z-machine interpreter using Glk for output.
2     Copyright (C) 1999  Evin Robertson
3
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.
8
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.
13
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.
17
18     The author can be reached at nitfol@deja.com
19 */
20 #include "nitfol.h"
21
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,
25               DO_TANDY,
26               HAS_ENDNUM };
27
28 struct z_cap_entry {
29   enum z_caps c;
30   int flagnum;    /* 1 or 2 */
31   int bit;
32   int min_zversion;
33   int max_zversion;
34   BOOL reverse;
35 };
36
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 }
56 };
57
58 static BOOL cur_cap[HAS_ENDNUM];
59
60 static void investigate_suckage(glui32 *wid, glui32 *hei)
61 {
62   winid_t w1, w2;
63
64   *wid = 70; *hei = 24; /* sensible defaults if we can't tell */
65
66   glk_stylehint_set(wintype_AllTypes, style_User2,
67                     stylehint_TextColor, 0x00ff0000);
68   glk_stylehint_set(wintype_AllTypes, style_Subheader,
69                     stylehint_Weight, 1);
70
71   w1 = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
72   if(!w1)
73     glk_exit();  /* Run screaming! */
74   w2 = glk_window_open(w1, winmethod_Above | winmethod_Fixed, 1,
75                        wintype_TextGrid, 0);
76
77   if(w2) {
78     winid_t o;
79     o = glk_window_get_parent(w2);
80     glk_window_set_arrangement(o, winmethod_Above | winmethod_Proportional,
81                                100, w2);
82     glk_window_get_size(w2, wid, hei);
83     glk_window_set_arrangement(o, winmethod_Above | winmethod_Fixed,
84                                1, w2);
85   }
86
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);
95 #else
96   cur_cap[HAS_SOUND] = FALSE;
97 #endif
98 #ifdef GLK_MODULE_IMAGE
99   cur_cap[HAS_GRAPHICS] = glk_gestalt(gestalt_Graphics, 0);
100 #else
101   cur_cap[HAS_GRAPHICS] = FALSE;
102 #endif
103   cur_cap[HAS_TIMER] = glk_gestalt(gestalt_Timer, 0);
104   cur_cap[HAS_MOUSE] = glk_gestalt(gestalt_MouseInput, wintype_TextGrid);
105
106   cur_cap[HAS_DEFVAR] = TRUE;
107
108   cur_cap[HAS_UNDO] = TRUE;
109   cur_cap[HAS_MENU] = FALSE;
110
111   if(w1)
112     glk_window_close(w1, NULL);
113   if(w2)
114     glk_window_close(w2, NULL);
115 }
116
117
118 void set_header(glui32 width, glui32 height)
119 {
120   unsigned i;
121   zword flags[3];
122   flags[1] = LOBYTE(HD_FLAGS1);
123   flags[2] = LOWORD(HD_FLAGS2);
124
125   cur_cap[DO_TANDY] = do_tandy;
126
127   cur_cap[IS_TRANS] = is_transcripting();
128   cur_cap[FORCE_FIXED] = is_fixed;
129
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;
135       else
136         flags[z_cap_table[i].flagnum] &= ~(1 << z_cap_table[i].bit);
137     }
138   }
139
140   LOBYTEwrite(HD_FLAGS1, flags[1]);
141   LOWORDwrite(HD_FLAGS2, flags[2]);
142
143   LOBYTEwrite(HD_TERPNUM, interp_num);
144   LOBYTEwrite(HD_TERPVER, interp_ver);
145
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 */
148   if(username)
149     for(i = 0; i < 8 && username[i] && !LOBYTE(HD_USERID + i); i++)
150       LOBYTEwrite(HD_USERID + i, username[i]);
151
152   if(zversion == 6) {
153     width = 320;
154     height = 200;
155   }
156
157
158   if(height > 255)
159     height = 255;
160   LOBYTEwrite(HD_SCR_HEIGHT, height);    /* screen height (255 = infinite) */
161   LOBYTEwrite(HD_SCR_WIDTH, width);      /* screen width */
162   if(zversion >= 5) {
163     LOWORDwrite(HD_SCR_WUNIT, width);    /* screen width in units */
164     LOWORDwrite(HD_SCR_HUNIT, height);   /* screen height in units */
165   }
166
167   if(zversion != 6) {
168     LOBYTEwrite(HD_FNT_WIDTH, 1);        /* font width/height in units */
169     LOBYTEwrite(HD_FNT_HEIGHT, 1);       /* font height/width in units */
170   } else {
171     LOBYTEwrite(HD_FNT_WIDTH, 8);
172     LOBYTEwrite(HD_FNT_HEIGHT, 8);
173   }
174
175   LOBYTEwrite(HD_DEF_BACK, 1);           /* default background color */
176   LOBYTEwrite(HD_DEF_FORE, 1);           /* default foreground color */
177
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);
182 }
183
184 static void check_ascii_mode(void)
185 {
186   BOOL hascr = FALSE;
187   BOOL haslf = FALSE;
188   BOOL hascrnolf = FALSE;
189   BOOL haslfnocr = FALSE;
190   BOOL has8bit = FALSE;
191   offset i;
192   for(i = 0; i < total_size; i++) {
193     if(z_memory[i] & 0x80)
194       has8bit = TRUE;
195     else if(z_memory[i] == 0x0a) {
196       haslf = TRUE;
197       if(i && z_memory[i-1] != 0x0d)
198         haslfnocr = TRUE;
199     }
200     else if(z_memory[i] == 0x0d) {
201       hascr = TRUE;
202       if(i+1 < total_size && z_memory[i+1] != 0x0a)
203         hascrnolf = TRUE;
204     }
205   }
206   if(!has8bit)
207     n_show_error(E_CORRUPT, "All bytes are 7 bit; top bits were likely stripped in transfer", 0);
208   else if(!hascr)
209     n_show_error(E_CORRUPT, "No CR bytes; likely transfered in ASCII mode", 0);
210   else if(!haslf)
211     n_show_error(E_CORRUPT, "No LF bytes; likely transfered in ASCII mode", 0);
212   else if(!hascrnolf)
213     n_show_error(E_CORRUPT, "All CR are followed by LFs; likely transfered in ASCII mode", 0);
214   else if(!haslfnocr)
215     n_show_error(E_CORRUPT, "All LFs are preceded by CRs; likely transfered in ASCII mode", 0);
216 }
217
218
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)
221 {
222   zbyte header[64];
223
224   if(glk_get_buffer_stream(zfile, (char *) header, 64) != 64) {
225     if(report_errors)
226       n_show_error(E_SYSTEM, "file too small", 0);
227     return FALSE;
228   }
229
230   zversion        = header[HD_ZVERSION];
231   switch(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;
236   case 8:
237     granularity = 8; break;
238   default:
239     if(report_errors)
240       n_show_error(E_VERSION, "unknown version number or not zcode", zversion);
241     return FALSE;
242   }
243   
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);
251   
252   switch(zversion) {
253   case 1: case 2:
254     game_size     = filesize;
255     break;
256   case 3:
257     game_size     = ((offset) MSBdecodeZ(header + HD_LENGTH)) * 2;
258     break;
259   case 4: case 5:
260     game_size     = ((offset) MSBdecodeZ(header + HD_LENGTH)) * 4;
261     break;
262   case 6: case 7: case 8:
263     game_size     = ((offset) MSBdecodeZ(header + HD_LENGTH)) * 8;
264     break;
265   }
266   if(zversion == 6 || zversion == 7) {
267     rstart        = ((offset) MSBdecodeZ(header + HD_RTN_OFFSET)) * 8;
268     sstart        = ((offset) MSBdecodeZ(header + HD_STR_OFFSET)) * 8;
269   } else {
270     rstart        = 0;
271     sstart        = 0;
272   }
273
274   /* Check consistency of stuff */
275
276   if(filesize < game_size) {
277     if(report_errors)
278       n_show_error(E_CORRUPT, "file on a diet", filesize);
279     return FALSE;
280   }
281
282   if(dynamic_size > game_size) {
283     if(report_errors)
284       n_show_error(E_CORRUPT, "dynamic memory length > game size", dynamic_size);
285     return FALSE;
286   }
287   
288   if(high_mem_mark < dynamic_size) {
289     if(report_errors)
290       n_show_error(E_CORRUPT, "dynamic memory overlaps high memory", dynamic_size);
291   }
292
293   if(PC > game_size) {
294     if(report_errors)
295       n_show_error(E_CORRUPT, "initial PC greater than game size", PC);
296     return FALSE;
297   }
298
299   if(PC < 64) {
300     if(report_errors)
301       n_show_error(E_CORRUPT, "initial PC in header", PC);
302     return FALSE;
303   }
304   
305   return TRUE;
306 }
307
308
309 void z_init(strid_t zfile)
310 {
311   offset bytes_read;
312   offset i;
313   glui32 width, height;
314
315   z_random(0); /* Initialize random number generator */
316
317   init_windows(is_fixed, 80, 24);
318
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);
322
323   n_free(z_memory);
324   if(game_size <= 65535)
325     z_memory = (zbyte *) n_malloc(65535);
326   else
327     z_memory = (zbyte *) n_malloc(game_size);
328
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);
333
334   z_checksum = 0;
335   if (zversion >= 3) {
336     for(i = 0x40; i < game_size; i++)
337       z_checksum += HIBYTE(i);
338     z_checksum = ARITHMASK(z_checksum);
339
340     if(LOWORD(HD_CHECKSUM) != 0 && z_checksum != LOWORD(HD_CHECKSUM)) {
341       n_show_error(E_CORRUPT, "Checksum does not match", z_checksum);
342       check_ascii_mode();
343     }
344   }
345
346
347   
348   init_stack(1024, 128);
349
350   if(zversion == 6) {
351     zword dummylocals[15];
352     mop_call(PC, 0, dummylocals, 0);
353   }
354
355   kill_windows();
356   investigate_suckage(&width, &height);
357   if(height == 0)
358     height = 1;
359   if(width == 0)
360     width = 80;
361   set_header(width, height);
362   init_windows(is_fixed, width, height);
363
364   if(zversion <= 3) {
365     opcodetable[OFFSET_0OP +  5] = op_save1;
366     opcodetable[OFFSET_0OP +  6] = op_restore1;
367   } else {
368     opcodetable[OFFSET_0OP +  5] = op_save4;
369     opcodetable[OFFSET_0OP +  6] = op_restore4;
370   }
371   if(zversion <= 4) {
372     opcodetable[OFFSET_0OP +  9] = op_pop;
373     opcodetable[OFFSET_1OP + 15] = op_not;
374     opcodetable[OFFSET_VAR +  4] = op_sread;
375   } else {
376     opcodetable[OFFSET_0OP +  9] = op_catch;
377     opcodetable[OFFSET_1OP + 15] = op_call_n;
378     opcodetable[OFFSET_VAR +  4] = op_aread;
379   }
380   if(zversion == 6) {
381     opcodetable[OFFSET_VAR + 11] = op_set_window6;
382   } else {
383     opcodetable[OFFSET_VAR + 11] = op_set_window;
384   }
385
386   objects_init();
387   init_sound();
388
389   in_timer = FALSE;
390   exit_decoder = FALSE;
391   time_ret = 0;
392
393 #ifdef DEBUGGING
394   init_infix(0);
395 #endif
396
397   if(!quiet) {
398     output_string("Nitfol 0.5 Copyright 1999 Evin Robertson.\r");
399 #ifdef DEBUGGING    
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");
401 #else
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");
403 #endif
404   }
405 }
406
407
408 void z_close(void)
409 {
410 #ifdef DEBUGGING
411   kill_infix();
412 #endif
413   kill_sound();
414   kill_undo();
415   free_windows();
416   kill_stack();
417   n_free(z_memory);
418 }