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