blorblib.c: A more or less portable C library for Z-machine interpreters which want to use Blorb files. Version 1.0.2 Andrew Plotkin http://www.eblong.com/zarf/blorb/index.html I'll try to keep this simple. See blorb.h for detailed type definitions. General notes: Most of these functions return an error code (type bb_err_t, which is equivalent to int.) Always check this error code. If it's non-zero, the call failed and no other information was returned. What you do is, compile blorblib.c into your interpreter, and #include "blorb.h". Be sure to uncomment one of the ENDIAN definitions in "blorb.h". (If you get it wrong, the library will tell you when you try to use it.) When the your interpreter starts up and is given a Blorb file, you should open the file. Then call: bb_err_t err; FILE *file; /* The Blorb file you just opened */ bb_map_t *map; err = bb_create_map(file, &map); If err is zero (bb_err_None), then map now points to an (opaque) structure which describes the Blorb file. You will use this value in all future blorblib calls. (It's ok to have more than one Blorb file open at a time; you get different bb_map_t pointers.) When you are finished with the Blorb file, call err = bb_destroy_map(map); to destroy the map structure and all its data (including data chunks it has loaded.) After bb_destroy_map() returns, you can fclose(file). Do *not* close the file while the map structure still exists. blorblib relies on the open file to load resources. There are three vaguely-defined layers in this API. I'm not going to go to the trouble of insulating you from the lower layers -- you can't get *too* confused -- but it's generally easier to use the higher-level calls rather than the lower level. All three layers use a similar protocol to load in data. You call a load function, and pass in a method constant, and a pointer to a bb_result_t structure. The library fills in the bb_result_t structure according to the load method you requested. #define bb_method_DontLoad (0) #define bb_method_Memory (1) #define bb_method_FilePos (2) typedef struct bb_result_struct { int chunknum; union { void *ptr; uint32 startpos; } data; uint32 length; } bb_result_t; If you request bb_method_Memory, then result.data.ptr will contain a pointer to the data you requested; result.length is the number of bytes. The library allocates this memory, so you should not write into it or try to free() it. The library is sensible about remembering what data is loaded; if you load the same chunk twice, it will not allocate two identical pieces of memory. To deallocate the memory, call bb_unload_chunk(result.chunknum). If you request bb_method_FilePos, then result.data.startpos will contain the position in the Blorb file of the data; result.length is the number of bytes. You can use fseek() and the usual stdio functions to read the data yourself. If you request bb_method_DontLoad, then result.data won't contain any meaningful data. However, result.length and result.chunknum will still be set. * The bottom layer (chunk loading): These are calls you can use to load an IFF chunk directly. bb_err_t bb_load_chunk_by_type(bb_map_t *map, int method, bb_result_t *res, uint32 chunktype, int count); This loads a chunk by its IFF chunk type. If count is zero, it loads the first chunk of that type; if 1, the second; and so on. You probably shouldn't do this to get resource chunks (sounds, images, executable data). However, it's the only way to load the optional chunks -- author, copyright, and annotation. bb_err_t bb_load_chunk_by_number(bb_map_t *map, int method, bb_result_t *res, int chunknum); This loads a chunk based on its chunknum. (Note that a chunk number is *not* the same as a resource number.) You might do this using a chunknum that you got from the result structure after a previous load. bb_err_t bb_unload_chunk(bb_map_t *map, int chunknum); This deallocates the memory allocated by a bb_method_Memory load. You would use the chunknum that you got from the result structure after the load. If no memory is allocated, this does nothing. * The middle layer (resource loading): These are calls you use to load a resource based on usage. bb_err_t bb_load_resource(bb_map_t *map, int method, bb_result_t *res, uint32 usage, int resnum); This loads a chunk based on usage (bb_ID_Snd, bb_ID_Pict, or bb_ID_Exec) and resource number. Note that a resource number is *not* the same as a chunk number. The resource number is a constant, which the game file uses to find the data. The chunk number is just an internal key in the IFF file. bb_err_t bb_count_resources(bb_map_t *map, uint32 usage, int *num, int *min, int *max); This counts how many resources there are for a particular usage, and their minimum and maximum resource numbers. The results are written into *num, *min, and *max. (Any of these pointers can be NULL if you don't care about that value.) If there are no resources of that usage, all three values will be zero. * The top layer (resource loading with extra data): These are special calls for loading sound or image resources, which also return extra information (looping flag for sounds, resolution data for images.) bb_err_t bb_load_resource_pict(bb_map_t *map, int method, bb_result_t *res, int resnum, bb_aux_pict_t **auxdata); bb_err_t bb_load_resource_snd(bb_map_t *map, int method, bb_result_t *res, int resnum, bb_aux_sound_t **auxdata); These do what they say they do. If you don't care about the extra data, pass NULL as the last argument; the result is effectively the same as bb_load_resource(). If you do care, you should declare pointers bb_aux_pict_t *auxpic; bb_aux_sound_t *auxsound; and pass &auxpict or &auxsound as the last argument. The pointer may be set to NULL (meaning no extra data for this resource), or it may be set pointing to a structure you can read. (This structure is allocated by the library, so you should not write into or free() it.) * There are also some calls for loading data which is not associated with any particular resource. You could get this data yourself with bb_load_chunk_by_type(), but these calls are easier. uint16 bb_get_release_num(bb_map_t *map); The release number of the resource file. If there is none, this returns 0. bb_zheader_t *bb_get_zheader(bb_map_t *map); The Z-code header data (which gives the serial number, release number, and checksum of the Z-code file this Blorb file is associated with.) If there is none, this returns NULL. bb_resolution_t *bb_get_resolution(bb_map_t *map); The resolution data (which gives the preferred, minimum, and maximum window size.) If there is none, this returns NULL. bb_err_t bb_get_palette(bb_map_t *map, bb_palette_t **res); The palette data. You should declare a pointer bb_palette_t *pal; and pass &pal as the second argument. If pal is set to NULL, there is no palette data. If not, you should test pal->isdirect. If pal->isdirect is TRUE, then pal->data.depth contains a preferred color depth (either 16 ot 32 bits per pixel.) If pal->isdirect is FALSE, then pal->data.table contains a list of up to 256 preferred color entries; pal->data.table.colors is an array of entries, and pal->data.table.numcolors is the length of the array. (If you're wondering why bb_get_palette() doesn't just return a pointer like bb_get_zheader() and bb_get_resolution(), it's because the palette isn't loaded until you ask for it. So there might be read or allocation errors. Ok, it's not a good reason.)