1 /* gi_blorb.c: Blorb library layer for Glk API.
3 Designed by Andrew Plotkin <erkyrath@eblong.com>
4 http://www.eblong.com/zarf/glk/index.html
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
28 /* The magic macro of endian conversion. */
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))
36 /* More four-byte constants. */
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'))
42 /* giblorb_chunkdesc_t: Describes one chunk of the Blorb file. */
43 typedef struct giblorb_chunkdesc_struct {
46 glui32 startpos; /* start of chunk header */
47 glui32 datpos; /* start of data (either startpos or startpos+8) */
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; */
53 } giblorb_chunkdesc_t;
55 /* giblorb_resdesc_t: Describes one resource in the Blorb file. */
56 typedef struct giblorb_resdesc_struct {
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
69 giblorb_chunkdesc_t *chunks; /* list of chunk descriptors */
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. */
77 #define giblorb_Inited_Magic (0xB7012BED)
79 /* Static variables. */
81 static int lib_inited = FALSE;
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);
92 static giblorb_err_t giblorb_initialize()
94 return giblorb_err_None;
97 giblorb_err_t giblorb_create_map(strid_t file, giblorb_map_t **newmap)
102 glui32 nextpos, totallength;
103 giblorb_chunkdesc_t *chunks;
104 int chunks_size, numchunks;
110 err = giblorb_initialize();
116 /* First, chew through the file and index the chunks. */
118 glk_stream_set_position(file, 0, seekmode_Start);
120 readlen = glk_get_buffer_stream(file, buffer, 12);
122 return giblorb_err_Read;
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;
129 totallength = giblorb_native4(buffer+4) + 8;
134 chunks = (giblorb_chunkdesc_t *)giblorb_malloc(sizeof(giblorb_chunkdesc_t)
137 while (nextpos < totallength) {
140 giblorb_chunkdesc_t *chu;
142 glk_stream_set_position(file, nextpos, seekmode_Start);
144 readlen = glk_get_buffer_stream(file, buffer, 8);
146 return giblorb_err_Read;
148 type = giblorb_native4(buffer+0);
149 len = giblorb_native4(buffer+4);
151 if (numchunks >= chunks_size) {
153 chunks = (giblorb_chunkdesc_t *)giblorb_realloc(chunks,
154 sizeof(giblorb_chunkdesc_t) * chunks_size);
158 chu = &(chunks[chunum]);
162 chu->startpos = nextpos;
163 if (type == giblorb_ID_FORM) {
164 chu->datpos = nextpos;
168 chu->datpos = nextpos+8;
174 nextpos = nextpos + len + 8;
178 if (nextpos > totallength)
179 return giblorb_err_Format;
182 /* The basic IFF structure seems to be ok, and we have a list of
183 chunks. Now we allocate the map structure itself. */
185 map = (giblorb_map_t *)giblorb_malloc(sizeof(giblorb_map_t));
187 giblorb_free(chunks);
188 return giblorb_err_Alloc;
191 map->inited = giblorb_Inited_Magic;
193 map->chunks = chunks;
194 map->numchunks = numchunks;
195 map->resources = NULL;
196 map->ressorted = NULL;
197 map->numresources = 0;
198 /*map->releasenum = 0;
200 map->resolution = NULL;
201 map->palettechunk = -1;
203 map->auxsound = NULL;
204 map->auxpict = NULL;*/
206 /* Now we do everything else involved in loading the Blorb file,
207 such as building resource lists. */
209 err = giblorb_initialize_map(map);
211 giblorb_destroy_map(map);
216 return giblorb_err_None;
219 static giblorb_err_t giblorb_initialize_map(giblorb_map_t *map)
221 /* It is important that the map structure be kept valid during this
222 function. If this returns an error, giblorb_destroy_map() will
226 giblorb_result_t chunkres;
232 int gotindex = FALSE;
234 for (ix=0; ix<map->numchunks; ix++) {
235 giblorb_chunkdesc_t *chu = &map->chunks[ix];
239 case giblorb_ID_RIdx:
240 /* Resource index chunk: build the resource list and
244 return giblorb_err_Format; /* duplicate index chunk */
245 err = giblorb_load_chunk_by_number(map, giblorb_method_Memory,
250 ptr = chunkres.data.ptr;
251 len = chunkres.length;
252 numres = giblorb_native4(ptr+0);
256 giblorb_resdesc_t *resources;
257 giblorb_resdesc_t **ressorted;
259 if (len != numres*12+4)
260 return giblorb_err_Format; /* bad length field */
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;
270 for (jx=0; jx<numres; jx++) {
271 giblorb_resdesc_t *res = &(resources[jx]);
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);
278 while (ix2 < map->numchunks
279 && map->chunks[ix2].startpos < respos)
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 */
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);
297 map->numresources = numres;
298 map->resources = resources;
299 map->ressorted = ressorted;
302 giblorb_unload_chunk(map, ix);
309 return giblorb_err_None;
312 giblorb_err_t giblorb_destroy_map(giblorb_map_t *map)
316 if (!map || !map->chunks || map->inited != giblorb_Inited_Magic)
317 return giblorb_err_NotAMap;
319 for (ix=0; ix<map->numchunks; ix++) {
320 giblorb_chunkdesc_t *chu = &(map->chunks[ix]);
322 giblorb_free(chu->ptr);
328 giblorb_free(map->chunks);
334 if (map->resources) {
335 giblorb_free(map->resources);
336 map->resources = NULL;
339 if (map->ressorted) {
340 giblorb_free(map->ressorted);
341 map->ressorted = NULL;
344 map->numresources = 0;
351 return giblorb_err_None;
354 /* Chunk-handling functions. */
356 giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map,
357 glui32 method, giblorb_result_t *res, glui32 type,
362 for (ix=0; ix < map->numchunks; ix++) {
363 if (map->chunks[ix].type == type) {
370 if (ix >= map->numchunks) {
371 return giblorb_err_NotFound;
374 return giblorb_load_chunk_by_number(map, method, res, ix);
377 giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map,
378 glui32 method, giblorb_result_t *res, glui32 chunknum)
380 giblorb_chunkdesc_t *chu;
382 if (chunknum < 0 || chunknum >= map->numchunks)
383 return giblorb_err_NotFound;
385 chu = &(map->chunks[chunknum]);
389 case giblorb_method_DontLoad:
393 case giblorb_method_FilePos:
394 res->data.startpos = chu->datpos;
397 case giblorb_method_Memory:
401 void *dat = giblorb_malloc(chu->len);
404 return giblorb_err_Alloc;
406 glk_stream_set_position(map->file, chu->datpos,
409 readlen = glk_get_buffer_stream(map->file, dat,
411 if (readlen != chu->len)
412 return giblorb_err_Read;
416 res->data.ptr = chu->ptr;
420 res->chunknum = chunknum;
421 res->length = chu->len;
422 res->chunktype = chu->type;
424 return giblorb_err_None;
427 giblorb_err_t giblorb_load_resource(giblorb_map_t *map, glui32 method,
428 giblorb_result_t *res, glui32 usage, glui32 resnum)
430 giblorb_resdesc_t sample;
431 giblorb_resdesc_t *found;
433 sample.usage = usage;
434 sample.resnum = resnum;
436 found = giblorb_bsearch(&sample, map->ressorted, map->numresources);
439 return giblorb_err_NotFound;
441 return giblorb_load_chunk_by_number(map, method, res, found->chunknum);
444 giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, glui32 chunknum)
446 giblorb_chunkdesc_t *chu;
448 if (chunknum < 0 || chunknum >= map->numchunks)
449 return giblorb_err_NotFound;
451 chu = &(map->chunks[chunknum]);
454 giblorb_free(chu->ptr);
458 return giblorb_err_None;
461 giblorb_err_t giblorb_count_resources(giblorb_map_t *map, glui32 usage,
462 glui32 *num, glui32 *min, glui32 *max)
467 glui32 minval, maxval;
473 for (ix=0; ix<map->numresources; ix++) {
474 if (map->resources[ix].usage == usage) {
475 val = map->resources[ix].resnum;
498 return giblorb_err_None;
501 /* Sorting and searching. */
503 static int sortsplot(giblorb_resdesc_t *v1, giblorb_resdesc_t *v2)
505 if (v1->usage < v2->usage)
507 if (v1->usage > v2->usage)
509 if (v1->resnum < v2->resnum)
511 if (v1->resnum > v2->resnum)
516 static void giblorb_qsort(giblorb_resdesc_t **list, int len)
518 int ix, jx, res, val;
519 giblorb_resdesc_t *tmpptr, *pivot;
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]);
528 list[ix] = list[ix+1];
535 /* Split the list. */
540 while (ix < jx-1 && sortsplot(list[ix], pivot) < 0)
542 while (ix < jx-1 && sortsplot(list[jx-1], pivot) > 0)
547 list[ix] = list[jx-1];
551 /* Sort the halves. */
552 giblorb_qsort(list+0, ix);
553 giblorb_qsort(list+ix, len-ix);
557 giblorb_resdesc_t *giblorb_bsearch(giblorb_resdesc_t *sample,
558 giblorb_resdesc_t **list, int len)
560 int top, bot, val, res;
567 res = sortsplot(list[val], sample);
582 /* Boring utility functions. If your platform doesn't support ANSI
583 malloc(), feel free to edit these however you like. */
585 #include <stdlib.h> /* The OS-native header file -- you can edit
588 static void *giblorb_malloc(glui32 len)
593 static void *giblorb_realloc(void *ptr, glui32 len)
595 return realloc(ptr, len);
598 static void giblorb_free(void *ptr)