Update to Blorb protocol 1.5
[projects/chimara/chimara.git] / libchimara / gi_blorb.c
1 /* gi_blorb.c: Blorb library layer for Glk API.
2     gi_blorb version 1.5.
3     Designed by Andrew Plotkin <erkyrath@eblong.com>
4     http://eblong.com/zarf/glk/
5
6     This file is copyright 1998-2010 by Andrew Plotkin. You may copy,
7     distribute, and incorporate it into your own programs, by any means
8     and under any conditions, as long as you do not modify it. You may
9     also modify this file, incorporate it into your own programs,
10     and distribute the modified version, as long as you retain a notice
11     in your program or documentation which mentions my name and the URL
12     shown above.
13 */
14
15 #include "glk.h"
16 #include "gi_blorb.h"
17
18 #ifndef NULL
19 #define NULL 0
20 #endif
21 #ifndef TRUE
22 #define TRUE 1
23 #endif
24 #ifndef FALSE
25 #define FALSE 0
26 #endif
27
28 /* The magic macro of endian conversion. */
29
30 #define giblorb_native4(v)   \
31     ( (((glui32)((v)[3])      ) & 0x000000ff)    \
32     | (((glui32)((v)[2]) <<  8) & 0x0000ff00)    \
33     | (((glui32)((v)[1]) << 16) & 0x00ff0000)    \
34     | (((glui32)((v)[0]) << 24) & 0xff000000))
35
36 /* More four-byte constants. */
37
38 #define giblorb_ID_FORM (giblorb_make_id('F', 'O', 'R', 'M'))
39 #define giblorb_ID_IFRS (giblorb_make_id('I', 'F', 'R', 'S'))
40 #define giblorb_ID_RIdx (giblorb_make_id('R', 'I', 'd', 'x'))
41
42 /* giblorb_chunkdesc_t: Describes one chunk of the Blorb file. */
43 typedef struct giblorb_chunkdesc_struct {
44     glui32 type;
45     glui32 len;
46     glui32 startpos; /* start of chunk header */
47     glui32 datpos; /* start of data (either startpos or startpos+8) */
48     
49     void *ptr; /* pointer to malloc'd data, if loaded */
50     int auxdatnum; /* entry in the auxsound/auxpict array; -1 if none.
51         This only applies to chunks that represent resources;  */
52     
53 } giblorb_chunkdesc_t;
54
55 /* giblorb_resdesc_t: Describes one resource in the Blorb file. */
56 typedef struct giblorb_resdesc_struct {
57     glui32 usage;
58     glui32 resnum;
59     glui32 chunknum;
60 } giblorb_resdesc_t;
61
62 /* giblorb_map_t: Holds the complete description of an open Blorb file. */
63 struct giblorb_map_struct {
64     glui32 inited; /* holds giblorb_Inited_Magic if the map structure is 
65         valid */
66     strid_t file;
67     
68     int numchunks;
69     giblorb_chunkdesc_t *chunks; /* list of chunk descriptors */
70     
71     int numresources;
72     giblorb_resdesc_t *resources; /* list of resource descriptors */
73     giblorb_resdesc_t **ressorted; /* list of pointers to descriptors 
74         in map->resources -- sorted by usage and resource number. */
75 };
76
77 #define giblorb_Inited_Magic (0xB7012BED) 
78
79 /* Static variables. */
80
81 static int lib_inited = FALSE;
82
83 static giblorb_err_t giblorb_initialize(void);
84 static giblorb_err_t giblorb_initialize_map(giblorb_map_t *map);
85 static void giblorb_qsort(giblorb_resdesc_t **list, int len);
86 static giblorb_resdesc_t *giblorb_bsearch(giblorb_resdesc_t *sample, 
87     giblorb_resdesc_t **list, int len);
88 static void *giblorb_malloc(glui32 len);
89 static void *giblorb_realloc(void *ptr, glui32 len);
90 static void giblorb_free(void *ptr);
91
92 static giblorb_err_t giblorb_initialize()
93 {
94     return giblorb_err_None;
95 }
96
97 giblorb_err_t giblorb_create_map(strid_t file, giblorb_map_t **newmap)
98 {
99     giblorb_err_t err;
100     giblorb_map_t *map;
101     glui32 readlen;
102     glui32 nextpos, totallength;
103     giblorb_chunkdesc_t *chunks;
104     int chunks_size, numchunks;
105     char buffer[16];
106     
107     *newmap = NULL;
108     
109     if (!lib_inited) {
110         err = giblorb_initialize();
111         if (err)
112             return err;
113         lib_inited = TRUE;
114     }
115
116     /* First, chew through the file and index the chunks. */
117     
118     glk_stream_set_position(file, 0, seekmode_Start);
119     
120     readlen = glk_get_buffer_stream(file, buffer, 12);
121     if (readlen != 12)
122         return giblorb_err_Read;
123     
124     if (giblorb_native4(buffer+0) != giblorb_ID_FORM)
125         return giblorb_err_Format;
126     if (giblorb_native4(buffer+8) != giblorb_ID_IFRS)
127         return giblorb_err_Format;
128     
129     totallength = giblorb_native4(buffer+4) + 8;
130     nextpos = 12;
131
132     chunks_size = 8;
133     numchunks = 0;
134     chunks = (giblorb_chunkdesc_t *)giblorb_malloc(sizeof(giblorb_chunkdesc_t) 
135         * chunks_size);
136
137     while (nextpos < totallength) {
138         glui32 type, len;
139         int chunum;
140         giblorb_chunkdesc_t *chu;
141         
142         glk_stream_set_position(file, nextpos, seekmode_Start);
143         
144         readlen = glk_get_buffer_stream(file, buffer, 8);
145         if (readlen != 8)
146             return giblorb_err_Read;
147         
148         type = giblorb_native4(buffer+0);
149         len = giblorb_native4(buffer+4);
150         
151         if (numchunks >= chunks_size) {
152             chunks_size *= 2;
153             chunks = (giblorb_chunkdesc_t *)giblorb_realloc(chunks, 
154                 sizeof(giblorb_chunkdesc_t) * chunks_size);
155         }
156         
157         chunum = numchunks;
158         chu = &(chunks[chunum]);
159         numchunks++;
160         
161         chu->type = type;
162         chu->startpos = nextpos;
163         if (type == giblorb_ID_FORM) {
164             chu->datpos = nextpos;
165             chu->len = len+8;
166         }
167         else {
168             chu->datpos = nextpos+8;
169             chu->len = len;
170         }
171         chu->ptr = NULL;
172         chu->auxdatnum = -1;
173         
174         nextpos = nextpos + len + 8;
175         if (nextpos & 1)
176             nextpos++;
177             
178         if (nextpos > totallength)
179             return giblorb_err_Format;
180     }
181     
182     /* The basic IFF structure seems to be ok, and we have a list of
183         chunks. Now we allocate the map structure itself. */
184     
185     map = (giblorb_map_t *)giblorb_malloc(sizeof(giblorb_map_t));
186     if (!map) {
187         giblorb_free(chunks);
188         return giblorb_err_Alloc;
189     }
190         
191     map->inited = giblorb_Inited_Magic;
192     map->file = file;
193     map->chunks = chunks;
194     map->numchunks = numchunks;
195     map->resources = NULL;
196     map->ressorted = NULL;
197     map->numresources = 0;
198     /*map->releasenum = 0;
199     map->zheader = NULL;
200     map->resolution = NULL;
201     map->palettechunk = -1;
202     map->palette = NULL;
203     map->auxsound = NULL;
204     map->auxpict = NULL;*/
205     
206     /* Now we do everything else involved in loading the Blorb file,
207         such as building resource lists. */
208     
209     err = giblorb_initialize_map(map);
210     if (err) {
211         giblorb_destroy_map(map);
212         return err;
213     }
214     
215     *newmap = map;
216     return giblorb_err_None;
217 }
218
219 static giblorb_err_t giblorb_initialize_map(giblorb_map_t *map)
220 {
221     /* It is important that the map structure be kept valid during this
222         function. If this returns an error, giblorb_destroy_map() will 
223         be called. */
224         
225     int ix, jx;
226     giblorb_result_t chunkres;
227     giblorb_err_t err;
228     char *ptr;
229     glui32 len;
230     glui32 numres;
231     int gotindex = FALSE; 
232
233     for (ix=0; ix<map->numchunks; ix++) {
234         giblorb_chunkdesc_t *chu = &map->chunks[ix];
235         
236         switch (chu->type) {
237         
238             case giblorb_ID_RIdx:
239                 /* Resource index chunk: build the resource list and 
240                 sort it. */
241                 
242                 if (gotindex) 
243                     return giblorb_err_Format; /* duplicate index chunk */
244                 err = giblorb_load_chunk_by_number(map, giblorb_method_Memory, 
245                     &chunkres, ix);
246                 if (err) 
247                     return err;
248                 
249                 ptr = chunkres.data.ptr;
250                 len = chunkres.length;
251                 numres = giblorb_native4(ptr+0);
252
253                 if (numres) {
254                     int ix2;
255                     giblorb_resdesc_t *resources;
256                     giblorb_resdesc_t **ressorted;
257                     
258                     if (len != numres*12+4)
259                         return giblorb_err_Format; /* bad length field */
260                     
261                     resources = (giblorb_resdesc_t *)giblorb_malloc(numres 
262                         * sizeof(giblorb_resdesc_t));
263                     ressorted = (giblorb_resdesc_t **)giblorb_malloc(numres 
264                         * sizeof(giblorb_resdesc_t *));
265                     if (!ressorted || !resources)
266                         return giblorb_err_Alloc;
267                     
268                     ix2 = 0;
269                     for (jx=0; jx<numres; jx++) {
270                         giblorb_resdesc_t *res = &(resources[jx]);
271                         glui32 respos;
272                         
273                         res->usage = giblorb_native4(ptr+jx*12+4);
274                         res->resnum = giblorb_native4(ptr+jx*12+8);
275                         respos = giblorb_native4(ptr+jx*12+12);
276                         
277                         while (ix2 < map->numchunks 
278                             && map->chunks[ix2].startpos < respos)
279                             ix2++;
280                         
281                         if (ix2 >= map->numchunks 
282                             || map->chunks[ix2].startpos != respos)
283                             return giblorb_err_Format; /* start pos does  
284                                 not match a real chunk */
285                         
286                         res->chunknum = ix2;
287                         
288                         ressorted[jx] = res;
289                     }
290                     
291                     /* Sort a resource list (actually a list of pointers to 
292                         structures in map->resources.) This makes it easy 
293                         to find resources by usage and resource number. */
294                     giblorb_qsort(ressorted, numres);
295                     
296                     map->numresources = numres;
297                     map->resources = resources;
298                     map->ressorted = ressorted;
299                 }
300                 
301                 giblorb_unload_chunk(map, ix);
302                 gotindex = TRUE;
303                 break;
304             
305         }
306     }
307     
308     return giblorb_err_None;
309 }
310
311 giblorb_err_t giblorb_destroy_map(giblorb_map_t *map)
312 {
313     int ix;
314     
315     if (!map || !map->chunks || map->inited != giblorb_Inited_Magic)
316         return giblorb_err_NotAMap;
317     
318     for (ix=0; ix<map->numchunks; ix++) {
319         giblorb_chunkdesc_t *chu = &(map->chunks[ix]);
320         if (chu->ptr) {
321             giblorb_free(chu->ptr);
322             chu->ptr = NULL;
323         }
324     }
325     
326     if (map->chunks) {
327         giblorb_free(map->chunks);
328         map->chunks = NULL;
329     }
330     
331     map->numchunks = 0;
332     
333     if (map->resources) {
334         giblorb_free(map->resources);
335         map->resources = NULL;
336     }
337     
338     if (map->ressorted) {
339         giblorb_free(map->ressorted);
340         map->ressorted = NULL;
341     }
342     
343     map->numresources = 0;
344     
345     map->file = NULL;
346     map->inited = 0;
347     
348     giblorb_free(map);
349
350     return giblorb_err_None;
351 }
352
353 /* Chunk-handling functions. */
354
355 giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map, 
356     glui32 method, giblorb_result_t *res, glui32 type, 
357     glui32 count)
358 {
359     int ix;
360     
361     for (ix=0; ix < map->numchunks; ix++) {
362         if (map->chunks[ix].type == type) {
363             if (count == 0)
364                 break;
365             count--;
366         }
367     }
368     
369     if (ix >= map->numchunks) {
370         return giblorb_err_NotFound;
371     }
372     
373     return giblorb_load_chunk_by_number(map, method, res, ix);
374 }
375
376 giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map, 
377     glui32 method, giblorb_result_t *res, glui32 chunknum)
378 {
379     giblorb_chunkdesc_t *chu;
380     
381     if (chunknum < 0 || chunknum >= map->numchunks)
382         return giblorb_err_NotFound;
383
384     chu = &(map->chunks[chunknum]);
385     
386     switch (method) {
387     
388         case giblorb_method_DontLoad:
389             /* do nothing */
390             break;
391             
392         case giblorb_method_FilePos:
393             res->data.startpos = chu->datpos;
394             break;
395             
396         case giblorb_method_Memory:
397             if (!chu->ptr) {
398                 glui32 readlen;
399                 void *dat = giblorb_malloc(chu->len);
400                 
401                 if (!dat)
402                     return giblorb_err_Alloc;
403                 
404                 glk_stream_set_position(map->file, chu->datpos, 
405                     seekmode_Start);
406                 
407                 readlen = glk_get_buffer_stream(map->file, dat, 
408                     chu->len);
409                 if (readlen != chu->len)
410                     return giblorb_err_Read;
411                 
412                 chu->ptr = dat;
413             }
414             res->data.ptr = chu->ptr;
415             break;
416     }
417     
418     res->chunknum = chunknum;
419     res->length = chu->len;
420     res->chunktype = chu->type;
421     
422     return giblorb_err_None;
423 }
424
425 giblorb_err_t giblorb_load_resource(giblorb_map_t *map, glui32 method, 
426     giblorb_result_t *res, glui32 usage, glui32 resnum)
427 {
428     giblorb_resdesc_t sample;
429     giblorb_resdesc_t *found;
430     
431     sample.usage = usage;
432     sample.resnum = resnum;
433     
434     found = giblorb_bsearch(&sample, map->ressorted, map->numresources);
435     
436     if (!found)
437         return giblorb_err_NotFound;
438     
439     return giblorb_load_chunk_by_number(map, method, res, found->chunknum);
440 }
441
442 giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, glui32 chunknum)
443 {
444     giblorb_chunkdesc_t *chu;
445     
446     if (chunknum < 0 || chunknum >= map->numchunks)
447         return giblorb_err_NotFound;
448
449     chu = &(map->chunks[chunknum]);
450     
451     if (chu->ptr) {
452         giblorb_free(chu->ptr);
453         chu->ptr = NULL;
454     }
455     
456     return giblorb_err_None;
457 }
458
459 giblorb_err_t giblorb_count_resources(giblorb_map_t *map, glui32 usage,
460     glui32 *num, glui32 *min, glui32 *max)
461 {
462     int ix;
463     int count;
464     glui32 val;
465     glui32 minval, maxval;
466     
467     count = 0;
468     minval = 0;
469     maxval = 0;
470     
471     for (ix=0; ix<map->numresources; ix++) {
472         if (map->resources[ix].usage == usage) {
473             val = map->resources[ix].resnum;
474             if (count == 0) {
475                 count++;
476                 minval = val;
477                 maxval = val;
478             }
479             else {
480                 count++;
481                 if (val < minval)
482                     minval = val;
483                 if (val > maxval)
484                     maxval = val;
485             }
486         }
487     }
488     
489     if (num)
490         *num = count;
491     if (min)
492         *min = minval;
493     if (max)
494         *max = maxval;
495     
496     return giblorb_err_None;
497 }
498
499 /* Sorting and searching. */
500
501 static int sortsplot(giblorb_resdesc_t *v1, giblorb_resdesc_t *v2)
502 {
503     if (v1->usage < v2->usage)
504         return -1;
505     if (v1->usage > v2->usage)
506         return 1;
507     if (v1->resnum < v2->resnum)
508         return -1;
509     if (v1->resnum > v2->resnum)
510         return 1;
511     return 0;
512 }
513
514 static void giblorb_qsort(giblorb_resdesc_t **list, int len)
515 {
516     int ix, jx, res;
517     giblorb_resdesc_t *tmpptr, *pivot;
518     
519     if (len < 6) {
520         /* The list is short enough for a bubble-sort. */
521         for (jx=len-1; jx>0; jx--) {
522             for (ix=0; ix<jx; ix++) {
523                 res = sortsplot(list[ix], list[ix+1]);
524                 if (res > 0) {
525                     tmpptr = list[ix];
526                     list[ix] = list[ix+1];
527                     list[ix+1] = tmpptr;
528                 }
529             }
530         }
531     }
532     else {
533         /* Split the list. */
534         pivot = list[len/2];
535         ix=0;
536         jx=len;
537         while (1) {
538             while (ix < jx-1 && sortsplot(list[ix], pivot) < 0)
539                 ix++;
540             while (ix < jx-1 && sortsplot(list[jx-1], pivot) > 0)
541                 jx--;
542             if (ix >= jx-1)
543                 break;
544             tmpptr = list[ix];
545             list[ix] = list[jx-1];
546             list[jx-1] = tmpptr;
547         }
548         ix++;
549         /* Sort the halves. */
550         giblorb_qsort(list+0, ix);
551         giblorb_qsort(list+ix, len-ix);
552     }
553 }
554
555 giblorb_resdesc_t *giblorb_bsearch(giblorb_resdesc_t *sample, 
556     giblorb_resdesc_t **list, int len)
557 {
558     int top, bot, val, res;
559     
560     bot = 0;
561     top = len;
562     
563     while (bot < top) {
564         val = (top+bot) / 2;
565         res = sortsplot(list[val], sample);
566         if (res == 0)
567             return list[val];
568         if (res < 0) {
569             bot = val+1;
570         }
571         else {
572             top = val;
573         }
574     }
575     
576     return NULL;
577 }
578
579
580 /* Boring utility functions. If your platform doesn't support ANSI 
581     malloc(), feel free to edit these however you like. */
582
583 #include <glib.h> /* The OS-native header file -- you can edit 
584     this too. */
585
586 static void *giblorb_malloc(glui32 len)
587 {
588     return g_malloc(len);
589 }
590
591 static void *giblorb_realloc(void *ptr, glui32 len)
592 {
593     return g_realloc(ptr, len);
594 }
595
596 static void giblorb_free(void *ptr)
597 {
598     g_free(ptr);
599 }