Update interpreters to latest Garglk codebase
[projects/chimara/chimara.git] / interpreters / nitfol / quetzal.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18     The author can be reached at nitfol@deja.com
19 */
20 #include "nitfol.h"
21
22 /* Note that quetzal stack save/restore is handled at the bottom of stack.c */
23
24
25 /* Sets *diff to a malloced quetzal diff of the first length bytes of a and b
26  * *diff_length is set to the length of *diff.  Returns successfulness.  */
27 BOOL quetzal_diff(const zbyte *a, const zbyte *b, glui32 length,
28                   zbyte **diff, glui32 *diff_length, BOOL do_utf8)
29 {
30   /* Worst case: every other byte is the same as in the original, so we have
31      to store 1.5 times the original length.  Allocate a couple bytes extra
32      to be on the safe side. (yes, I realize it could actually be twice the
33      original length if you use a really bad algorithm, but I don't) */
34   zbyte *attempt = (zbyte *) n_malloc((length * 3) / 2 + 2);
35                                                 
36   glui32 attempt_len = 0;
37   glui32 same_len;
38
39   *diff = NULL;
40
41   while(length) {
42     /* Search through consecutive identical bytes */
43     for(same_len = 0; same_len < length && a[same_len] == b[same_len]; same_len++)
44       ;
45     a += same_len; b += same_len; length -= same_len;
46
47     if(length) {
48       /* If we hit the end of the region, we don't have to record that the
49          bytes at the end are the same */
50       while(same_len) {
51         attempt[attempt_len++] = 0;
52         same_len--; /* We always store length-1 */
53         if(do_utf8) {
54           if(same_len <= 0x7f) {
55             attempt[attempt_len++] = same_len;
56             same_len = 0;
57           } else {
58             if(same_len <= 0x7fff) {
59               attempt[attempt_len++] = (same_len & 0x7f) | 0x80;
60               attempt[attempt_len++] = (same_len & 0x7f80) >> 7;
61               same_len = 0;
62             } else {
63               attempt[attempt_len++] = (0x7fff & 0x7f) | 0x80;
64               attempt[attempt_len++] = (0x7fff & 0x7f80) >> 7;
65               same_len -= 0x7fff;
66             }
67           }
68         } else {
69           if(same_len <= 0xff) {
70             attempt[attempt_len++] = same_len;
71             same_len = 0;
72           } else {
73             attempt[attempt_len++] = 0xff;
74             same_len -= 0xff;
75           }
76         }
77       }
78
79       attempt[attempt_len++] = *a++ ^ *b++;
80       length--;
81     }
82   }
83
84   *diff = (zbyte *) n_realloc(attempt, attempt_len);
85   *diff_length = attempt_len;
86   return TRUE;
87 }
88
89 /* Applies a quetzal diff to dest */
90 BOOL quetzal_undiff(zbyte *dest, glui32 length,
91                     const zbyte *diff, glui32 diff_length, BOOL do_utf8)
92 {
93   glui32 iz = 0;
94   glui32 id;
95
96   for(id = 0; id < diff_length; id++) {
97     if(diff[id] == 0) {
98       unsigned runlen;
99       if(++id >= diff_length)
100         return FALSE;  /* Incomplete run */
101       runlen = diff[id];
102       if(do_utf8 && diff[id] & 0x80) {
103         if(++id >= diff_length)
104           return FALSE; /* Incomplete extended run */
105         runlen = (runlen & 0x7f) | (((unsigned) diff[id]) << 7);
106       }
107       iz += runlen + 1;
108     } else {
109       dest[iz] ^= diff[id];
110       iz++;
111     }
112     if(iz >= length)
113       return FALSE; /* Too long */
114   }
115   return TRUE;
116 }
117
118
119 static unsigned qifhd[] = { 2, 6, 2, 3, 0 };
120 enum qifhdnames { qrelnum, qsernum, qchecksum = qsernum + 6, qinitPC };
121
122 BOOL savequetzal(strid_t stream)
123 {
124   unsigned n;
125   glui32 start_loc, last_loc;
126   glui32 qs[13];
127   glui32 hdrsize, memsize, stksize, padding, chunksize, intdsize;
128   zbyte *original = (zbyte *) n_malloc(dynamic_size);
129   zbyte *diff = NULL;
130
131   glk_stream_set_position(current_zfile, zfile_offset, seekmode_Start);
132   glk_get_buffer_stream(current_zfile, (char *) original, dynamic_size);
133
134   if(!quetzal_diff(original, z_memory, dynamic_size, &diff, &memsize, FALSE)
135      || memsize >= dynamic_size) {  /* If we're losing try uncompressed */
136     if(diff)
137       free(diff);
138     diff = NULL;
139     memsize = dynamic_size;
140   }
141
142   hdrsize = 13;
143   stksize = get_quetzal_stack_size();
144   intdsize = intd_get_size();
145   padding = 8 + (hdrsize & 1) +
146             8 + (memsize & 1) +
147             8 + (stksize & 1);
148   if(intdsize)
149     padding += 8 + (intdsize & 1);
150   chunksize = 4 + hdrsize + memsize + stksize + intdsize + padding;
151
152
153   iffputchunk(stream, "FORM", chunksize);
154   start_loc = glk_stream_get_position(stream);
155
156   w_glk_put_buffer_stream(stream, "IFZS", 4);
157
158   iffputchunk(stream, "IFhd", hdrsize);
159   last_loc = glk_stream_get_position(stream);
160   qs[qrelnum] = LOWORD(HD_RELNUM);
161   for(n = 0; n < 6; n++)
162     qs[qsernum + n] = LOBYTE(HD_SERNUM + n);
163   qs[qchecksum] = LOWORD(HD_CHECKSUM);
164   qs[qinitPC] = PC;
165   emptystruct(stream, qifhd, qs);
166
167   if(glk_stream_get_position(stream) - last_loc != hdrsize) {
168     n_show_error(E_SAVE, "header size miscalculation", glk_stream_get_position(stream) - last_loc);
169     return FALSE;
170   }
171
172   if(intdsize) {
173     iffputchunk(stream, "IntD", intdsize);
174
175     last_loc = glk_stream_get_position(stream);
176     intd_filehandle_make(stream);
177
178     if(glk_stream_get_position(stream) - last_loc != intdsize) {
179       n_show_error(E_SAVE, "IntD size miscalculation", glk_stream_get_position(stream) - last_loc);
180       return FALSE;
181     }
182   }
183
184
185   if(diff) {
186     iffputchunk(stream, "CMem", memsize);
187     last_loc = glk_stream_get_position(stream);
188     w_glk_put_buffer_stream(stream, (char *) diff, memsize);
189   } else {
190     iffputchunk(stream, "UMem", memsize);
191     last_loc = glk_stream_get_position(stream);
192     w_glk_put_buffer_stream(stream, (char *) z_memory, dynamic_size);
193   }
194
195   if(glk_stream_get_position(stream) - last_loc != memsize) {
196     n_show_error(E_SAVE, "memory size miscalculation", glk_stream_get_position(stream) - last_loc);
197     return FALSE;
198   }
199
200   iffputchunk(stream, "Stks", stksize);
201   last_loc = glk_stream_get_position(stream);
202   quetzal_stack_save(stream);
203
204   if(glk_stream_get_position(stream) - last_loc != stksize) {
205     n_show_error(E_SAVE, "stack miscalculation", glk_stream_get_position(stream) - last_loc);
206     return FALSE;
207   }
208
209   if(glk_stream_get_position(stream) - start_loc != chunksize) {
210     n_show_error(E_SAVE, "chunks size miscalculation", glk_stream_get_position(stream) - last_loc);
211     return FALSE;
212   }
213
214   return TRUE;
215 }
216
217
218 BOOL restorequetzal(strid_t stream)
219 {
220   char desttype[4];
221   glui32 chunksize;
222   glui32 start;
223
224   if(!ifffindchunk(stream, "FORM", &chunksize, 0)) {
225     n_show_error(E_SAVE, "no FORM chunk", 0);
226     return FALSE;
227   }
228
229   glk_get_buffer_stream(stream, desttype, 4);
230   if(n_strncmp(desttype, "IFZS", 4) != 0) {
231     n_show_error(E_SAVE, "FORM chunk not IFZS; this isn't a quetzal file", 0);
232     return FALSE;
233   }
234
235   start = glk_stream_get_position(stream);
236
237   if(!ifffindchunk(stream, "IFhd", &chunksize, start)) {
238     n_show_error(E_SAVE, "no IFhd chunk", 0);
239     return FALSE;
240   } else {
241     unsigned n;
242     glui32 qsifhd[10];
243     
244     fillstruct(stream, qifhd, qsifhd, NULL);
245
246     if(qsifhd[qrelnum] != LOWORD(HD_RELNUM)) {
247       n_show_error(E_SAVE, "release number does not match", qsifhd[qrelnum]);
248       return FALSE;
249     }
250     for(n = 0; n < 6; n++) {
251       if(qsifhd[qsernum + n] != LOBYTE(HD_SERNUM + n)) {
252         n_show_error(E_SAVE, "serial number does not match", n);
253         return FALSE;
254       }
255     }
256     if(qsifhd[qchecksum] != LOWORD(HD_CHECKSUM)) {
257       n_show_error(E_SAVE, "checksum does not match", qsifhd[qchecksum]);
258       return FALSE;
259     }
260     if(qsifhd[qinitPC] > total_size) {
261       n_show_error(E_SAVE, "PC past end of memory", qsifhd[qinitPC]);
262       return FALSE;
263     }
264     
265     PC = qsifhd[qinitPC];
266   }
267   if(!ifffindchunk(stream, "UMem", &chunksize, start)) {
268     if(!ifffindchunk(stream, "CMem", &chunksize, start)) {
269       n_show_error(E_SAVE, "no memory chunk (UMem or CMem)", 0);
270       return FALSE;
271     } else {
272       zbyte *compressed_chunk = (zbyte *) malloc(chunksize);
273       glk_get_buffer_stream(stream, (char *) compressed_chunk, chunksize);
274
275       glk_stream_set_position(current_zfile, zfile_offset, seekmode_Start);
276       glk_get_buffer_stream(current_zfile, (char *) z_memory, dynamic_size);
277
278       if(!quetzal_undiff(z_memory, dynamic_size,
279                          compressed_chunk, chunksize, FALSE)) {
280         n_show_error(E_SAVE, "error in compressed data", 0);
281         return FALSE;
282       }
283     }
284   } else {
285     if(chunksize != dynamic_size) {
286       n_show_error(E_SAVE, "uncompressed memory chunk not expected size",
287                  chunksize);
288       return FALSE;
289     }
290     glk_get_buffer_stream(stream, (char *) z_memory, chunksize);
291   }
292
293   if(!ifffindchunk(stream, "Stks", &chunksize, start)) {
294     n_show_error(E_SAVE, "no Stks chunk", 0);
295     return FALSE;
296   } else {
297     if(!quetzal_stack_restore(stream, chunksize))
298       return FALSE;
299   }
300
301   return TRUE;
302 }
303
304 static unsigned qintd[] = { 4, 1, 1, 2, 4 };
305 enum qintdnames { qopid, qflags, qcontid, qresrvd, qintid };
306
307 strid_t quetzal_findgamefile(strid_t stream)
308 {
309   char desttype[4];
310   glui32 chunksize;
311   glui32 start;
312
313   if(!ifffindchunk(stream, "FORM", &chunksize, 0))
314     return 0;
315
316   glk_get_buffer_stream(stream, desttype, 4);
317   if(n_strncmp(desttype, "IFZS", 4) != 0)
318     return 0;
319
320   start = glk_stream_get_position(stream);
321
322   if(ifffindchunk(stream, "IntD", &chunksize, start)) {
323     glui32 qsintd[6];
324     strid_t file;
325     fillstruct(stream, qintd, qsintd, NULL);
326     file = intd_filehandle_open(stream, qsintd[qopid],
327                                 qsintd[qcontid], qsintd[qintid],
328                                 chunksize - 12);
329     if(file)
330       return file;
331   }
332
333   if(ifffindchunk(stream, "IFhd", &chunksize, start)) {
334     unsigned n;
335     glui32 qsifhd[10];
336     strid_t file = 0;
337     char serial[6];
338     
339     fillstruct(stream, qifhd, qsifhd, NULL);
340
341     for(n = 0; n < 6; n++)
342       serial[n] = qsifhd[qsernum + n];
343
344     do {
345       file = startup_findfile();
346       if(file) {
347         if(check_game_for_save(file, qsifhd[qrelnum], serial,
348                                qsifhd[qchecksum]))
349           return file;
350       }
351     } while(file);
352   }
353
354   return 0;
355 }
356
357