/* * ============================================================================ * RES_FILE: Resource File Manipulation Routines * * Author: J. Zbiciak * Last Revision: 8/23/98 * ============================================================================ * * Exported functionality implemented in this file: * * RES_ENCODE -- encodes a record into external storage format * RES_DECODE -- decodes a record into internal storage format * RES_TYPE_NAME -- returns a human-readable name for a type number * RES_CLEAR_ERR -- clears any errors that are in a res_state_t structure. * RES_FIND_REC -- find named record in an archive (not hierarchially) * RES_INIT_ARCH -- initialize an archive record * RES_REPORT_ERR -- Adds an instance of "error" to a res_state_t. * * Internal helper functionality: * * ARCH_DECODE -- Decodes an archive resource. * ENCODE_ARCH_HDR -- Encodes an archive header * DECODE_ARCH_HDR -- Decodes an archive header * * Internal helper macros: * * HDR_LEN_ARCH -- Archive main header length * HDR_LEN_REC -- Resource record header length * * ============================================================================ */ #include "config.h" #include /* for sprintf() */ #include /* for NULL */ #include /* for size_t */ #include /* for memcpy(), strncpy() */ #include "res_file.h" /* for type definitions */ #include "data_fmt.h" /* for data format conversions */ #include "macros.h" /* for pack/unpack macros */ /* * ============================================================================ * ============================================================================ * NON-EXPORTED FUNCTION PROTOTYPES AND FILE-LEVEL MACROS * ============================================================================ * ============================================================================ */ static int arch_decode (res_rec_t *rec, unsigned decode_mask, int (*enc)(res_rec_t*), int (*dec)(res_rec_t*)); static void encode_arch_hdr (void *raw, res_arch_t *header); static void decode_arch_hdr (void *raw, res_arch_t *header); static void encode_rec_hdr (void *raw, res_rec_t *header); static void decode_rec_hdr (void *raw, res_rec_t *header); #define HDR_LEN_ARCH (49) #define HDR_LEN_REC (31) #define HDR_LEN_IMG (8) /* * ============================================================================ * ============================================================================ * EXPORTED FUNCTION DEFINITIONS * ============================================================================ * ============================================================================ */ /* * ============================================================================ * RES_ENCODE -- Converts a record to external storage format * * This is a general purpose function that acts on the type and state * of a record, calling internal routines to perform the encode if * necessary. * * Returns values: * 0 -- record successfully encoded (or was already encoded) * -1 -- an error occured during encoding (record->state has details) * ============================================================================ */ int /* Return code: 0 or -1 */ res_encode ( res_rec_t *record /* Record containing resource to encode */ ) { res_malloc_t l_malloc; res_free_t l_free; /* --------------------------------------------------------------------- */ /* Initial sanity check. Do not proceed if it fails. */ /* --------------------------------------------------------------------- */ if (!record || !record->lib_state || !record->lib_state->malloc || !record->lib_state->free) return -1; l_malloc = record->lib_state->malloc; l_free = record->lib_state->free; /* --------------------------------------------------------------------- */ /* If record is already encoded, just return "success." */ /* --------------------------------------------------------------------- */ if (record->rec_state == RES_s_encoded) return 0; /* --------------------------------------------------------------------- */ /* Choose action based on type of record. If we have a special */ /* encoder function registered, call it instead of using our built-in */ /* defaults. We ignore the special encoder/decoder if this is an */ /* archive record. */ /* --------------------------------------------------------------------- */ if (record->enc && record->type != RES_t_archive) { if (record->enc(record)) { res_report_err(record, RES_e_special_enc); return -1; } } else switch (record->type) { /* ----------------------------------------------------------------- */ /* For these types, we don't have a default encoder, so we should */ /* not be in the decoded state unless the user registered a */ /* special encoder. So, we report RES_e_inconsistent. */ /* ----------------------------------------------------------------- */ case RES_t_raw: case RES_t_html: case RES_t_palette: case RES_t_audio: case RES_t_pcx: case RES_t_unknown: { res_report_err(record, RES_e_inconsistent); return -1; } /* ----------------------------------------------------------------- */ /* The image and font types are both encoded identically, so just */ /* manually call out to the RLE-encode function on the resource. */ /* ----------------------------------------------------------------- */ case RES_t_image: case RES_t_font: { if (fmt_rle_encode(record)) return -1; break; } /* ----------------------------------------------------------------- */ /* The archive type is special -- we need to iteratively encode */ /* its contents. We do that by iterating over the resource list, */ /* calling ourselves for every resource contained therein. */ /* ----------------------------------------------------------------- */ case RES_t_archive: { res_rec_t *r, *p; int i; size_t tot_size = 0; char *raw, *data; /* ------------------------------------------------------------- */ /* Iterate over all records contained in archive, encoding */ /* each resource. Tally up the total number of encoded bytes. */ /* Immediately abort if we encounter an error. */ /* ------------------------------------------------------------- */ tot_size = HDR_LEN_ARCH; for (i = 0, r = record->res.arch->res_rec; r; r = r->next, i++) { if (r->lib_state != record->lib_state) { res_report_err(record, RES_e_inconsistent); return -1; } if (res_encode(r)) return -1; tot_size += HDR_LEN_REC + r->len; } /* ------------------------------------------------------------- */ /* Double-check count stored in archive resource. */ /* ------------------------------------------------------------- */ if (i != record->res.arch->count) { res_report_err(record, RES_e_inconsistent); return -1; } /* ------------------------------------------------------------- */ /* Paste together all of the memory images into one, encoding */ /* headers and freeing items along the way. */ /* ------------------------------------------------------------- */ raw = data = l_malloc(tot_size); if (!raw) { res_report_err(record, RES_e_no_mem); return -1; } encode_arch_hdr(raw, record->res.arch); raw += HDR_LEN_ARCH; for (r = record->res.arch->res_rec; r; ) { /* --------------------------------------------------------- */ /* Update the offset field on this resource if it was set */ /* before. This technically isn't necessary, but I am */ /* being rightfully paranoid. :-) */ /* --------------------------------------------------------- */ if (r->ofs) r->ofs = (unsigned) (raw - data); /* --------------------------------------------------------- */ /* Encode the header, and then paste raw data after it. */ /* --------------------------------------------------------- */ encode_rec_hdr(raw, r); raw += HDR_LEN_REC; memcpy(raw, r->res.data, r->len); raw += r->len; /* --------------------------------------------------------- */ /* Free the raw resource as it is no longer needed. */ /* --------------------------------------------------------- */ p = r; r = r->next; l_free(p->res.data); l_free(p); } record->len = tot_size; record->res.data = data; break; } /* ----------------------------------------------------------------- */ /* Any other types are invalid and should be flagged as errors. */ /* ----------------------------------------------------------------- */ default: { res_report_err(record, RES_e_invalid_type); return -1; } } /* --------------------------------------------------------------------- */ /* Set our state to "encoded" and report SUCCESS! */ /* --------------------------------------------------------------------- */ record->rec_state = RES_s_encoded; return 0; } /* * ============================================================================ * RES_DECODE -- Converts a record to internal storage format * * This is a general purpose function that acts on the type and state * of a record, calling internal routines to perform the decode if * necessary. The "decode_mask" is a bit mask which designates what types * we're allowed to decode. If the "RES_t_archive" type is set in the * decode mask, along with the special bit "RES_RECURSE", then we will * recursively process all resource archives encountered. * * Note: Unlike the res_encode routine, this routine will not simply * exit if it is already marked "decoded". If the record contains a * resource archive and the decode mask is set for recursion (see above), * then all enclosed resources are decoded recursively, to ensure that * they're all decoded. This allows easy decoding of a resource archive * that was not fully decoded by a previous call, without having to * re-encode. Otherwise, the behavior is the same. * * If some resources need a special decoder function because res_file * doesn't have a built-in default for the type (eg. audio files, * ROM files in 8+2 format, etc.), it is recommended to call this function * with a special encoder and decoder pointer, with only the particular * resource type's bit set in the decode mask. Special case: It is * safe to set the RES_t_archive's bit in the decode_mask, since the * special encoder and decoder functions are IGNORED for archive records. * * If special encoders/decoders are not being registered, then their values * should be given as NULL. If a special decoder is given, but no special * encoder, then the resource cannot be re-encoded, and any attempt to * do so will yield RES_e_inconsistent. * * Returns values: * 0 -- record successfully decoded (or was already decoded) * -1 -- an error occured during decoding (record->state has details) * ============================================================================ */ int /* Return code: 0 or -1 */ res_decode ( res_rec_t *record, /* Record containing resource to decode */ unsigned decode_mask, /* Bitmask of what types to decode */ int (*enc)(res_rec_t*), /* Special encode function (if any) */ int (*dec)(res_rec_t*) /* Special decode function (if any) */ ) { int decoded = 0; /* --------------------------------------------------------------------- */ /* Initial sanity check. Do not proceed if it fails. */ /* --------------------------------------------------------------------- */ if (!record || !record->lib_state || !record->lib_state->malloc || !record->lib_state->free) return -1; /* --------------------------------------------------------------------- */ /* If record is already decoded, just return "success." */ /* Exception: If record is an archive, and the recurse flag is set, */ /* don't return. */ /* --------------------------------------------------------------------- */ if ( record->rec_state == RES_s_decoded && (record->type != RES_t_archive || !(decode_mask & MASK_BIT(RES_t_archive))) ) return 0; /* --------------------------------------------------------------------- */ /* Choose action based on type of record. If this record isn't in our */ /* allowed decode type mask, then don't decode this record. If we */ /* a special encoder/decoder and this is not an archive type, register */ /* these functions and use the special decoder to decode this record. */ /* Otherwise, use our defaults. */ /* --------------------------------------------------------------------- */ if ( (MASK_BIT(record->type) & decode_mask) == 0 ) return 0; if (record->type != RES_t_archive) { record->dec = dec; record->enc = enc; } else { record->dec = NULL; record->enc = NULL; } if (record->dec) { if (record->dec(record)) { res_report_err(record, RES_e_special_dec); return -1; } } else switch (record->type) { /* ----------------------------------------------------------------- */ /* For these types, we cannot actually decode them. So, if the */ /* caller asked us to decode them, just ignore the caller and */ /* leave these in the "encoded" state. */ /* ----------------------------------------------------------------- */ case RES_t_raw: case RES_t_html: case RES_t_palette: case RES_t_audio: case RES_t_pcx: case RES_t_unknown: { decoded = 0; break; } /* ----------------------------------------------------------------- */ /* The archive type requires the greatest amount of work. Call */ /* the external helper function to actually perform the decode. */ /* ----------------------------------------------------------------- */ case RES_t_archive: { if (arch_decode(record, decode_mask, enc, dec)) return -1; decoded = 1; break; } /* ----------------------------------------------------------------- */ /* The image and font types are both encoded identically, so just */ /* manually call out to the RLE-decode function on the resource. */ /* ----------------------------------------------------------------- */ case RES_t_image: case RES_t_font: { if (fmt_rle_decode(record)) return -1; decoded = 1; break; } /* ----------------------------------------------------------------- */ /* Any other types are invalid and should be flagged as errors. */ /* ----------------------------------------------------------------- */ default: { res_report_err(record, RES_e_invalid_type); return -1; } } /* --------------------------------------------------------------------- */ /* Set our state to "decoded" and report SUCCESS! */ /* --------------------------------------------------------------------- */ if (decoded) record->rec_state = RES_s_decoded; return 0; } /* * ============================================================================ * RES_NAME_TYPE -- returns a name for a type number * * Returns one of a set of pre-defined type names for known types, or a * string of the form "Unknown 0xXXXX" for unknown type numbers. * ============================================================================ */ static const char *res_type_names[] = { "Raw Data", "Resource Archive", "RLE-encoded Image", "RLE-encoded Font", "Palette", "Sound", "PCX Image", "HTML" }; static char res_unknown_buf[16]; const char * /* Pointer to resource name */ res_type_name ( res_type_t type /* Resource type name */ ) { /* --------------------------------------------------------------------- */ /* If this is a known type, go ahead and look it up in our table. */ /* --------------------------------------------------------------------- */ if ((int)type >= 0 && (int)type < (int)RES_t_unknown) { return res_type_names[(int)type]; } /* --------------------------------------------------------------------- */ /* Otherwise, construct a string containing the "Unknown 0xXXXX" name. */ /* Note: This sprintf is safe, because the string length is bounded. */ /* --------------------------------------------------------------------- */ sprintf(res_unknown_buf,"Unknown 0x%.4X", ((int)type) & 0xFFFF); res_unknown_buf[15] = 0; return res_unknown_buf; } /* * ============================================================================ * RES_FIND_REC -- find named record in an archive (not hierarchially) * ============================================================================ */ res_rec_t * /* Resource record (if found) */ res_find_rec ( res_rec_t *archive, /* Archive to search in */ const char *name /* Resource name to look for */ ) { /* --------------------------------------------------------------------- */ /* XXX: THIS IS NOT YET IMPLEMENTED BUT NEEDS TO BE!!! */ /* --------------------------------------------------------------------- */ res_report_err(archive, RES_e_not_impl); (void)name; return NULL; } /* * ============================================================================ * RES_CLEAR_ERR -- clears any errors that are in a res_state_t structure. * ============================================================================ */ int /* Return code: 0 or -1 */ res_clear_err ( res_rec_t *record /* Record to reset */ ) { res_state_t *s; int i; /* --------------------------------------------------------------------- */ /* Sanity check: Make sure I can do this! */ /* --------------------------------------------------------------------- */ if (!record || !record->lib_state) return -1; /* --------------------------------------------------------------------- */ /* Set the error count to 0 and mark all error slots as e_ok */ /* --------------------------------------------------------------------- */ s = record->lib_state; s->num_err = 0; for (i = 0; i < RES_MAXERR; i++) { strncpy(s->err_msg[i], "No error detected.", RES_ERRMSGLEN - 1); s->err_msg [i][RES_ERRMSGLEN-1] = 0; s->err_list[i] = RES_e_ok; } return 0; } /* * ============================================================================ * RES_INIT_REC -- initialize an archive record * * This accepts a pointer to an uninitialized record structure and sets * up the "lib_state" structure within it. This init function allocates the * res_state_t for the caller using the provided malloc function. * * The calling program can pass in NULL pointers for the malloc, realloc, * and free functions, if it wishes to rely on the standard library's * implementation. * ============================================================================ */ int /* Return code: 0 or -1 */ res_init_rec ( res_rec_t *record, /* Record to initialize */ res_malloc_t malloc_func, /* malloc function to use for archive */ res_realloc_t realloc_func, /* realloc function to use for archive */ res_free_t free_func /* free function to use for archive */ ) { /* --------------------------------------------------------------------- */ /* Sanity check: Make sure I at least have a pointer to a record. */ /* --------------------------------------------------------------------- */ if (!record) return -1; /* --------------------------------------------------------------------- */ /* Set up function pointers if they are NULL. */ /* --------------------------------------------------------------------- */ if (!malloc_func) malloc_func = (res_malloc_t ) malloc; if (!realloc_func) realloc_func = (res_realloc_t) realloc; if (!free_func) free_func = (res_free_t ) free; /* --------------------------------------------------------------------- */ /* Allocate a res_state_t and register it as the lib_state structure. */ /* --------------------------------------------------------------------- */ record->lib_state = malloc_func(sizeof(res_state_t)); if (!record->lib_state) return -1; record->lib_state->malloc = malloc_func; record->lib_state->realloc = realloc_func; record->lib_state->free = free_func; /* --------------------------------------------------------------------- */ /* Do some minor clearing of fields, and return SUCCESS! */ /* --------------------------------------------------------------------- */ record->rec_state = RES_s_notpresent; record->next = NULL; res_clear_err(record); return 0; } /* * ============================================================================ * RES_REPORT_ERR -- Adds an instance of "error" to a res_state_t. * * If the "error" is an unknown error number, then the message "Unknown * error" is given instead, and the error number is set to "RES_e_unknown". * This code is very specific to the number codes assigned in the enum. * ============================================================================ */ static const char * res_err_msgs[] = { "Unknown error!", "Too many errors!", "No error detected", "Out of memory", "Input buffer underflowed", "Output buffer overflowed", "Invalid file format", "Data structure is inconsistent", "RLE decoder error", "Invalid resource type", "Function not implemented", }; void res_report_err ( res_rec_t *record, res_err_t error ) { res_state_t *s; int msg_num; if (!record || !record->lib_state) return; s = record->lib_state; if (s->num_err >= RES_MAXERR) { error = RES_e_overflow; s->num_err = RES_MAXERR-1; } if ((int)error < -1 || (int)error > 8) error = RES_e_unknown; msg_num = (int)error + 2; strncpy(s->err_msg[s->num_err], res_err_msgs[msg_num], RES_ERRMSGLEN - 1); if (record->ident[0]) { strncat(s->err_msg[s->num_err],": '", RES_ERRMSGLEN - 1); strncat(s->err_msg[s->num_err],record->ident,RES_ERRMSGLEN - 1); strncat(s->err_msg[s->num_err],"'", RES_ERRMSGLEN - 1); } s->err_msg [s->num_err][RES_ERRMSGLEN - 1] = 0; s->err_list[s->num_err] = error; s->num_err++; return; } /* * ============================================================================ * ============================================================================ * NON-EXPORTED FUNCTION DEFINITIONS * ============================================================================ * ============================================================================ */ /* * ============================================================================ * ARCH_DECODE -- Decodes an archive resource. * * This helper routine actually steps through an archive resource pulling * out its bits and pieces. If the recurse flag is set, each of the * resources contained in this archive are themselves decoded. * ============================================================================ */ static int arch_decode ( res_rec_t *record, unsigned decode_mask, int (*enc)(res_rec_t*), /* Special encode function (if any) */ int (*dec)(res_rec_t*) /* Special decode function (if any) */ ) { res_malloc_t l_malloc; res_free_t l_free; res_rec_t *p, *r; res_arch_t *archive = NULL; int i; size_t ofs; l_malloc = record->lib_state->malloc; l_free = record->lib_state->free; /* --------------------------------------------------------------------- */ /* First, determine if we need to decode this archive record or not. */ /* --------------------------------------------------------------------- */ if (record->rec_state == RES_s_decoded) { if (! (decode_mask & RES_RECURSE) ) { /* ------------------------------------------------------------- */ /* If it's not encoded and we're not recursing, stop now. */ /* ------------------------------------------------------------- */ return 0; } archive = record->res.arch; } /* --------------------------------------------------------------------- */ /* If we need to decode the archive itself, then do so now. */ /* --------------------------------------------------------------------- */ if (record->rec_state == RES_s_encoded) { ofs = 0; if (ofs + HDR_LEN_ARCH > record->len) { res_report_err(record, RES_e_underflow); return -1; } archive = l_malloc(sizeof(res_arch_t)); if (!archive) { res_report_err(record, RES_e_no_mem); return -1; } decode_arch_hdr(record->res.data,archive); archive->res_rec = p = NULL; ofs = HDR_LEN_ARCH; for (i = 0; i < archive->count; i++) { if (ofs + HDR_LEN_REC > record->len) { res_report_err(record, RES_e_underflow); goto abort; } r = l_malloc(sizeof(res_rec_t)); if (!r) goto abort_no_mem; decode_rec_hdr((char *)record->res.data + ofs, r); ofs += HDR_LEN_REC; if (ofs + r->dist > record->len) { l_free(r); res_report_err(record, RES_e_underflow); goto abort; } r->next = NULL; r->lib_state = record->lib_state; r->rec_state = RES_s_encoded; r->res.data = l_malloc(r->len); if (!r->res.data) { l_free(r); goto abort_no_mem; } memcpy(r->res.data, (char *)record->res.data + ofs, r->len); ofs += r->dist; if (!p) archive->res_rec = r; else p->next = r; p = r; } } /* --------------------------------------------------------------------- */ /* Now, if we're set to recurse, go over each of the resources in this */ /* archive and decode them. */ /* --------------------------------------------------------------------- */ if (decode_mask & RES_RECURSE) for (r = archive->res_rec; r; r = r->next) { if (r->rec_state == RES_s_encoded) if (res_decode(r, decode_mask, enc, dec)) return -1; } /* --------------------------------------------------------------------- */ /* Return SUCCESS! */ /* --------------------------------------------------------------------- */ record->res.arch = archive; record->rec_state = RES_s_decoded; return 0; abort_no_mem: res_report_err(record, RES_e_no_mem); abort: r = archive->res_rec; while (r) { p = r; r = r->next; if (p->res.data) l_free(p->res.data); l_free(p); } l_free(archive); return -1; } /* * ============================================================================ * ENCODE_ARCH_HDR -- encodes an archive header * DECODE_ARCH_HDR -- decodes an archive header * * Archive Header Format: * * Offset Length Format Description * ------ ------ ------ -------------------------------------------- * 0 13 String Identification string -- always "Resource" * 13 2 Word Type (?) -- always 0x0000 * 15 2 Word Number of resources * 17 32 n/a Unused (?) -- always zeroes * ============================================================================ */ static void encode_arch_hdr(void *raw, res_arch_t *header) { PACK_STR(raw, 0, header->ident ); PACK_I16(raw, 13, header->state ); PACK_I16(raw, 15, header->count ); ZERO_RAW(raw, 17, 32); } static void decode_arch_hdr(void *raw, res_arch_t *header) { UNPACK_STR(raw, 0, header->ident , 13); UNPACK_I16(raw, 13, header->state ); UNPACK_I16(raw, 15, header->count ); if (1) { int i, j; for (i = j = 0; i < 32; i++) j += !!((char*)raw)[17 + i]; if (j) { printf("%13s :",header->ident); for (i = 0; i < 32; i++) printf("%.2X",((unsigned char*)raw)[17 + i]); putchar('\n'); fflush(stdout); } } } /* * ============================================================================ * ENCODE_REC_HDR -- encodes a resource record header * DECODE_REC_HDR -- decodes a resource record header * * Resource Record Header Format: * * Offset Length Format Description * ------ ------ ------ -------------------------------------------- * 0 13 String Name of resource * 13 2 Word Resource type (see type table below) * 15 4 Long Length of resource in bytes * 19 4 Long Distance until next resource * 23 4 Long Offset into archive (should be ignored) * 27 8 n/a Unused. * ============================================================================ */ static void encode_rec_hdr (void *raw, res_rec_t *header) { PACK_STR(raw, 0, header->ident ); PACK_I16(raw, 13, header->type ); PACK_I32(raw, 15, header->len ); PACK_I32(raw, 19, header->len ); /* I always write packed archives */ PACK_I32(raw, 23, header->ofs ); ZERO_RAW(raw, 27, 8); } static void decode_rec_hdr (void *raw, res_rec_t *header) { UNPACK_STR(raw, 0, header->ident , 13); UNPACK_I16(raw, 13, header->type ); UNPACK_I32(raw, 15, header->len ); UNPACK_I32(raw, 19, header->dist ); UNPACK_I32(raw, 23, header->ofs ); } /* * ============================================================================ * Copyright (c) 1998, Joseph Zbiciak. * ============================================================================ */