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 /* Note that quetzal stack save/restore is handled at the bottom of stack.c */
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)
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);
36 glui32 attempt_len = 0;
42 /* Search through consecutive identical bytes */
43 for(same_len = 0; same_len < length && a[same_len] == b[same_len]; same_len++)
45 a += same_len; b += same_len; length -= same_len;
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 */
51 attempt[attempt_len++] = 0;
52 same_len--; /* We always store length-1 */
54 if(same_len <= 0x7f) {
55 attempt[attempt_len++] = same_len;
58 if(same_len <= 0x7fff) {
59 attempt[attempt_len++] = (same_len & 0x7f) | 0x80;
60 attempt[attempt_len++] = (same_len & 0x7f80) >> 7;
63 attempt[attempt_len++] = (0x7fff & 0x7f) | 0x80;
64 attempt[attempt_len++] = (0x7fff & 0x7f80) >> 7;
69 if(same_len <= 0xff) {
70 attempt[attempt_len++] = same_len;
73 attempt[attempt_len++] = 0xff;
79 attempt[attempt_len++] = *a++ ^ *b++;
84 *diff = (zbyte *) n_realloc(attempt, attempt_len);
85 *diff_length = attempt_len;
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)
96 for(id = 0; id < diff_length; id++) {
99 if(++id >= diff_length)
100 return FALSE; /* Incomplete run */
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);
109 dest[iz] ^= diff[id];
113 return FALSE; /* Too long */
119 static unsigned qifhd[] = { 2, 6, 2, 3, 0 };
120 enum qifhdnames { qrelnum, qsernum, qchecksum = qsernum + 6, qinitPC };
122 BOOL savequetzal(strid_t stream)
125 glui32 start_loc, last_loc;
127 glui32 hdrsize, memsize, stksize, padding, chunksize, intdsize;
128 zbyte *original = (zbyte *) n_malloc(dynamic_size);
131 glk_stream_set_position(current_zfile, zfile_offset, seekmode_Start);
132 glk_get_buffer_stream(current_zfile, (char *) original, dynamic_size);
134 if(!quetzal_diff(original, z_memory, dynamic_size, &diff, &memsize, FALSE)
135 || memsize >= dynamic_size) { /* If we're losing try uncompressed */
139 memsize = dynamic_size;
143 stksize = get_quetzal_stack_size();
144 intdsize = intd_get_size();
145 padding = 8 + (hdrsize & 1) +
149 padding += 8 + (intdsize & 1);
150 chunksize = 4 + hdrsize + memsize + stksize + intdsize + padding;
153 iffputchunk(stream, "FORM", chunksize);
154 start_loc = glk_stream_get_position(stream);
156 w_glk_put_buffer_stream(stream, "IFZS", 4);
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);
165 emptystruct(stream, qifhd, qs);
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);
173 iffputchunk(stream, "IntD", intdsize);
175 last_loc = glk_stream_get_position(stream);
176 intd_filehandle_make(stream);
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);
186 iffputchunk(stream, "CMem", memsize);
187 last_loc = glk_stream_get_position(stream);
188 w_glk_put_buffer_stream(stream, (char *) diff, memsize);
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);
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);
200 iffputchunk(stream, "Stks", stksize);
201 last_loc = glk_stream_get_position(stream);
202 quetzal_stack_save(stream);
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);
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);
218 BOOL restorequetzal(strid_t stream)
224 if(!ifffindchunk(stream, "FORM", &chunksize, 0)) {
225 n_show_error(E_SAVE, "no FORM chunk", 0);
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);
235 start = glk_stream_get_position(stream);
237 if(!ifffindchunk(stream, "IFhd", &chunksize, start)) {
238 n_show_error(E_SAVE, "no IFhd chunk", 0);
244 fillstruct(stream, qifhd, qsifhd, NULL);
246 if(qsifhd[qrelnum] != LOWORD(HD_RELNUM)) {
247 n_show_error(E_SAVE, "release number does not match", qsifhd[qrelnum]);
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);
256 if(qsifhd[qchecksum] != LOWORD(HD_CHECKSUM)) {
257 n_show_error(E_SAVE, "checksum does not match", qsifhd[qchecksum]);
260 if(qsifhd[qinitPC] > total_size) {
261 n_show_error(E_SAVE, "PC past end of memory", qsifhd[qinitPC]);
265 PC = qsifhd[qinitPC];
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);
272 zbyte *compressed_chunk = (zbyte *) malloc(chunksize);
273 glk_get_buffer_stream(stream, (char *) compressed_chunk, chunksize);
275 glk_stream_set_position(current_zfile, zfile_offset, seekmode_Start);
276 glk_get_buffer_stream(current_zfile, (char *) z_memory, dynamic_size);
278 if(!quetzal_undiff(z_memory, dynamic_size,
279 compressed_chunk, chunksize, FALSE)) {
280 n_show_error(E_SAVE, "error in compressed data", 0);
285 if(chunksize != dynamic_size) {
286 n_show_error(E_SAVE, "uncompressed memory chunk not expected size",
290 glk_get_buffer_stream(stream, (char *) z_memory, chunksize);
293 if(!ifffindchunk(stream, "Stks", &chunksize, start)) {
294 n_show_error(E_SAVE, "no Stks chunk", 0);
297 if(!quetzal_stack_restore(stream, chunksize))
304 static unsigned qintd[] = { 4, 1, 1, 2, 4 };
305 enum qintdnames { qopid, qflags, qcontid, qresrvd, qintid };
307 strid_t quetzal_findgamefile(strid_t stream)
313 if(!ifffindchunk(stream, "FORM", &chunksize, 0))
316 glk_get_buffer_stream(stream, desttype, 4);
317 if(n_strncmp(desttype, "IFZS", 4) != 0)
320 start = glk_stream_get_position(stream);
322 if(ifffindchunk(stream, "IntD", &chunksize, start)) {
325 fillstruct(stream, qintd, qsintd, NULL);
326 file = intd_filehandle_open(stream, qsintd[qopid],
327 qsintd[qcontid], qsintd[qintid],
333 if(ifffindchunk(stream, "IFhd", &chunksize, start)) {
339 fillstruct(stream, qifhd, qsifhd, NULL);
341 for(n = 0; n < 6; n++)
342 serial[n] = qsifhd[qsernum + n];
345 file = startup_findfile();
347 if(check_game_for_save(file, qsifhd[qrelnum], serial,