Merge branch 'gtk3'
[projects/chimara/chimara.git] / babel / tads.c
diff --git a/babel/tads.c b/babel/tads.c
deleted file mode 100644 (file)
index bde1e5a..0000000
+++ /dev/null
@@ -1,1827 +0,0 @@
-/* \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 '&lt;', '&gt;', and '&amp;',\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, "&lt;");\r
-            ++p, --len;\r
-            break;\r
-\r
-        case '>':\r
-            write_ifiction_z(ctx, "&gt;");\r
-            ++p, --len;\r
-            break;\r
-\r
-        case '&':\r
-            write_ifiction_z(ctx, "&amp;");\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