-/* \r
- * tads.c - Treaty of Babel common functions for tads2 and tads3 modules\r
- * \r
- * This file depends on treaty_builder.h\r
- * \r
- * This file is public domain, but note that any changes to this file may\r
- * render it noncompliant with the Treaty of Babel\r
- * \r
- * Modified\r
- *. 04/08/2006 LRRaszewski - changed babel API calls to threadsafe versions\r
- *. 04/08/2006 MJRoberts - initial implementation\r
- */\r
-\r
-\r
-#include "treaty.h"\r
-#include <ctype.h>\r
-#include <stdio.h>\r
-#include <string.h>\r
-#include <stdlib.h>\r
-#include "tads.h"\r
-#include "md5.h"\r
-\r
-#define ASSERT_OUTPUT_SIZE(x) \\r
- do { if (output_extent < (x)) return INVALID_USAGE_RV; } while (0)\r
-\r
-#define T2_SIGNATURE "TADS2 bin\012\015\032"\r
-#define T3_SIGNATURE "T3-image\015\012\032"\r
-\r
-#ifndef FALSE\r
-#define FALSE 0\r
-#endif\r
-#ifndef TRUE\r
-#define TRUE 1\r
-#endif\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * private structures \r
- */\r
-\r
-/*\r
- * resource information structure - this encapsulates the location and size\r
- * of a binary resource object embedded in a story file \r
- */\r
-typedef struct resinfo resinfo;\r
-struct resinfo\r
-{\r
- /* pointer and length of the data in the story file buffer */\r
- const char *ptr;\r
- int32 len;\r
-\r
- /* tads major version (currently, 2 or 3) */\r
- int tads_version;\r
-};\r
-\r
-/*\r
- * Name/value pair list entry \r
- */\r
-typedef struct valinfo valinfo;\r
-struct valinfo\r
-{\r
- const char *name;\r
- size_t name_len;\r
-\r
- /* value string */\r
- char *val;\r
- size_t val_len;\r
-\r
- /* next entry in the list */\r
- valinfo *nxt;\r
-};\r
-\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * forward declarations \r
- */\r
-static valinfo *parse_game_info(const void *story_file, int32 story_len,\r
- int *version);\r
-static int find_resource(const void *story_file, int32 story_len,\r
- const char *resname, resinfo *info);\r
-static int find_cover_art(const void *story_file, int32 story_len,\r
- resinfo *resp, int32 *image_format,\r
- int32 *width, int32 *height);\r
-static int t2_find_res(const void *story_file, int32 story_len,\r
- const char *resname, resinfo *info);\r
-static int t3_find_res(const void *story_file, int32 story_len,\r
- const char *resname, resinfo *info);\r
-static valinfo *find_by_key(valinfo *list_head, const char *key);\r
-static void delete_valinfo_list(valinfo *head);\r
-static int32 generate_md5_ifid(void *story_file, int32 extent,\r
- char *output, int32 output_extent);\r
-static int32 synth_ifiction(valinfo *vals, int tads_version,\r
- char *buf, int32 bufsize,\r
- void *story_file, int32 extent);\r
-static int get_png_dim(const void *img, int32 extent,\r
- int32 *xout, int32 *yout);\r
-static int get_jpeg_dim(const void *img, int32 extent,\r
- int32 *xout, int32 *yout);\r
-\r
-\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Get the IFID for a given story file. \r
- */\r
-int32 tads_get_story_file_IFID(void *story_file, int32 extent,\r
- char *output, int32 output_extent)\r
-{\r
- valinfo *vals;\r
- \r
- /* if we have GameInfo, try looking for an IFID there */\r
- if ((vals = parse_game_info(story_file, extent, 0)) != 0)\r
- {\r
- valinfo *val;\r
- int found = 0;\r
- \r
- /* find the "IFID" key */\r
- if ((val = find_by_key(vals, "IFID")) != 0)\r
- {\r
- char *p;\r
- \r
- /* copy the output as a null-terminated string */\r
- ASSERT_OUTPUT_SIZE((int32)val->val_len + 1);\r
- memcpy(output, val->val, val->val_len);\r
- output[val->val_len] = '\0';\r
-\r
- /* \r
- * count up the IFIDs in the buffer - there might be more than\r
- * one, separated by commas \r
- */\r
- for (found = 1, p = output ; *p != '\0' ; ++p)\r
- {\r
- /* if this is a comma, it delimits a new IFID */\r
- if (*p == ',')\r
- ++found;\r
- }\r
- }\r
-\r
- /* delete the GameInfo list */\r
- delete_valinfo_list(vals);\r
-\r
- /* if we found an IFID, indicate how many results we found */\r
- if (found != 0)\r
- return found;\r
- }\r
-\r
- /* \r
- * we didn't find an IFID in the GameInfo, so generate a default IFID\r
- * using the MD5 method \r
- */\r
- return generate_md5_ifid(story_file, extent, output, output_extent);\r
-}\r
-\r
-/*\r
- * Get the size of the ifiction metadata for the game \r
- */\r
-int32 tads_get_story_file_metadata_extent(void *story_file, int32 extent)\r
-{\r
- valinfo *vals;\r
- int32 ret;\r
- int ver;\r
- \r
- /*\r
- * First, make sure we have a GameInfo record. If we don't, simply\r
- * indicate that there's no metadata to fetch. \r
- */\r
- if ((vals = parse_game_info(story_file, extent, &ver)) == 0)\r
- return NO_REPLY_RV;\r
-\r
- /*\r
- * Run the ifiction synthesizer with no output buffer, to calculate the\r
- * size we need. \r
- */\r
- ret = synth_ifiction(vals, ver, 0, 0, story_file, extent);\r
-\r
- /* delete the value list */\r
- delete_valinfo_list(vals);\r
-\r
- /* return the required size */\r
- return ret;\r
-}\r
-\r
-/*\r
- * Get the ifiction metadata for the game\r
- */\r
-int32 tads_get_story_file_metadata(void *story_file, int32 extent,\r
- char *buf, int32 bufsize)\r
-{\r
- valinfo *vals;\r
- int32 ret;\r
- int ver;\r
-\r
- /* make sure we have metadata to fetch */\r
- if ((vals = parse_game_info(story_file, extent, &ver)) == 0)\r
- return NO_REPLY_RV;\r
-\r
- /* synthesize the ifiction data into the output buffer */\r
- ret = synth_ifiction(vals, ver, buf, bufsize, story_file, extent);\r
-\r
- /* if that required more space than we had available, return an error */\r
- if (ret > bufsize)\r
- ret = INVALID_USAGE_RV;\r
-\r
- /* delete the value list */\r
- delete_valinfo_list(vals);\r
-\r
- /* return the result */\r
- return ret;\r
-}\r
-\r
-/*\r
- * Get the size of the cover art \r
- */\r
-int32 tads_get_story_file_cover_extent(void *story_file, int32 story_len)\r
-{\r
- resinfo res;\r
- \r
- /* look for the cover art resource */\r
- if (find_cover_art(story_file, story_len, &res, 0, 0, 0))\r
- return res.len;\r
- else\r
- return NO_REPLY_RV;\r
-}\r
-\r
-/*\r
- * Get the format of the cover art \r
- */\r
-int32 tads_get_story_file_cover_format(void *story_file, int32 story_len)\r
-{\r
- int32 typ;\r
-\r
- /* look for CoverArt.jpg */\r
- if (find_cover_art(story_file, story_len, 0, &typ, 0, 0))\r
- return typ;\r
- else\r
- return NO_REPLY_RV;\r
-}\r
-\r
-/*\r
- * Get the cover art data \r
- */\r
-int32 tads_get_story_file_cover(void *story_file, int32 story_len,\r
- void *outbuf, int32 output_extent)\r
-{\r
- resinfo res;\r
-\r
- /* look for CoverArt.jpg, then for CoverArt.png */\r
- if (find_cover_art(story_file, story_len, &res, 0, 0, 0))\r
- {\r
- /* got it - copy the data to the buffer */\r
- ASSERT_OUTPUT_SIZE(res.len);\r
- memcpy(outbuf, res.ptr, res.len);\r
-\r
- /* success */\r
- return res.len;\r
- }\r
-\r
- /* otherwise, we didn't find it */\r
- return NO_REPLY_RV;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Generate a default IFID using the MD5 hash method \r
- */\r
-static int32 generate_md5_ifid(void *story_file, int32 extent,\r
- char *output, int32 output_extent)\r
-{\r
- md5_state_t md5;\r
- unsigned char md5_buf[16];\r
- char *p;\r
- int i;\r
-\r
- /* calculate the MD5 hash of the story file */\r
- md5_init(&md5);\r
- md5_append(&md5, story_file, extent);\r
- md5_finish(&md5, md5_buf);\r
-\r
- /* make sure we have room to store the result */\r
- ASSERT_OUTPUT_SIZE(39);\r
-\r
- /* the prefix is "TADS2-" or "TADS3-", depending on the format */\r
- if (tads_match_sig(story_file, extent, T2_SIGNATURE))\r
- strcpy(output, "TADS2-");\r
- else\r
- strcpy(output, "TADS3-");\r
-\r
- /* the rest is the MD5 hash of the file, as hex digits */\r
- for (i = 0, p = output + strlen(output) ; i < 16 ; p += 2, ++i)\r
- sprintf(p, "%02X", md5_buf[i]);\r
-\r
- /* indicate that we found one result */\r
- return 1;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Some UTF-8 utility functions and macros. We use our own rather than the\r
- * ctype.h macros because we're parsing UTF-8 text. \r
- */\r
-\r
-/* is c a space? */\r
-#define u_isspace(c) ((unsigned char)(c) < 128 && isspace(c))\r
-\r
-/* is c a horizontal space? */\r
-#define u_ishspace(c) (u_isspace(c) && (c) != '\n' && (c) != '\r')\r
-\r
-/* is-newline - matches \n, \r, and \u2028 */\r
-static int u_isnl(const char *p, int32 len)\r
-{\r
- return (*p == '\n' \r
- || *p == '\r'\r
- || (len >= 3\r
- && *(unsigned char *)p == 0xe2\r
- && *(unsigned char *)(p+1) == 0x80\r
- && *(unsigned char *)(p+2) == 0xa8));\r
-}\r
-\r
-/* skip to the next utf-8 character */\r
-static void nextc(const char **p, int32 *len)\r
-{\r
- /* skip the first byte */\r
- if (*len != 0)\r
- ++*p, --*len;\r
-\r
- /* skip continuation bytes */\r
- while (*len != 0 && (**p & 0xC0) == 0x80)\r
- ++*p, --*len;\r
-}\r
-\r
-/* skip to the previous utf-8 character */\r
-static void prevc(const char **p, int32 *len)\r
-{\r
- /* move back one byte */\r
- --*p, ++*len;\r
-\r
- /* keep skipping as long as we're looking at continuation characters */\r
- while ((**p & 0xC0) == 0x80)\r
- --*p, ++*len;\r
-}\r
-\r
-/*\r
- * Skip a newline sequence. Skips all common conventions, including \n,\r
- * \r, \n\r, \r\n, and \u2028. \r
- */\r
-static void skip_newline(const char **p, int32 *rem)\r
-{\r
- /* make sure we have something to skip */\r
- if (*rem == 0)\r
- return;\r
-\r
- /* check what we have */\r
- switch (**(const unsigned char **)p)\r
- {\r
- case '\n':\r
- /* skip \n or \n\r */\r
- nextc(p, rem);\r
- if (**p == '\r')\r
- nextc(p, rem);\r
- break;\r
-\r
- case '\r':\r
- /* skip \r or \r\n */\r
- nextc(p, rem);\r
- if (**p == '\n')\r
- nextc(p, rem);\r
- break;\r
-\r
- case 0xe2:\r
- /* \u2028 (unicode line separator) - just skip the one character */\r
- nextc(p, rem);\r
- break;\r
- }\r
-}\r
-\r
-/*\r
- * Skip to the next line \r
- */\r
-static void skip_to_next_line(const char **p, int32 *rem)\r
-{\r
- /* look for the next newline */\r
- for ( ; *rem != 0 ; nextc(p, rem))\r
- {\r
- /* if this is a newline of some kind, we're at the end of the line */\r
- if (u_isnl(*p, *rem))\r
- {\r
- /* skip the newline, and we're done */\r
- skip_newline(p, rem);\r
- break;\r
- }\r
- }\r
-}\r
-\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * ifiction synthesizer output context \r
- */\r
-typedef struct synthctx synthctx;\r
-struct synthctx\r
-{\r
- /* the current output pointer */\r
- char *buf;\r
-\r
- /* the number of bytes remaining in the output buffer */\r
- int32 buf_size;\r
-\r
- /* \r
- * the total number of bytes needed for the output (this might be more\r
- * than we've actually written, since we count up the bytes required\r
- * even if we need more space than the buffer provides) \r
- */\r
- int32 total_size;\r
-\r
- /* the head of the name/value pair list from the parsed GameInfo */\r
- valinfo *vals;\r
-};\r
-\r
-/* initialize a synthesizer context */\r
-static void init_synthctx(synthctx *ctx, char *buf, int32 bufsize,\r
- valinfo *vals)\r
-{\r
- /* set up at the beginning of the output buffer */\r
- ctx->buf = buf;\r
- ctx->buf_size = bufsize;\r
-\r
- /* we haven't written anything to the output buffer yet */\r
- ctx->total_size = 0;\r
-\r
- /* remember the name/value pair list */\r
- ctx->vals = vals;\r
-}\r
-\r
-/* \r
- * Write out a chunk to a synthesized ifiction record, updating pointers\r
- * and counters. We won't copy past the end of the buffer, but we'll\r
- * continue counting the output length needed in any case. \r
- */\r
-static void write_ifiction(synthctx *ctx, const char *src, size_t srclen)\r
-{\r
- int32 copy_len;\r
-\r
- /* copy as much as we can, up to the remaining buffer size */\r
- copy_len = srclen;\r
- if (copy_len > ctx->buf_size)\r
- copy_len = ctx->buf_size;\r
-\r
- /* do the copying, if any */\r
- if (copy_len != 0)\r
- {\r
- /* copy the bytes */\r
- memcpy(ctx->buf, src, (size_t)copy_len);\r
-\r
- /* adjust the buffer pointer and output buffer size remaining */\r
- ctx->buf += copy_len;\r
- ctx->buf_size -= copy_len;\r
- }\r
-\r
- /* count this source data in the total size */\r
- ctx->total_size += srclen;\r
-}\r
-\r
-/* write a null-terminated chunk to the synthesized ifiction record */\r
-static void write_ifiction_z(synthctx *ctx, const char *src)\r
-{\r
- write_ifiction(ctx, src, strlen(src));\r
-}\r
-\r
-/*\r
- * Write a PCDATA string to the synthesized ifiction record. In\r
- * particular, we rewrite '<', '>', and '&' as '<', '>', and '&',\r
- * respectively; we trim off leading and trailing spaces; and we compress\r
- * each run of whitespace down to a single \u0020 (' ') character.\r
- */\r
-static void write_ifiction_pcdata(synthctx *ctx, const char *p, size_t len)\r
-{\r
- /* first, skip any leading whitespace */\r
- for ( ; len != 0 && u_ishspace(*p) ; ++p, --len) ;\r
-\r
- /* keep going until we run out of string */\r
- for (;;)\r
- {\r
- const char *start;\r
- \r
- /* scan to the next whitespace or markup-significant character */\r
- for (start = p ;\r
- len != 0 && !u_ishspace(*p)\r
- && *p != '<' && *p != '>' && *p != '&' ; ++p, --len) ;\r
-\r
- /* write the part up to here */\r
- if (p != start)\r
- write_ifiction(ctx, start, p - start);\r
-\r
- /* if we've reached the end of the string, we can stop */\r
- if (len == 0)\r
- break;\r
-\r
- /* check what stopped us */\r
- switch (*p)\r
- {\r
- case '<':\r
- write_ifiction_z(ctx, "<");\r
- ++p, --len;\r
- break;\r
-\r
- case '>':\r
- write_ifiction_z(ctx, ">");\r
- ++p, --len;\r
- break;\r
-\r
- case '&':\r
- write_ifiction_z(ctx, "&");\r
- ++p, --len;\r
- break;\r
-\r
- default:\r
- /* \r
- * The only other thing that could have stopped us is\r
- * whitespace. Skip all consecutive whitespace. \r
- */\r
- for ( ; len != 0 && u_ishspace(*p) ; ++p, --len);\r
-\r
- /* \r
- * if that's not the end of the string, replace the run of\r
- * whitespace with a single space character in the output; if\r
- * we've reached the end of the string, we don't even want to\r
- * do that, since we want to trim off trailing spaces \r
- */\r
- if (len != 0)\r
- write_ifiction_z(ctx, " ");\r
- break;\r
- }\r
- }\r
-}\r
-\r
-/*\r
- * Translate a GameInfo keyed value to the corresponding ifiction tagged\r
- * value. We find the GameInfo value keyed by 'gameinfo_key', and write\r
- * out the same string under the ifiction XML tag 'ifiction_tag'. We write\r
- * a complete XML container sequence - <tag>value</tag>.\r
- * \r
- * If the given GameInfo key doesn't exist, we use the default value string\r
- * 'dflt', if given. If the GameInfo key doesn't exist and 'dflt' is null,\r
- * we don't write anything - we don't even write the open/close tags.\r
- * \r
- * If 'html' is true, we assume the value is in html format, and we write\r
- * it untranslated. Otherwise, we write it as PCDATA, translating markup\r
- * characters into '&' entities and compressing whitespace. \r
- */\r
-static void write_ifiction_xlat_base(synthctx *ctx, int indent,\r
- const char *gameinfo_key,\r
- const char *ifiction_tag,\r
- const char *dflt, int html)\r
-{\r
- valinfo *val;\r
- const char *valstr;\r
- size_t vallen;\r
- \r
- /* look up the GameInfo key */\r
- if ((val = find_by_key(ctx->vals, gameinfo_key)) != 0)\r
- {\r
- /* we found the GameInfo value - use it */\r
- valstr = val->val;\r
- vallen = val->val_len;\r
- }\r
- else if (dflt != 0)\r
- {\r
- /* the GameInfo value doesn't exist, but we have a default - use it */\r
- valstr = dflt;\r
- vallen = strlen(dflt);\r
- }\r
- else\r
- {\r
- /* there's no GameInfo value and no default, so write nothing */\r
- return;\r
- }\r
-\r
- /* write the indentation */\r
- while (indent != 0)\r
- {\r
- static const char spaces[] = " ";\r
- size_t cur;\r
-\r
- /* figure how much we can write on this round */\r
- cur = indent;\r
- if (cur > sizeof(spaces) - 1)\r
- cur = sizeof(spaces) - 1;\r
-\r
- /* write it */\r
- write_ifiction(ctx, spaces, cur);\r
-\r
- /* deduct it from the amount remaining */\r
- indent -= cur;\r
- }\r
-\r
- /* write the open tag */\r
- write_ifiction_z(ctx, "<");\r
- write_ifiction_z(ctx, ifiction_tag);\r
- write_ifiction_z(ctx, ">");\r
-\r
- /* write the value, applying pcdata translations */\r
- if (html)\r
- write_ifiction(ctx, valstr, vallen);\r
- else\r
- write_ifiction_pcdata(ctx, valstr, vallen);\r
-\r
- /* write the close tag */\r
- write_ifiction_z(ctx, "</");\r
- write_ifiction_z(ctx, ifiction_tag);\r
- write_ifiction_z(ctx, ">\n");\r
-}\r
-\r
-#define write_ifiction_xlat(ctx, indent, gikey, iftag, dflt) \\r
- write_ifiction_xlat_base(ctx, indent, gikey, iftag, dflt, FALSE)\r
-\r
-#define write_ifiction_xlat_html(ctx, indent, gikey, iftag, dflt) \\r
- write_ifiction_xlat_base(ctx, indent, gikey, iftag, dflt, TRUE)\r
-\r
-\r
-/*\r
- * Retrieve the next author name from the GameInfo "Author" format. The\r
- * format is as follows:\r
- * \r
- * name <email> <email>... ; ...\r
- * \r
- * That is, each author is listed with a name followed by one or more email\r
- * addresses in angle brackets, and multiple authors are separated by\r
- * semicolons. \r
- */\r
-static int scan_author_name(const char **p, size_t *len,\r
- const char **start, const char **end)\r
-{\r
- /* keep going until we find a non-empty author name */\r
- for (;;)\r
- {\r
- /* skip leading spaces */\r
- for ( ; *len != 0 && u_ishspace(**p) ; ++*p, --*len) ;\r
-\r
- /* if we ran out of string, there's definitely no author name */\r
- if (*len == 0)\r
- return FALSE;\r
-\r
- /* \r
- * Find the end of this author name. The author name ends at the\r
- * next semicolon or angle bracket. \r
- */\r
- for (*start = *p ; *len != 0 && **p != ';' && **p != '<' ;\r
- ++*p, --*len) ;\r
-\r
- /* trim off any trailing spaces */\r
- for (*end = *p ; *end > *start && u_ishspace(*(*end - 1)) ; --*end) ;\r
-\r
- /* now skip any email addresses */\r
- while (*len != 0 && **p == '<')\r
- {\r
- /* skip to the closing bracket */\r
- for (++*p, --*len ; *len != 0 && **p != '>' ; ++*p, --*len) ;\r
-\r
- /* skip the bracket */\r
- if (*len != 0)\r
- ++*p, --*len;\r
-\r
- /* skip whitespace */\r
- for ( ; *len != 0 && u_ishspace(**p) ; ++*p, --*len) ;\r
-\r
- /* \r
- * if we're not at a semicolon, another angle bracket, or the\r
- * end of the string, it's a syntax error \r
- */\r
- if (*len != 0 && **p != '<' && **p != ';')\r
- {\r
- *len = 0;\r
- return FALSE;\r
- }\r
- }\r
-\r
- /* if we're at a semicolon, skip it */\r
- if (*len != 0 && **p == ';')\r
- ++*p, --*len;\r
-\r
- /* \r
- * if we found a non-empty name, return it; otherwise, continue on\r
- * to the next semicolon section \r
- */\r
- if (*end != *start)\r
- return TRUE;\r
- }\r
-}\r
-\r
-\r
-/*\r
- * Synthesize an ifiction record for the given GameInfo name/value pair\r
- * list. Returns the number of bytes required for the result, including\r
- * null termination. We'll copy as much as we can to the output buffer, up\r
- * to bufsize; if the buffer size is insufficient to hold the result, we'll\r
- * still indicate the length needed for the full result, but we're careful\r
- * not to actually copy anything past the end of the buffer. \r
- */\r
-static int32 synth_ifiction(valinfo *vals, int tads_version,\r
- char *buf, int32 bufsize,\r
- void *story_file, int32 extent)\r
-{\r
- char default_ifid[TREATY_MINIMUM_EXTENT];\r
- valinfo *ifid = find_by_key(vals, "IFID");\r
- const char *ifid_val;\r
- size_t ifid_len;\r
- valinfo *author = find_by_key(vals, "AuthorEmail");\r
- valinfo *url = find_by_key(vals, "Url");\r
- synthctx ctx;\r
- const char *p;\r
- size_t rem;\r
- int32 art_fmt;\r
- int32 art_wid, art_ht;\r
-\r
- /* initialize the output content */\r
- init_synthctx(&ctx, buf, bufsize, vals);\r
-\r
- /* make sure the tads version is one we know how to handle */\r
- if (tads_version != 2 && tads_version != 3)\r
- return NO_REPLY_RV;\r
-\r
- /* \r
- * The IFID is mandatory. If there's not an IFID specifically listed\r
- * in the GameInfo, we need to generate the default IFID based on the\r
- * MD5 hash of the game file. \r
- */\r
- if (ifid != 0)\r
- {\r
- /* use the explicit IFID(s) listed in the GameInfo */\r
- ifid_val = ifid->val;\r
- ifid_len = ifid->val_len;\r
- }\r
- else\r
- {\r
- /* generate the default IFID */\r
- generate_md5_ifid(story_file, extent,\r
- default_ifid, TREATY_MINIMUM_EXTENT);\r
-\r
- /* use this as the IFID */\r
- ifid_val = default_ifid;\r
- ifid_len = strlen(default_ifid);\r
- }\r
-\r
- /* write the header, and start the <identification> section */\r
- write_ifiction_z(\r
- &ctx,\r
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"\r
- "<ifindex version=\"1.0\" "\r
- "xmlns=\"http://babel.ifarchive.org/protocol/iFiction/\">\n"\r
- " <!-- Bibliographic data translated from TADS GameInfo -->\n"\r
- " <story>\n"\r
- " <colophon>\n"\r
- " <generator>Babel</generator>\n"\r
- " <generatorversion>" TREATY_VERSION "</generatorversion>\n"\r
- " <originated>2006-04-14</originated>\n"\r
- " </colophon>\n"\r
- " <identification>\n");\r
-\r
- /* write each IFID (there might be several) */\r
- for (p = ifid_val, rem = ifid_len ; rem != 0 ; )\r
- {\r
- const char *start;\r
- const char *end;\r
-\r
- /* skip leading spaces */\r
- for ( ; rem != 0 && u_ishspace(*p) ; ++p, --rem) ;\r
- \r
- /* find the end of this IFID */\r
- for (start = p ; rem != 0 && *p != ',' ; ++p, --rem) ;\r
-\r
- /* remove trailing spaces */\r
- for (end = p ; end > start && u_ishspace(*(end-1)) ; --end) ;\r
-\r
- /* if we found one, write it out */\r
- if (end != start)\r
- {\r
- write_ifiction_z(&ctx, " <ifid>");\r
- write_ifiction(&ctx, start, end - start);\r
- write_ifiction_z(&ctx, "</ifid>\n");\r
- }\r
-\r
- /* skip the comma */\r
- if (rem != 0 && *p == ',')\r
- ++p, --rem;\r
- }\r
-\r
- /* add the format information */\r
- write_ifiction_z(&ctx,\r
- tads_version == 2\r
- ? " <format>tads2</format>\n"\r
- : " <format>tads3</format>\n");\r
-\r
- /* close the <identification> section and start the <bibliographic> */\r
- write_ifiction_z(&ctx,\r
- " </identification>\n"\r
- " <bibliographic>\n");\r
-\r
- /* write the various bibliographic data */\r
- write_ifiction_xlat(&ctx, 6, "Name", "title", "An Interactive Fiction");\r
- write_ifiction_xlat(&ctx, 6, "Headline", "headline", 0);\r
- write_ifiction_xlat(&ctx, 6, "Desc", "description", 0);\r
- write_ifiction_xlat(&ctx, 6, "Genre", "genre", 0);\r
- write_ifiction_xlat(&ctx, 6, "Forgiveness", "forgiveness", 0);\r
- write_ifiction_xlat(&ctx, 6, "Series", "series", 0);\r
- write_ifiction_xlat(&ctx, 6, "SeriesNumber", "seriesnumber", 0);\r
- write_ifiction_xlat(&ctx, 6, "Language", "language", 0);\r
- write_ifiction_xlat(&ctx, 6, "FirstPublished", "firstpublished", 0);\r
-\r
- /* if there's an author, write the list of author names */\r
- if (author != 0)\r
- {\r
- int cnt;\r
- int i;\r
- const char *start;\r
- const char *end;\r
-\r
- /* start the <author> tag */\r
- write_ifiction_z(&ctx, " <author>");\r
- \r
- /* \r
- * first, count up the number of authors - authors are separated by\r
- * semicolons, so there's one more author than there are semicolons\r
- */\r
- for (p = author->val, rem = author->val_len, cnt = 1 ;\r
- scan_author_name(&p, &rem, &start, &end) ; ) ;\r
-\r
- /* \r
- * Now generate the list of authors. If there are multiple\r
- * authors, use commas to separate them. \r
- */\r
- for (p = author->val, rem = author->val_len, i = 0 ; ; ++i)\r
- {\r
- /* scan this author's name */\r
- if (!scan_author_name(&p, &rem, &start, &end))\r
- break;\r
- \r
- /* write out this author name */\r
- write_ifiction_pcdata(&ctx, start, end - start);\r
-\r
- /* if there's another name to come, write a separator */\r
- if (i + 1 < cnt)\r
- {\r
- /* \r
- * write just "and" to separate two items; write ","\r
- * between items in lists of more than two, with ",and"\r
- * between the last two items \r
- */\r
- write_ifiction_z(&ctx,\r
- cnt == 2 ? " and " :\r
- i + 2 < cnt ? ", " : ", and ");\r
- }\r
- }\r
-\r
- /* end the <author> tag */\r
- write_ifiction_z(&ctx, "</author>\n");\r
- }\r
-\r
- /* end the biblio section */\r
- write_ifiction_z(&ctx, " </bibliographic>\n");\r
-\r
- /* if there's cover art, add its information */\r
- if (find_cover_art(story_file, extent, 0, &art_fmt, &art_wid, &art_ht)\r
- && (art_fmt == PNG_COVER_FORMAT || art_fmt == JPEG_COVER_FORMAT))\r
- {\r
- char buf[200];\r
- \r
- sprintf(buf,\r
- " <cover>\n"\r
- " <format>%s</format>\n"\r
- " <height>%lu</height>\n"\r
- " <width>%lu</width>\n"\r
- " </cover>\n",\r
- art_fmt == PNG_COVER_FORMAT ? "png" : "jpg",\r
- (long)art_ht, (long)art_wid);\r
-\r
- write_ifiction_z(&ctx, buf);\r
- }\r
-\r
- /* if there's an author email, include it */\r
- if (author != 0 || url != 0)\r
- {\r
- const char *p;\r
- size_t rem;\r
- int i;\r
- \r
- /* open the section */\r
- write_ifiction_z(&ctx, " <contacts>\n");\r
-\r
- /* add the author email, if provided */\r
- if (author != 0)\r
- {\r
- /* write the email list */\r
- for (i = 0, p = author->val, rem = author->val_len ; ; ++i)\r
- {\r
- const char *start;\r
- \r
- /* skip to the next email address */\r
- for ( ; rem != 0 && *p != '<' ; ++p, --rem) ;\r
- \r
- /* if we didn't find an email address, we're done */\r
- if (rem == 0)\r
- break;\r
- \r
- /* find the matching '>' */\r
- for (++p, --rem, start = p ; rem != 0 && *p != '>' ;\r
- ++p, --rem) ;\r
-\r
- /* \r
- * if this is the first one, open the section; otherwise,\r
- * add a comma \r
- */\r
- if (i == 0)\r
- write_ifiction_z(&ctx, " <authoremail>");\r
- else\r
- write_ifiction_z(&ctx, ",");\r
- \r
- /* write this address */\r
- write_ifiction(&ctx, start, p - start);\r
- \r
- /* \r
- * skip the closing bracket, if there is one; if we're out\r
- * of string, we're done \r
- */\r
- if (rem != 0)\r
- ++p, --rem;\r
- else\r
- break;\r
- }\r
-\r
- /* if we found any emails to write, end the section */\r
- if (i != 0)\r
- write_ifiction_z(&ctx, "</authoremail>\n");\r
- }\r
-\r
- /* if there's a URL, add it */\r
- if (url != 0)\r
- {\r
- write_ifiction_z(&ctx, " <url>");\r
- write_ifiction(&ctx, url->val, url->val_len);\r
- write_ifiction_z(&ctx, "</url>\n");\r
- }\r
-\r
- /* close the section */\r
- write_ifiction_z(&ctx, " </contacts>\n");\r
- }\r
-\r
- /* add the tads-specific section */\r
- write_ifiction_z(&ctx, " <tads>\n");\r
- \r
- write_ifiction_xlat(&ctx, 6, "Version", "version", 0);\r
- write_ifiction_xlat(&ctx, 6, "ReleaseDate", "releasedate", 0);\r
- write_ifiction_xlat(&ctx, 6, "PresentationProfile",\r
- "presentationprofile", 0);\r
- write_ifiction_xlat(&ctx, 6, "Byline", "byline", 0);\r
-\r
- write_ifiction_z(&ctx, " </tads>\n");\r
-\r
- /* close the story section and the main body */\r
- write_ifiction_z(&ctx, " </story>\n</ifindex>\n");\r
- \r
- /* add the null terminator */\r
- write_ifiction(&ctx, "", 1);\r
-\r
- /* return the total output size */\r
- return ctx.total_size;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Check a data block to see if it starts with the given signature. \r
- */\r
-int tads_match_sig(const void *buf, int32 len, const char *sig)\r
-{\r
- /* note the length of the signature string */\r
- size_t sig_len = strlen(sig);\r
- \r
- /* if matches if the buffer starts with the signature string */\r
- return (len >= (int32)sig_len && memcmp(buf, sig, sig_len) == 0);\r
-}\r
-\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * portable-to-native format conversions \r
- */\r
-#define osbyte(p, ofs) \\r
- (*(((unsigned char *)(p)) + (ofs)))\r
-\r
-#define osrp1(p) \\r
- ((unsigned int)osbyte(p, 0))\r
-\r
-#define osrp2(p) \\r
- ((unsigned int)osbyte(p, 0) \\r
- + ((unsigned int)osbyte(p, 1) << 8))\r
-\r
-#define osrp4(p) \\r
- (((unsigned long)osbyte(p, 0)) \\r
- + (((unsigned long)osbyte(p, 1)) << 8) \\r
- + (((unsigned long)osbyte(p, 2)) << 16) \\r
- + (((unsigned long)osbyte(p, 3)) << 24))\r
-\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Parse a game file and retrieve the GameInfo data. Returns the head of a\r
- * linked list of valinfo entries.\r
- */\r
-static valinfo *parse_game_info(const void *story_file, int32 story_len,\r
- int *tads_version)\r
-{\r
- resinfo res;\r
- const char *p;\r
- int32 rem;\r
- valinfo *val_head = 0;\r
-\r
- /* \r
- * first, find the GameInfo resource - if it's not there, there's no\r
- * game information to parse \r
- */\r
- if (!find_resource(story_file, story_len, "GameInfo.txt", &res))\r
- return 0;\r
-\r
- /* if the caller wants the TADS version number, hand it back */\r
- if (tads_version != 0)\r
- *tads_version = res.tads_version;\r
-\r
- /* parse the data */\r
- for (p = res.ptr, rem = res.len ; rem != 0 ; )\r
- {\r
- const char *name_start;\r
- size_t name_len;\r
- const char *val_start;\r
- valinfo *val;\r
- const char *inp;\r
- int32 inlen;\r
- char *outp;\r
-\r
- /* skip any leading whitespace */\r
- while (rem != 0 && u_isspace(*p))\r
- ++p, --rem;\r
-\r
- /* if the line starts with '#', it's a comment, so skip it */\r
- if (rem != 0 && *p == '#')\r
- {\r
- skip_to_next_line(&p, &rem);\r
- continue;\r
- }\r
-\r
- /* we must have the start of a name - note it */\r
- name_start = p;\r
-\r
- /* skip ahead to a space or colon */\r
- while (rem != 0 && *p != ':' && !u_ishspace(*p))\r
- nextc(&p, &rem);\r
-\r
- /* note the length of the name */\r
- name_len = p - name_start;\r
-\r
- /* skip any whitespace before the presumed colon */\r
- while (rem != 0 && u_ishspace(*p))\r
- nextc(&p, &rem);\r
-\r
- /* if we're not at a colon, the line is ill-formed, so skip it */\r
- if (rem == 0 || *p != ':')\r
- {\r
- /* skip the entire line, and go back for the next one */\r
- skip_to_next_line(&p, &rem);\r
- continue;\r
- }\r
-\r
- /* skip the colon and any whitespace immediately after it */\r
- for (nextc(&p, &rem) ; rem != 0 && u_ishspace(*p) ; nextc(&p, &rem)) ;\r
-\r
- /* note where the value starts */\r
- val_start = p;\r
-\r
- /*\r
- * Scan the value to get its length. The value runs from here to\r
- * the next newline that's not followed immediately by a space. \r
- */\r
- while (rem != 0)\r
- {\r
- const char *nl;\r
- int32 nlrem;\r
- \r
- /* skip to the next line */\r
- skip_to_next_line(&p, &rem);\r
-\r
- /* if we're at eof, we can stop now */\r
- if (rem == 0)\r
- break;\r
-\r
- /* note where this line starts */\r
- nl = p;\r
- nlrem = rem;\r
-\r
- /* \r
- * if we're at a non-whitespace character, it's definitely not\r
- * a continuation line \r
- */\r
- if (!u_ishspace(*p))\r
- break;\r
-\r
- /* \r
- * check for spaces followed by a non-space character - this\r
- * would signify a continuation line\r
- */\r
- for ( ; rem != 0 && u_ishspace(*p) ; nextc(&p, &rem)) ;\r
- if (rem == 0 || u_isnl(p, rem))\r
- {\r
- /* \r
- * we're at end of file, we found a line with nothing but\r
- * whitespace, so this isn't a continuation line; go back\r
- * to the start of this line and end the value here \r
- */\r
- p = nl;\r
- rem = nlrem;\r
- break;\r
- }\r
-\r
- /* \r
- * We found whitespace followed by non-whitespace, so this is a\r
- * continuation line. Keep going for now.\r
- */\r
- }\r
-\r
- /* remove any trailing newlines */\r
- while (p > val_start)\r
- {\r
- /* move back one character */\r
- prevc(&p, &rem);\r
-\r
- /* \r
- * if it's a newline, keep going; otherwise, keep this\r
- * character and stop trimming \r
- */\r
- if (!u_isnl(p, rem))\r
- {\r
- nextc(&p, &rem);\r
- break;\r
- }\r
- }\r
-\r
- /* \r
- * Allocate a new value entry. Make room for the entry itself plus\r
- * a copy of the value. We don't need to make a copy of the name,\r
- * since we can just use the original copy from the story file\r
- * buffer. We do need a copy of the value because we might need to\r
- * transform it slightly, to remove newlines and leading spaces on\r
- * continuation lines. \r
- */\r
- val = (valinfo *)malloc(sizeof(valinfo) + (p - val_start));\r
-\r
- /* link it into our list */\r
- val->nxt = val_head;\r
- val_head = val;\r
-\r
- /* point the name directly to the name in the buffer */\r
- val->name = name_start;\r
- val->name_len = name_len;\r
-\r
- /* point the value to the space allocated along with the valinfo */\r
- val->val = (char *)(val + 1);\r
-\r
- /* store the name, removing newlines and continuation-line spaces */\r
- for (outp = val->val, inp = val_start, inlen = p - val_start ;\r
- inlen != 0 ; )\r
- {\r
- const char *l;\r
- \r
- /* find the next newline */\r
- for (l = inp ; inlen != 0 && !u_isnl(inp, inlen) ;\r
- nextc(&inp, &inlen)) ;\r
-\r
- /* copy this line to the output */\r
- memcpy(outp, l, inp - l);\r
- outp += inp - l;\r
-\r
- /* if we're out of input, we're done */\r
- if (inlen == 0)\r
- break;\r
-\r
- /* we're at a newline: replace it with a space in the output */\r
- *outp++ = ' ';\r
-\r
- /* skip the newline and subsequent whitespace in the input */\r
- for (skip_newline(&inp, &inlen) ;\r
- inlen != 0 && u_ishspace(*inp) ; nextc(&inp, &inlen)) ;\r
- }\r
-\r
- /* set the length of the parsed value */\r
- val->val_len = outp - val->val;\r
-\r
- /* skip to the next line and continue parsing */\r
- skip_to_next_line(&p, &rem);\r
- }\r
-\r
- /* return the head of the linked list of value entries */\r
- return val_head;\r
-}\r
-static int my_memicmp(const void *aa, const void *bb, int l)\r
-{\r
- int s=0,i;\r
- char *a=(char *) aa;\r
- char *b=(char *) bb;\r
- for(i=0;i<l && !s;i++)\r
- s=tolower(a[i])-tolower(b[i]);\r
- return s;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Given a valinfo list obtained from parse_game_info(), find the value for\r
- * the given key \r
- */\r
-static valinfo *find_by_key(valinfo *list_head, const char *key)\r
-{\r
- valinfo *p;\r
- size_t key_len = strlen(key);\r
- \r
- /* scan the list for the given key */\r
- for (p = list_head ; p != 0 ; p = p->nxt)\r
- {\r
- /* if this one matches the key we're looking for, return it */\r
- if (p->name_len == key_len && my_memicmp(p->name, key, key_len) == 0)\r
- return p;\r
- }\r
-\r
- /* no luck */\r
- return 0;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Delete a valinfo list obtained from parse_game_info() \r
- */\r
-static void delete_valinfo_list(valinfo *head)\r
-{\r
- /* keep going until we run out of entries */\r
- while (head != 0)\r
- {\r
- /* remember the next entry, before we delete this one */\r
- valinfo *nxt = head->nxt;\r
-\r
- /* delete this one */\r
- free(head);\r
-\r
- /* move on to the next one */\r
- head = nxt;\r
- }\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Find the cover art resource. We'll look for CoverArt.jpg and\r
- * CoverArt.png, in that order. \r
- */\r
-static int find_cover_art(const void *story_file, int32 story_len,\r
- resinfo *resp, int32 *image_format,\r
- int32 *width, int32 *height)\r
-{\r
- resinfo res;\r
- int32 x, y;\r
-\r
- /* if they didn't want the resource info, provide a placeholder */\r
- if (resp == 0)\r
- resp = &res;\r
-\r
- /* look for CoverArt.jpg first */\r
- if (find_resource(story_file, story_len, "CoverArt.jpg", resp))\r
- {\r
- /* get the width and height */\r
- if (!get_jpeg_dim(resp->ptr, resp->len, &x, &y))\r
- return FALSE;\r
-\r
- /* hand back the width and height if it was requested */\r
- if (width != 0)\r
- *width = x;\r
- if (height != 0)\r
- *height = y;\r
-\r
- /* tell them it's a JPEG image */\r
- if (image_format != 0)\r
- *image_format = JPEG_COVER_FORMAT;\r
-\r
- /* indicate success */\r
- return TRUE;\r
- }\r
-\r
- /* look for CoverArt.png second */\r
- if (find_resource(story_file, story_len, "CoverArt.png", resp))\r
- {\r
- /* get the width and height */\r
- if (!get_png_dim(resp->ptr, resp->len, &x, &y))\r
- return FALSE;\r
-\r
- /* hand back the width and height if it was requested */\r
- if (width != 0)\r
- *width = x;\r
- if (height != 0)\r
- *height = y;\r
-\r
- /* tell them it's a PNG image */\r
- if (image_format != 0)\r
- *image_format = PNG_COVER_FORMAT;\r
-\r
- /* indicate success */\r
- return TRUE;\r
- }\r
-\r
- /* didn't find it */\r
- return FALSE;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Find a resource in a TADS 2 or 3 story file that's been loaded into\r
- * memory. On success, fills in the offset and size of the resource and\r
- * returns TRUE; if the resource isn't found, returns FALSE.\r
- */\r
-static int find_resource(const void *story_file, int32 story_len,\r
- const char *resname, resinfo *info)\r
-{\r
- /* if there's no file, there's no resource */\r
- if (story_file == 0)\r
- return FALSE;\r
-\r
- /* check for tads 2 */\r
- if (tads_match_sig(story_file, story_len, T2_SIGNATURE))\r
- {\r
- info->tads_version = 2;\r
- return t2_find_res(story_file, story_len, resname, info);\r
- }\r
-\r
- /* check for tads 3 */\r
- if (tads_match_sig(story_file, story_len, T3_SIGNATURE))\r
- {\r
- info->tads_version = 3;\r
- return t3_find_res(story_file, story_len, resname, info);\r
- }\r
-\r
- /* it's not one of ours */\r
- return FALSE;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Find a resource in a tads 2 game file \r
- */\r
-static int t2_find_res(const void *story_file, int32 story_len,\r
- const char *resname, resinfo *info)\r
-{\r
- const char *basep = (const char *)story_file;\r
- const char *endp = basep + story_len;\r
- const char *p;\r
- size_t resname_len;\r
-\r
- /* note the length of the name we're seeking */\r
- resname_len = strlen(resname);\r
-\r
- /* \r
- * skip past the tads 2 file header (13 bytes for the signature, 7\r
- * bytes for the version header, 2 bytes for the flags, 26 bytes for\r
- * the timestamp) \r
- */\r
- p = basep + 13 + 7 + 2 + 26;\r
-\r
- /* \r
- * scan the sections in the file; stop on $EOF, and skip everything\r
- * else but HTMLRES, which is the section type that \r
- */\r
- while (p < endp)\r
- {\r
- unsigned long endofs;\r
-\r
- /*\r
- * We're pointing to a section block header, which looks like this:\r
- * \r
- *. <byte> type-length\r
- *. <byte * type-length> type-name\r
- *. <uint32> next-section-address\r
- */\r
-\r
- /* read the ending offset */\r
- endofs = osrp4(p + 1 + osrp1(p));\r
-\r
- /* check the type */\r
- if (p[0] == 7 && memcmp(p + 1, "HTMLRES", 7) == 0)\r
- {\r
- unsigned long found_ofs;\r
- int found;\r
- unsigned long entry_cnt;\r
-\r
- /* we haven't found the resource yet */\r
- found = FALSE;\r
-\r
- /* \r
- * It's a multimedia resource block. Skip the section block\r
- * header and look at the index table - the index table\r
- * consists of a uint32 giving the number of entries, followed\r
- * by a reserved uint32, followed by the entries. \r
- */\r
- p += 12;\r
- entry_cnt = osrp4(p);\r
-\r
- /* skip to the first index entry */\r
- p += 8;\r
-\r
- /* scan the index entries */\r
- for ( ; entry_cnt != 0 ; --entry_cnt)\r
- {\r
- unsigned long res_ofs;\r
- unsigned long res_siz;\r
- size_t name_len;\r
-\r
- /*\r
- * We're at the next index entry, which looks like this:\r
- *\r
- *. <uint32> resource-address (bytes from end of index)\r
- *. <uint32> resource-length (in bytes)\r
- *. <uint2> name-length\r
- *. <byte * name-length> name\r
- */\r
- res_ofs = osrp4(p);\r
- res_siz = osrp4(p + 4);\r
- name_len = osrp2(p + 8);\r
- p += 10;\r
-\r
- /* check for a match to the name we're looking for */\r
- if (name_len == resname_len\r
- && my_memicmp(resname, p, name_len) == 0)\r
- {\r
- /* \r
- * it's the one we want - note its resource location\r
- * and size, but keep scanning for now, since we need\r
- * to find the end of the index before we'll know where\r
- * the actual resources begin \r
- */\r
- found = TRUE;\r
- found_ofs = res_ofs;\r
- info->len = res_siz;\r
- }\r
-\r
- /* skip this one's name */\r
- p += name_len;\r
- }\r
-\r
- /* \r
- * if we found our resource, the current seek position is the\r
- * base of the offset we found in the directory; so we can\r
- * finally fix up the offset to give the actual file location\r
- * and return the result \r
- */\r
- if (found)\r
- {\r
- /* fix up the offset with the actual file location */\r
- info->ptr = p + found_ofs;\r
-\r
- /* tell the caller we found it */\r
- return TRUE;\r
- }\r
- }\r
- else if (p[0] == 4 && memcmp(p + 1, "$EOF", 4) == 0)\r
- {\r
- /* \r
- * that's the end of the file - we've finished without finding\r
- * the resource, so return failure \r
- */\r
- return FALSE;\r
- }\r
-\r
- /* move to the next section */\r
- p = basep + endofs;\r
- }\r
-\r
- /* \r
- * reached EOF without an $EOF marker - file must be corrupted; return\r
- * 'not found' \r
- */\r
- return FALSE;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Find a resource in a T3 image file \r
- */\r
-static int t3_find_res(const void *story_file, int32 story_len,\r
- const char *resname, resinfo *info)\r
-{\r
- const char *basep = (const char *)story_file;\r
- const char *endp = basep + story_len;\r
- const char *p;\r
- size_t resname_len;\r
-\r
- /* note the length of the name we're seeking */\r
- resname_len = strlen(resname);\r
-\r
- /* \r
- * skip the file header - 11 bytes for the signature, 2 bytes for the\r
- * format version, 32 reserved bytes, and 24 bytes for the timestamp \r
- */\r
- p = basep + 11 + 2 + 32 + 24;\r
-\r
- /* scan the data blocks */\r
- while (p < endp)\r
- {\r
- unsigned long siz;\r
-\r
- /*\r
- * We're at the next block header, which looks like this:\r
- *\r
- *. <byte * 4> type-name\r
- *. <uint32> block-size\r
- *. <uint16> flags\r
- */\r
-\r
- /* get the block size */\r
- siz = osrp4(p + 4);\r
-\r
- /* check the type */\r
- if (memcmp(p, "MRES", 4) == 0)\r
- {\r
- unsigned int entry_cnt;\r
- unsigned int i;\r
- const char *blockp;\r
-\r
- /* skip the header */\r
- p += 10;\r
-\r
- /* \r
- * remember the location of the base of the block - the data\r
- * seek location for each index entry is given as an offset\r
- * from this location \r
- */\r
- blockp = p;\r
-\r
- /* the first thing in the table is the number of entries */\r
- entry_cnt = osrp2(p);\r
- p += 2;\r
-\r
- /* read the entries */\r
- for (i = 0 ; i < entry_cnt ; ++i)\r
- {\r
- unsigned long entry_ofs;\r
- unsigned long entry_siz;\r
- size_t entry_name_len;\r
- char namebuf[256];\r
- char *xp;\r
- size_t xi;\r
-\r
- /* \r
- * Parse this index entry:\r
- * \r
- *. <uint32> address (as offset from the block base)\r
- *. <uint32> size (in bytes)\r
- *. <uint8> name-length\r
- *. <byte * name-length> name (all bytes XORed with 0xFF)\r
- */\r
- entry_ofs = osrp4(p);\r
- entry_siz = osrp4(p + 4);\r
- entry_name_len = (unsigned char)p[8];\r
-\r
- /* unmask the name */\r
- memcpy(namebuf, p + 9, resname_len);\r
- for (xi = resname_len, xp = namebuf ; xi != 0 ; --xi)\r
- *xp++ ^= 0xFF;\r
-\r
- /* if this is the one we're looking for, return it */\r
- if (entry_name_len == resname_len\r
- && my_memicmp(resname, namebuf, resname_len) == 0)\r
- {\r
- /* \r
- * fill in the return information - note that the entry\r
- * offset given in the header is an offset from data\r
- * block's starting location, so fix this up to an\r
- * absolute seek location for the return value \r
- */\r
- info->ptr = blockp + entry_ofs;\r
- info->len = entry_siz;\r
-\r
- /* return success */\r
- return TRUE;\r
- }\r
-\r
- /* skip this entry (header + name length) */\r
- p += 9 + entry_name_len;\r
- }\r
-\r
- /* \r
- * if we got this far, we didn't find the name; so skip past\r
- * the MRES section by adding the section length to the base\r
- * pointer, and resume the main file scan \r
- */\r
- p = blockp + siz;\r
- }\r
- else if (memcmp(p, "EOF ", 4) == 0)\r
- {\r
- /* \r
- * end of file - we've finished without finding the resource,\r
- * so return failure \r
- */\r
- return FALSE;\r
- }\r
- else\r
- {\r
- /* \r
- * we don't care about anything else - just skip this block and\r
- * keep going; to skip the block, simply seek ahead past the\r
- * block header and then past the block's contents, using the\r
- * size given the in block header \r
- */\r
- p += siz + 10;\r
- }\r
- }\r
-\r
- /* \r
- * reached EOF without an EOF marker - file must be corrupted; return\r
- * 'not found' \r
- */\r
- return FALSE;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * JPEG and PNG information extraction (based on the versions in\r
- * babel_story_functions.c) \r
- */\r
-static int get_jpeg_dim(const void *img, int32 extent,\r
- int32 *xout, int32 *yout)\r
-{\r
- const unsigned char *dp=(const unsigned char *) img;\r
- const unsigned char *ep=dp+extent;\r
- unsigned int t1, t2, w, h;\r
-\r
- t1 = *dp++;\r
- t2 = *dp++;\r
- if (t1 != 0xff || t2 != 0xD8)\r
- return FALSE;\r
-\r
- while(1)\r
- {\r
- if (dp>ep) return FALSE;\r
- for(t1=*(dp++);t1!=0xff;t1=*(dp++)) if (dp>ep) return FALSE;\r
- do { t1=*(dp++); if (dp>ep) return FALSE;} while (t1 == 0xff);\r
-\r
- if ((t1 & 0xF0) == 0xC0 && !(t1==0xC4 || t1==0xC8 || t1==0xCC))\r
- {\r
- dp+=3;\r
- if (dp>ep) return FALSE;\r
- h=*(dp++) << 8;\r
- if (dp>ep) return FALSE;\r
- h|=*(dp++);\r
- if (dp>ep) return FALSE;\r
- w=*(dp++) << 8;\r
- if (dp>ep) return FALSE;\r
- w|=*(dp);\r
-\r
- *xout = w;\r
- *yout = h;\r
- return TRUE;\r
- }\r
- else if (t1==0xD8 || t1==0xD9)\r
- break;\r
- else\r
- {\r
- int l;\r
-\r
- if (dp>ep) return FALSE;\r
- l=*(dp++) << 8;\r
- if (dp>ep) return FALSE;\r
- l|= *(dp++);\r
- l-=2;\r
- dp+=l;\r
- if (dp>ep) return FALSE;\r
- }\r
- }\r
- return FALSE;\r
-}\r
-\r
-static int32 png_read_int(const unsigned char *mem)\r
-{\r
- int32 i4 = mem[0],\r
- i3 = mem[1],\r
- i2 = mem[2],\r
- i1 = mem[3];\r
- return i1 | (i2<<8) | (i3<<16) | (i4<<24);\r
-}\r
-\r
-\r
-static int get_png_dim(const void *img, int32 extent,\r
- int32 *xout, int32 *yout)\r
-{\r
- const unsigned char *dp=(const unsigned char *)img;\r
-\r
- if (extent<33 ||\r
- !(dp[0]==137 && dp[1]==80 && dp[2]==78 && dp[3]==71 &&\r
- dp[4]==13 && dp[5] == 10 && dp[6] == 26 && dp[7]==10)||\r
- !(dp[12]=='I' && dp[13]=='H' && dp[14]=='D' && dp[15]=='R'))\r
- return FALSE;\r
-\r
- *xout = png_read_int(dp+16);\r
- *yout = png_read_int(dp+20);\r
- return TRUE;\r
-}\r
-\r
-/* ------------------------------------------------------------------------ */\r
-/*\r
- * Testing main() - this implements a set of unit tests on the tads\r
- * version. \r
- */\r
-\r
-#ifdef TADS_TEST\r
-\r
-#include "babel_handler.h"\r
-\r
-void main(int argc, char **argv)\r
-{\r
- FILE *fp;\r
- int32 siz;\r
- void *buf;\r
- valinfo *head;\r
- int32 rv;\r
- int tadsver;\r
- char outbuf[TREATY_MINIMUM_EXTENT];\r
-\r
- /* check arguments */\r
- if (argc != 2)\r
- {\r
- printf("usage: tads <game-file>\n");\r
- exit(1);\r
- }\r
-\r
- /* initialize the babel subsystems */\r
- babel_init(argv[1]);\r
-\r
- /* open the story file */\r
- if ((fp = fopen(argv[1], "rb")) == 0)\r
- {\r
- printf("error opening input file\n");\r
- exit(2);\r
- }\r
-\r
- /* check the file size */\r
- fseek(fp, 0, SEEK_END);\r
- siz = ftell(fp);\r
- fseek(fp, 0, SEEK_SET);\r
-\r
- /* allocate space for it */\r
- if ((buf = malloc(siz)) == 0)\r
- {\r
- printf("error allocating space to load file\n");\r
- exit(2);\r
- }\r
-\r
- /* load it */\r
- if ((int32)fread(buf, 1, siz, fp) != siz)\r
- {\r
- printf("error reading file\n");\r
- exit(2);\r
- }\r
-\r
- /* done with the file */\r
- fclose(fp);\r
-\r
-\r
-\r
- /* ===== test 1 - basic parse_game_info() test ===== */\r
-\r
- /* parse the gameinfo record and print the results */\r
- if ((head = parse_game_info(buf, siz, &tadsver)) != 0)\r
- {\r
- valinfo *val;\r
-\r
- printf("found GameInfo - tads major version = %d\n", tadsver);\r
- for (val = head ; val != 0 ; val = val->nxt)\r
- {\r
- printf("%.*s=[%.*s]\n",\r
- (int)val->name_len, val->name,\r
- (int)val->val_len, val->val);\r
- }\r
- printf("\n");\r
- }\r
- else\r
- printf("no GameInfo found\n\n");\r
-\r
-\r
-\r
- /* ===== test 2 - test the get_story_file_IFID generator ===== */\r
- rv = tads_get_story_file_IFID(buf, siz, outbuf, TREATY_MINIMUM_EXTENT);\r
- if (rv == 1)\r
- printf("IFID = [%s]\n\n", outbuf);\r
- else\r
- printf("IFID return code = %ld\n", rv);\r
-\r
-\r
-\r
- /* ===== test 3 - test the ifiction synthesizer ===== */\r
- if ((rv = tads_get_story_file_metadata_extent(buf, siz)) > 0)\r
- {\r
- char *ifbuf;\r
-\r
- /* try allocating the space */\r
- if ((ifbuf = malloc((size_t)rv)) != 0)\r
- {\r
- /* synthesize the story file */\r
- rv = tads_get_story_file_metadata(buf, siz, ifbuf, rv);\r
- if (rv > 0)\r
- printf("ifiction metadata:\n=====\n%.*s\n=====\n\n",\r
- (int)rv, ifbuf);\r
- else\r
- printf("tads_get_story_file_metadata result = %ld\n", rv);\r
- }\r
- else\r
- printf("unable to allocate %ld bytes for metadata record\n", rv);\r
- }\r
- else\r
- printf("tads_get_story_file_metadata_extent result code = %ld\n", rv);\r
- \r
-\r
- /* free the loaded story file buffer */\r
- free(buf);\r
-}\r
-\r
-\r
-#endif TADS_TEST\r
-\r