Eliminated warnings about static functions declared with G_GNUC_INTERNAL
[projects/chimara/chimara.git] / src / gi_blorb.c
1 /* gi_blorb.c: Blorb library layer for Glk API.
2     gi_blorb version 1.4.
3     Designed by Andrew Plotkin <erkyrath@eblong.com>
4     http://www.eblong.com/zarf/glk/index.html
5
6     This file is copyright 1998-2000 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 val;
231     glui32 numres;
232     int gotindex = FALSE; 
233
234     for (ix=0; ix<map->numchunks; ix++) {
235         giblorb_chunkdesc_t *chu = &map->chunks[ix];
236         
237         switch (chu->type) {
238         
239             case giblorb_ID_RIdx:
240                 /* Resource index chunk: build the resource list and 
241                 sort it. */
242                 
243                 if (gotindex) 
244                     return giblorb_err_Format; /* duplicate index chunk */
245                 err = giblorb_load_chunk_by_number(map, giblorb_method_Memory, 
246                     &chunkres, ix);
247                 if (err) 
248                     return err;
249                 
250                 ptr = chunkres.data.ptr;
251                 len = chunkres.length;
252                 numres = giblorb_native4(ptr+0);
253
254                 if (numres) {
255                     int ix2;
256                     giblorb_resdesc_t *resources;
257                     giblorb_resdesc_t **ressorted;
258                     
259                     if (len != numres*12+4)
260                         return giblorb_err_Format; /* bad length field */
261                     
262                     resources = (giblorb_resdesc_t *)giblorb_malloc(numres 
263                         * sizeof(giblorb_resdesc_t));
264                     ressorted = (giblorb_resdesc_t **)giblorb_malloc(numres 
265                         * sizeof(giblorb_resdesc_t *));
266                     if (!ressorted || !resources)
267                         return giblorb_err_Alloc;
268                     
269                     ix2 = 0;
270                     for (jx=0; jx<numres; jx++) {
271                         giblorb_resdesc_t *res = &(resources[jx]);
272                         glui32 respos;
273                         
274                         res->usage = giblorb_native4(ptr+jx*12+4);
275                         res->resnum = giblorb_native4(ptr+jx*12+8);
276                         respos = giblorb_native4(ptr+jx*12+12);
277                         
278                         while (ix2 < map->numchunks 
279                             && map->chunks[ix2].startpos < respos)
280                             ix2++;
281                         
282                         if (ix2 >= map->numchunks 
283                             || map->chunks[ix2].startpos != respos)
284                             return giblorb_err_Format; /* start pos does  
285                                 not match a real chunk */
286                         
287                         res->chunknum = ix2;
288                         
289                         ressorted[jx] = res;
290                     }
291                     
292                     /* Sort a resource list (actually a list of pointers to 
293                         structures in map->resources.) This makes it easy 
294                         to find resources by usage and resource number. */
295                     giblorb_qsort(ressorted, numres);
296                     
297                     map->numresources = numres;
298                     map->resources = resources;
299                     map->ressorted = ressorted;
300                 }
301                 
302                 giblorb_unload_chunk(map, ix);
303                 gotindex = TRUE;
304                 break;
305             
306         }
307     }
308     
309     return giblorb_err_None;
310 }
311
312 giblorb_err_t giblorb_destroy_map(giblorb_map_t *map)
313 {
314     int ix;
315     
316     if (!map || !map->chunks || map->inited != giblorb_Inited_Magic)
317         return giblorb_err_NotAMap;
318     
319     for (ix=0; ix<map->numchunks; ix++) {
320         giblorb_chunkdesc_t *chu = &(map->chunks[ix]);
321         if (chu->ptr) {
322             giblorb_free(chu->ptr);
323             chu->ptr = NULL;
324         }
325     }
326     
327     if (map->chunks) {
328         giblorb_free(map->chunks);
329         map->chunks = NULL;
330     }
331     
332     map->numchunks = 0;
333     
334     if (map->resources) {
335         giblorb_free(map->resources);
336         map->resources = NULL;
337     }
338     
339     if (map->ressorted) {
340         giblorb_free(map->ressorted);
341         map->ressorted = NULL;
342     }
343     
344     map->numresources = 0;
345     
346     map->file = NULL;
347     map->inited = 0;
348     
349     giblorb_free(map);
350
351     return giblorb_err_None;
352 }
353
354 /* Chunk-handling functions. */
355
356 giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map, 
357     glui32 method, giblorb_result_t *res, glui32 type, 
358     glui32 count)
359 {
360     int ix;
361     
362     for (ix=0; ix < map->numchunks; ix++) {
363         if (map->chunks[ix].type == type) {
364             if (count == 0)
365                 break;
366             count--;
367         }
368     }
369     
370     if (ix >= map->numchunks) {
371         return giblorb_err_NotFound;
372     }
373     
374     return giblorb_load_chunk_by_number(map, method, res, ix);
375 }
376
377 giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map, 
378     glui32 method, giblorb_result_t *res, glui32 chunknum)
379 {
380     giblorb_chunkdesc_t *chu;
381     
382     if (chunknum < 0 || chunknum >= map->numchunks)
383         return giblorb_err_NotFound;
384
385     chu = &(map->chunks[chunknum]);
386     
387     switch (method) {
388     
389         case giblorb_method_DontLoad:
390             /* do nothing */
391             break;
392             
393         case giblorb_method_FilePos:
394             res->data.startpos = chu->datpos;
395             break;
396             
397         case giblorb_method_Memory:
398             if (!chu->ptr) {
399                 giblorb_err_t err;
400                 glui32 readlen;
401                 void *dat = giblorb_malloc(chu->len);
402                 
403                 if (!dat)
404                     return giblorb_err_Alloc;
405                 
406                 glk_stream_set_position(map->file, chu->datpos, 
407                     seekmode_Start);
408                 
409                 readlen = glk_get_buffer_stream(map->file, dat, 
410                     chu->len);
411                 if (readlen != chu->len)
412                     return giblorb_err_Read;
413                 
414                 chu->ptr = dat;
415             }
416             res->data.ptr = chu->ptr;
417             break;
418     }
419     
420     res->chunknum = chunknum;
421     res->length = chu->len;
422     res->chunktype = chu->type;
423     
424     return giblorb_err_None;
425 }
426
427 giblorb_err_t giblorb_load_resource(giblorb_map_t *map, glui32 method, 
428     giblorb_result_t *res, glui32 usage, glui32 resnum)
429 {
430     giblorb_resdesc_t sample;
431     giblorb_resdesc_t *found;
432     
433     sample.usage = usage;
434     sample.resnum = resnum;
435     
436     found = giblorb_bsearch(&sample, map->ressorted, map->numresources);
437     
438     if (!found)
439         return giblorb_err_NotFound;
440     
441     return giblorb_load_chunk_by_number(map, method, res, found->chunknum);
442 }
443
444 giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, glui32 chunknum)
445 {
446     giblorb_chunkdesc_t *chu;
447     
448     if (chunknum < 0 || chunknum >= map->numchunks)
449         return giblorb_err_NotFound;
450
451     chu = &(map->chunks[chunknum]);
452     
453     if (chu->ptr) {
454         giblorb_free(chu->ptr);
455         chu->ptr = NULL;
456     }
457     
458     return giblorb_err_None;
459 }
460
461 giblorb_err_t giblorb_count_resources(giblorb_map_t *map, glui32 usage,
462     glui32 *num, glui32 *min, glui32 *max)
463 {
464     int ix;
465     int count;
466     glui32 val;
467     glui32 minval, maxval;
468     
469     count = 0;
470     minval = 0;
471     maxval = 0;
472     
473     for (ix=0; ix<map->numresources; ix++) {
474         if (map->resources[ix].usage == usage) {
475             val = map->resources[ix].resnum;
476             if (count == 0) {
477                 count++;
478                 minval = val;
479                 maxval = val;
480             }
481             else {
482                 count++;
483                 if (val < minval)
484                     minval = val;
485                 if (val > maxval)
486                     maxval = val;
487             }
488         }
489     }
490     
491     if (num)
492         *num = count;
493     if (min)
494         *min = minval;
495     if (max)
496         *max = maxval;
497     
498     return giblorb_err_None;
499 }
500
501 /* Sorting and searching. */
502
503 static int sortsplot(giblorb_resdesc_t *v1, giblorb_resdesc_t *v2)
504 {
505     if (v1->usage < v2->usage)
506         return -1;
507     if (v1->usage > v2->usage)
508         return 1;
509     if (v1->resnum < v2->resnum)
510         return -1;
511     if (v1->resnum > v2->resnum)
512         return 1;
513     return 0;
514 }
515
516 static void giblorb_qsort(giblorb_resdesc_t **list, int len)
517 {
518     int ix, jx, res, val;
519     giblorb_resdesc_t *tmpptr, *pivot;
520     
521     if (len < 6) {
522         /* The list is short enough for a bubble-sort. */
523         for (jx=len-1; jx>0; jx--) {
524             for (ix=0; ix<jx; ix++) {
525                 res = sortsplot(list[ix], list[ix+1]);
526                 if (res > 0) {
527                     tmpptr = list[ix];
528                     list[ix] = list[ix+1];
529                     list[ix+1] = tmpptr;
530                 }
531             }
532         }
533     }
534     else {
535         /* Split the list. */
536         pivot = list[len/2];
537         ix=0;
538         jx=len;
539         while (1) {
540             while (ix < jx-1 && sortsplot(list[ix], pivot) < 0)
541                 ix++;
542             while (ix < jx-1 && sortsplot(list[jx-1], pivot) > 0)
543                 jx--;
544             if (ix >= jx-1)
545                 break;
546             tmpptr = list[ix];
547             list[ix] = list[jx-1];
548             list[jx-1] = tmpptr;
549         }
550         ix++;
551         /* Sort the halves. */
552         giblorb_qsort(list+0, ix);
553         giblorb_qsort(list+ix, len-ix);
554     }
555 }
556
557 giblorb_resdesc_t *giblorb_bsearch(giblorb_resdesc_t *sample, 
558     giblorb_resdesc_t **list, int len)
559 {
560     int top, bot, val, res;
561     
562     bot = 0;
563     top = len;
564     
565     while (bot < top) {
566         val = (top+bot) / 2;
567         res = sortsplot(list[val], sample);
568         if (res == 0)
569             return list[val];
570         if (res < 0) {
571             bot = val+1;
572         }
573         else {
574             top = val;
575         }
576     }
577     
578     return NULL;
579 }
580
581
582 /* Boring utility functions. If your platform doesn't support ANSI 
583     malloc(), feel free to edit these however you like. */
584
585 #include <stdlib.h> /* The OS-native header file -- you can edit 
586     this too. */
587
588 static void *giblorb_malloc(glui32 len)
589 {
590     return malloc(len);
591 }
592
593 static void *giblorb_realloc(void *ptr, glui32 len)
594 {
595     return realloc(ptr, len);
596 }
597
598 static void giblorb_free(void *ptr)
599 {
600     free(ptr);
601 }
602
603