Пример #1
0
int CVmNetFile::can_write(VMG_ const char *fname, int sfid)
{
    /* note whether the file already exists */
    int existed = !osfacc(fname);

    /* try opening it (keeping existing contents) or creating it */
    osfildef *fp = osfoprwb(fname, OSFTBIN);
    if (fp != 0)
    {
        /* successfully opened it - close it */
        osfcls(fp);

        /* if it didn't already exist, delete it */
        if (!existed)
            osfdel(fname);

        /* success */
        return TRUE;
    }
    else
    {
        /* couldn't open the file */
        return FALSE;
    }
}
Пример #2
0
/*
 *   Look for a file in the "standard locations": current directory, program
 *   directory, PATH-like environment variables, etc.  The actual standard
 *   locations are specific to each platform; the implementation is free to
 *   use whatever conventions are appropriate to the local system.  On
 *   systems that have something like Unix environment variables, it might be
 *   desirable to define a TADS-specific variable (TADSPATH, for example)
 *   that provides a list of directories to search for TADS-related files.
 *   
 *   On return, fill in 'buf' with the full filename of the located copy of
 *   the file (if a copy was indeed found), in a format suitable for use with
 *   the osfopxxx() functions; in other words, after this function returns,
 *   the caller should be able to pass the contents of 'buf' to an osfopxxx()
 *   function to open the located file.
 *   
 *   Returns true (non-zero) if a copy of the file was located, false (zero)
 *   if the file could not be found in any of the standard locations.  
 */
int os_locate(const char *fname, int flen, const char *arg0,
              char *buf, size_t bufsiz)
{
	if (!osfacc(fname))
	{
		memcpy(buf, fname, flen);
		buf[flen] = 0;
		return TRUE;
	}
	return FALSE;
}
Пример #3
0
/*
 *   Rename a file
 */
void CVmNetFile::rename_to_local(VMG_ CVmNetFile *newname)
{
    /* if the new name isn't local, this isn't supported */
    if (newname->is_net_file())
        err_throw(VMERR_RENAME_FILE);

    /* if the destination file already exists, it's an error */
    if (!osfacc(newname->lclfname))
        err_throw(VMERR_RENAME_FILE);

    /* do the rename */
    if (!os_rename_file(lclfname, newname->lclfname))
        err_throw(VMERR_RENAME_FILE);
}
Пример #4
0
/*
 *   Given a game file argument, determine which engine (TADS 2 or TADS 3)
 *   should be used to run the game.  
 */
int vm_get_game_type(const char *filename,
                     char *actual_fname, size_t actual_fname_len,
                     const char *const *defexts, size_t defext_count)
{
    int good_count;
    int last_good_ver;
    size_t i;

    /* 
     *   If the exact filename as given exists, determine the file type
     *   directly from this file without trying any default extensions.
     */
    if (osfacc(filename) == 0)
    {
        /* the actual filename is exactly what we were given */
        if (actual_fname_len != 0)
        {
            /* copy the filename, limiting it to the buffer length */
            strncpy(actual_fname, filename, actual_fname_len);
            actual_fname[actual_fname_len - 1] = '\0';
        }

        /* return the type according to the file's signature */
        return vm_get_game_type_for_file(filename);
    }
    
    /* presume we won't find any good files using default extensions */
    good_count = 0;

    /* try each default extension supplied */
    for (i = 0 ; i < defext_count ; ++i)
    {
        int cur_ver;
        char cur_fname[OSFNMAX];

        /* 
         *   build the default filename from the given filename and the
         *   current default suffix 
         */
        strcpy(cur_fname, filename);
        os_defext(cur_fname, defexts[i]);

        /* get the version for this file */
        cur_ver = vm_get_game_type_for_file(cur_fname);
        
        /* if it's a valid code, note it and remember it */
        if (vm_ggt_is_valid(cur_ver))
        {
            /* it's a valid file - count it */
            ++good_count;

            /* remember its version as the last good file's version */
            last_good_ver = cur_ver;

            /* remember its name as the last good file's name */
            if (actual_fname_len != 0)
            {
                /* copy the filename, limiting it to the buffer length */
                strncpy(actual_fname, cur_fname, actual_fname_len);
                actual_fname[actual_fname_len - 1] = '\0';
            }
        }
    }

    /*
     *   If we had exactly one good match, return it.  We will already have
     *   filled in actual_fname with the last good filename, so all we need
     *   to do in this case is return the version ID for the last good file. 
     */
    if (good_count == 1)
        return last_good_ver;

    /*
     *   If we didn't find any matches, tell the caller there is no match
     *   for the given filename. 
     */
    if (good_count == 0)
        return VM_GGT_NOT_FOUND;

    /* we found more than one match, so the type is ambiguous */
    return VM_GGT_AMBIG;
}
Пример #5
0
/*
 *   Main Entrypoint for command-line invocations.  For simplicity, a
 *   normal C main() or equivalent entrypoint can invoke this routine
 *   directly, using the usual argc/argv conventions.
 *   
 *   Returns a status code suitable for use with exit(): OSEXSUCC if we
 *   successfully loaded and ran an executable, OSEXFAIL on failure.  If
 *   an error occurs, we'll fill in 'errbuf' with a message describing the
 *   problem.  
 */
int vm_run_image_main(CVmMainClientIfc *clientifc,
                      const char *executable_name,
                      int argc, char **argv, int defext, int test_mode,
                      CVmHostIfc *hostifc)
{
    int curarg;
    char image_file_name[OSFNMAX];
    int stat;
    const char *script_file;
    int script_quiet = TRUE;
    const char *log_file;
    const char *cmd_log_file;
    const char *res_dir;
    int load_from_exe;
    int show_banner;
    int found_image;
    int hide_usage;
    int usage_err;
    const char *charset;
    const char *log_charset;
    char *saved_state;

    /* we haven't found an image file yet */
    found_image = FALSE;

    /* presume we'll show usage on error */
    hide_usage = FALSE;

    /* presume there will be no usage error */
    usage_err = FALSE;

    /* presume we won't have any console input/output files */
    script_file = 0;
    log_file = 0;
    cmd_log_file = 0;

    /* presume we'll use the default OS character sets */
    charset = 0;
    log_charset = 0;

    /* presume we won't show the banner */
    show_banner = FALSE;

    /* presume we won't load from the .exe file */
    load_from_exe = FALSE;

    /* check to see if we can load from the .exe file */
    {
        osfildef *fp;

        /* look for an image file attached to the executable */
        fp = os_exeseek(argv[0], "TGAM");
        if (fp != 0)
        {
            /* close the file */
            osfcls(fp);
            
            /* note that we want to load from the executable */
            load_from_exe = TRUE;
        }
    }

    /* presume we won't restore a saved state file */
    saved_state = 0;

    /* presume we won't have a resource directory specified */
    res_dir = 0;

    /* scan options */
    for (curarg = 1 ; curarg < argc && argv[curarg][0] == '-' ; ++curarg)
    {
        /* 
         *   if the argument is just '-', it means we're explicitly leaving
         *   the image filename blank and skipping to the arguments to the VM
         *   program itself 
         */
        if (argv[curarg][1] == '\0')
            break;
        
        /* check the argument */
        switch(argv[curarg][1])
        {
        case 'b':
            if (strcmp(argv[curarg], "-banner") == 0)
            {
                /* make a note to show the banner */
                show_banner = TRUE;
            }
            else
                goto opt_error;
            break;

        case 'c':
            if (strcmp(argv[curarg], "-cs") == 0)
            {
                ++curarg;
                if (curarg < argc)
                    charset = argv[curarg];
                else
                    goto opt_error;
            }
            else if (strcmp(argv[curarg], "-csl") == 0)
            {
                ++curarg;
                if (curarg < argc)
                    log_charset = argv[curarg];
                else
                    goto opt_error;
            }
            else
                goto opt_error;
            break;

        case 'n':
            if (strcmp(argv[curarg], "-nobanner") == 0)
            {
                /* make a note not to show the banner */
                show_banner = FALSE;
            }
            else
                goto opt_error;
            break;
            
        case 's':
            /* file safety level - check the range */
            if (argv[curarg][2] < '0' || argv[curarg][2] > '4'
                || argv[curarg][3] != '\0')
            {
                /* invalid level */
                goto opt_error;
            }
            else
            {
                /* set the level in the host application */
                hostifc->set_io_safety(argv[curarg][2] - '0');
            }
            break;

        case 'i':
        case 'I':
            /* 
             *   read from a script file (little 'i' reads silently, big 'I'
             *   echoes the log as it goes) - the next argument, or the
             *   remainder of this argument, is the filename 
             */
            script_quiet = (argv[curarg][1] == 'i');
            script_file = get_opt_arg(argc, argv, &curarg, 2);
            if (script_file == 0)
                goto opt_error;
            break;

        case 'l':
            /* log output to file */
            log_file = get_opt_arg(argc, argv, &curarg, 2);
            if (log_file == 0)
                goto opt_error;
            break;

        case 'o':
            /* log commands to file */
            cmd_log_file = get_opt_arg(argc, argv, &curarg, 2);
            if (cmd_log_file == 0)
                goto opt_error;
            break;

        case 'p':
            /* check what follows */
            if (strcmp(argv[curarg], "-plain") == 0)
            {
                /* tell the client to set plain ASCII mode */
                clientifc->set_plain_mode();
                break;
            }
            else
                goto opt_error;
            break;

        case 'r':
            /* get the name of the saved state file to restore */
            saved_state = get_opt_arg(argc, argv, &curarg, 2);
            if (saved_state == 0)
                goto opt_error;
            break;

        case 'R':
            /* note the resource root directory */
            res_dir = get_opt_arg(argc, argv, &curarg, 2);
            if (res_dir == 0)
                goto opt_error;
            break;

        default:
        opt_error:
            /* discard remaining arguments */
            curarg = argc;

            /* note the error */
            usage_err = TRUE;
            break;
        }
    }

    /* 
     *   If there was no usage error so far, but we don't have an image
     *   filename argument, try to find the image file some other way.  If we
     *   found an image file embedded in the executable, don't even bother
     *   looking for an image-file argument - we can only run the embedded
     *   image in this case.  
     */
    if (usage_err)
    {
        /* there was a usage error - don't bother looking for an image file */
    }
    else if (!load_from_exe
             && curarg + 1 <= argc
             && strcmp(argv[curarg], "-") != 0)
    {
        /* the last argument is the image file name */
        strcpy(image_file_name, argv[curarg]);
        found_image = TRUE;

        /* 
         *   If the given filename exists, use it as-is; otherwise, if
         *   we're allowed to add an extension, try applying a default
         *   extension of "t3" (formerly "t3x") to the given name.  
         */
        if (defext && osfacc(image_file_name))
        {
            /* the given name doesn't exist - try a default extension */
            os_defext(image_file_name, "t3");             /* formerly "t3x" */
        }
    }
    else
    {
        /* 
         *   if we're loading from the executable, try using the executable
         *   filename as the image file 
         */
        if (load_from_exe
            && os_get_exe_filename(image_file_name, sizeof(image_file_name),
                                   argv[0]))
            found_image = TRUE;

        /* 
         *   If we still haven't found an image file, try to get the image
         *   file from the saved state file, if one was specified.  Don't
         *   attempt this if we're loading the image from the executable, as
         *   we don't want to allow running a different image file in that
         *   case.  
         */
        if (!load_from_exe && !found_image && saved_state != 0)
        {
            osfildef *save_fp;

            /* open the saved state file */
            save_fp = osfoprb(saved_state, OSFTT3SAV);
            if (save_fp != 0)
            {
                /* get the name of the image file */
                if (CVmSaveFile::restore_get_image(
                    save_fp, image_file_name, sizeof(image_file_name)) == 0)
                {
                    /* we successfully obtained the filename */
                    found_image = TRUE;
                }

                /* close the file */
                osfcls(save_fp);
            }
        }

        /* 
         *   if we haven't found the image, and the host system provides a
         *   way of asking the user for a filename, try that 
         */
        if (!load_from_exe && !found_image)
        {
            /* ask the host system for a game name */
            switch (hostifc->get_image_name(image_file_name,
                                            sizeof(image_file_name)))
            {
            case VMHOST_GIN_IGNORED:
                /* no effect - we have no new information */
                break;

            case VMHOST_GIN_CANCEL:
                /* 
                 *   the user cancelled the dialog - we don't have a
                 *   filename, but we also don't want to show usage, since
                 *   the user chose not to proceed 
                 */
                hide_usage = TRUE;
                break;
                
            case VMHOST_GIN_ERROR:
                /* 
                 *   an error occurred showing the dialog - there's not
                 *   much we can do except show the usage message 
                 */
                break;

            case VMHOST_GIN_SUCCESS:
                /* that was successful - we have an image file now */
                found_image = TRUE;
                break;
            }
        }
    }

    /* 
     *   if we don't have an image file name by this point, we can't
     *   proceed - show the usage message and terminate 
     */
    if (usage_err || !found_image)
    {
        char buf[OSFNMAX + 1024];

        /* show the usage message if allowed */
        if (load_from_exe && !usage_err)
        {
            sprintf(buf, "An error occurred loading the T3 VM program from "
                    "the embedded data file.  This application executable "
                    "file might be corrupted.\n");

            /* display the message */
            clientifc->display_error(0, buf, FALSE);
        }
        else if (!hide_usage)
        {
            /* build the usage message */
            sprintf(buf,
                    "%s\n"
                    "usage: %s [options] %sarguments]\n"
                    "options:\n"
                    "  -banner - show the version/copyright banner\n"
                    "  -cs xxx - use character set 'xxx' for keyboard "
                    "and display\n"
                    "  -csl xxx - use character set 'xxx' for log files\n"
                    "  -i file - read command input from file (quiet mode)\n"
                    "  -I file - read command input from file (echo mode)\n"
                    "  -l file - log all console input/output to file\n"
                    "  -o file - log console input to file\n"
                    "  -plain  - run in plain mode (no cursor positioning, "
                    "colors, etc.)\n"
                    "  -r file - restore saved state from file\n"
                    "  -R dir  - set directory for external resources\n"
                    "  -s#     - set I/O safety level (# in range 0 to 4 - 0 "
                    "is the least\n"
                    "            restrictive, 4 allows no file I/O at all)\n"
                    "\n"
                    "If provided, the optional extra arguments are passed "
                    "to the program's\n"
                    "main entrypoint.\n",
                    T3VM_BANNER_STRING, executable_name,
                    load_from_exe ? "[- " : "<image-file-name> [");
            
            /* display the message */
            clientifc->display_error(0, buf, FALSE);
        }
        
        /* return failure */
        return OSEXFAIL;
    }

    /* 
     *   if we're in test mode, replace the first argument to the program
     *   with its root name, so that we don't include any path information
     *   in the argument list 
     */
    if (test_mode && curarg <= argc && argv[curarg] != 0)
        argv[curarg] = os_get_root_name(argv[curarg]);
    
    /* run the program */
    stat = vm_run_image(clientifc, image_file_name, hostifc,
                        argv + curarg, argc - curarg,
                        script_file, script_quiet, log_file, cmd_log_file,
                        load_from_exe, show_banner,
                        charset, log_charset,
                        saved_state, res_dir);

    /* return the status code */
    return stat;
}
Пример #6
0
int CVmNetFile::exists(VMG_ const char *fname, int sfid)
{
    return !osfacc(fname);
}
Пример #7
0
/* Get the local time zone name, as a zoneinfo database key.  Many
 * modern Unix systems use zoneinfo keys as they native timezone
 * setting, but there are several different ways of storing this on a
 * per-process and system-wide basis.  We'll try looking for the common
 * mechanisms.
 */
int
os_get_zoneinfo_key( char *buf, size_t buflen )
{
    /* First, try the TZ environment variable.  This is used on nearly
     * all Unix-alikes for a per-process timezone setting, although
     * it will only contain a zoneinfo key in newer versions.  There
     * are several possible formats for specifying a zoneinfo key:
     *
     *  TZ=/usr/share/zoneinfo/America/Los_Angeles
     *    - A full absolute path name to a tzinfo file.  We'll sense
     *      this by looking for "/zoneinfo/" in the string, and if we
     *      find it, we'll return the portion after /zoneinfo/.
     *
     *  TZ=America/Los_Angeles
     *    - Just the zoneinfo key, without a path.  If we find a string
     *      that contains all alphabetics, undersores, and slashes, and
     *      has at least one internal slash but doesn't start with a
     *      slash, we probably have a zoneinfo key.  We'll see if we
     *      can find a matching file in the usual zoneinfo database
     *      locations: /etc/zoneinfo, /usr/share/zoneinfo; if we can,
     *      we'll return the key name, otherwise we'll assume this
     *      isn't actually a zoneinfo key but just happens to look like
     *      one in terms of format.
     *
     *  TZ=:America/Los_Angeles
     *  TZ=:/etc/zoneinfo/America/Los_Angeles
     *    - POSIX systems generally use the ":" prefix to signify that
     *      this is a zoneinfo path rather than the old-style "EST5EDT"
     *      type of self-contained zone description.  If we see a colon
     *      prefix with a relative path (properly formed in terms of
     *      its character content), we'll simply assume this is a
     *      zoneinfo key without even checking for an existing file,
     *      since there's not much else it could be.  If we see an
     *      absolute path, we'll search it for /zoneinfo/ and return
     *      the portion after this, again without checking for an
     *      existing file.
     */
    const char *tz = getenv("TZ");
    if (tz != 0 && tz[0] != '\0')
    {
        /* check that the string is formatted like a zoneinfo key */
#define tzcharok(c) (isalpha(c) != 0 || (c) == '/' || (c) == '_')
        int fmt_ok = TRUE;
        const char *p;
        fmt_ok &= (tz[0] == ':' || tzcharok(tz[0]));
        for (p = tz + 1 ; *p != '\0' ; ++p)
            fmt_ok &= tzcharok(*p);

        /* proceed only if it has the right format */
        if (fmt_ok)
        {
            /* check for a leading ':', per POSIX */
            if (tz[0] == ':')
            {
                /* yes, we have a leading ':', so it's almost certainly
                 * a zoneinfo key; if it's an absolute path, find the
                 * part after /zoneinfo/
                 */
                if (tz[1] == '/')
                {
                    /* absolute form - look for /zoneinfo/ */
                    const char *z = strstr(tz, "/zoneinfo/");
                    if (z != 0)
                    {
                        /* found it - return the part after /zoneinfo/ */
                        safe_strcpy(buf, buflen, z + 10);
                        return TRUE;
                    }
                }
                else
                {
                    /* relative path - return as-is minus the colon */
                    safe_strcpy(buf, buflen, tz + 1);
                    return TRUE;
                }
            }
            else
            {
                /* There's no colon, so it *might* be a zoneinfo key.
                 * If it's an absolute path containing /zoneinfo/, it's
                 * a solid bet.  If it's a relative path, look to see
                 * if we can find a file in one of the usual zoneinfo
                 * database locations.
                 */
                if (tz[0] == '/')
                {
                    /* absolute path - check for /zoneinfo/ */
                    const char *z = strstr(tz, "/zoneinfo/");
                    if (z != 0)
                    {
                        /* found it - return the part after /zoneinfo/ */
                        safe_strcpy(buf, buflen, z + 10);
                        return TRUE;
                    }
                }
                else
                {
                    /* relative path - look for a tzinfo file in the
                     * usual locations
                     */
                    static const char *dirs[] = {
                        "/etc/zoneinfo",
                        "/usr/share/zoneinfo",
                        0
                    };
                    const char **dir;
                    for (dir = dirs ; *dir != 0 ; ++dir)
                    {
                        /* build this full path */
                        char fbuf[OSFNMAX];
                        os_build_full_path(fbuf, sizeof(fbuf), *dir, tz);

                        /* check for a file at this location */
                        if (!osfacc(fbuf))
                        {
                            /* got it - looks like a good zoneinfo key */
                            safe_strcpy(buf, buflen, tz);
                            return TRUE;
                        }
                    }
                }
            }
        }
    }

    /* No luck with TZ, so try the system-wide settings next.
     *
     * If a file called /etc/timezone exists, it's usually a one-line
     * text file containing the zoneinfo key.  Read and return its
     * contents.
     */
    FILE *fp;
    if ((fp = fopen("/etc/timezone", "r")) != 0)
    {
        /* read the one-liner */
        char lbuf[256];
        int ok = FALSE;
        if (fgets(lbuf, sizeof(lbuf), fp) != 0)
        {
            /* strip any trailing newline */
            size_t l = strlen(lbuf);
            if (l != 0 && lbuf[l-1] == '\n')
                lbuf[l-1] = '\0';

            /* if it's in absolute format, return the part after
             * /zoneinfo/; otherwise just return the string
             */
            if (lbuf[0] == '/')
            {
                /* absoltue path - find /zoneinfo/ */
                const char *z = strstr(lbuf, "/zoneinfo/");
                if (z != 0)
                {
                    safe_strcpy(buf, buflen, z + 10);
                    ok = TRUE;
                }
            }
            else
            {
                /* relative notation - return it as-is */
                safe_strcpy(buf, buflen, lbuf);
                ok = TRUE;
            }
        }

        /* we're done with the file */
        fclose(fp);

        /* if we got our result, return success */
        if (ok)
            return TRUE;
    }

    /* If /etc/sysconfig/clock exists, read it and look for a line
     * starting with ZONE=.  This contains the zoneinfo key.
     */
    if ((fp = fopen("/etc/sysconfig/clock", "r")) != 0)
    {
        /* scan the file for ZONE=... */
        int ok = FALSE;
        for (;;)
        {
            /* read the next line */
            char lbuf[256];
            if (fgets(lbuf, sizeof(lbuf), fp) == 0)
                break;

            /* skip leading spaces */
            const char *p;
            for (p = lbuf ; isspace(*p) ; ++p) ;

            /* check for ZONE */
            if (memicmp(p, "zone", 4) != 0)
                continue;

            /* skip spaces after ZONE */
            for (p += 4 ; isspace(*p) ; ++p) ;

            /* check for '=' */
            if (*p != '=')
                continue;

            /* skip spaces after the '=' */
            for (++p ; isspace(*p) ; ++p) ;

            /* if it's in absolute form, look for /zoneinfo/ */
            if (*p == '/')
            {
                const char *z = strstr(p, "/zoneinfo/");
                if (z != 0)
                {
                    safe_strcpy(buf, buflen, z + 10);
                    ok = TRUE;
                }
            }
            else
            {
                /* relative notation - it's the zoneinfo key */
                safe_strcpy(buf, buflen, p);
                ok = TRUE;
            }

            /* that's our ZONE line, so we're done scanning the file */
            break;
        }

        /* done with the file */
        fclose(fp);

        /* if we got our result, return success */
        if (ok)
            return TRUE;
    }

    /* If /etc/localtime is a symbolic link, the linked file is the
     * actual zoneinfo file.  Resolve the link and return the portion
     * of the path after "/zoneinfo/".
     */
#if 0
    static const char *elt = "/etc/localtime";
    unsigned long mode;
    char linkbuf[OSFNMAX];
    const char *zi;
    if (osfmode(elt, FALSE, &mode, NULL)
        && (mode & OSFMODE_LINK) != 0
        && os_resolve_symlink(elt, linkbuf, sizeof(linkbuf))
        && (zi = strstr(linkbuf, "/zoneinfo/")) != 0)
    {
        /* it's a link containing /zoneinfo/, so return the portion
         * after /zoneinfo/
         */
        safe_strcpy(buf, buflen, zi + 10);
        return TRUE;
    }
#endif
    /* well, we're out of ideas - return failure */
    return FALSE;
}
Пример #8
0
/*
 *   Main program entrypoint 
 */
int main(int argc, char **argv)
{
    int curarg;
    int create;
    int recurse = TRUE;
    const char *image_fname;
    CRcResList *res_list = 0;
    rcmain_res_op_mode_t op_mode;
    int exit_stat;
    int err;
    MyHostIfc hostifc;
    char image_buf[OSFNMAX];

    /* assume we will operate on an existing file */
    create = FALSE;

    /* start out in add-recursive mode */
    op_mode = RCMAIN_RES_OP_MODE_ADD;

    /* scan options */
    for (curarg = 1 ; curarg < argc && argv[curarg][0] == '-' ; ++curarg)
    {
        switch(argv[curarg][1])
        {
        case 'c':
            if (strcmp(argv[curarg], "-create") == 0)
                create = TRUE;
            else
                goto bad_option;
            break;
            
        default:
        bad_option:
            /* invalid option - skip all arguments so we go to usage */
            curarg = argc;
            break;
        }
    }

    /* if there's nothing left, show usage */
    if (curarg >= argc)
    {
    show_usage:
        /* display usage */
        printf("usage: t3res [options] <image-file> [operations]\n"
               "Options:\n"
               "  -create      - create a new resource file\n"
               "Operations:\n"
               "  -add         - add the following resource files (default)\n"
               "  -recurse     - recursive - include files in "
               "subdirectories (default)\n"
               "  -norecurse   - do not include files in subdirectories\n"
               "  <file>       - add the file\n"
               "  <dir>        - add files in the directory\n"
               "  <file>=<res> - add file, using <res> as resource name\n"
               "\n"
               "-add is assumed if no conflicting option is specified.\n"
               "If no resource name is explicitly provided for a file, "
               "the resource is named\n"
               "by converting the filename to a URL-style resource name.\n");

        /* give up */
        exit_stat = OSEXFAIL;
        goto done;
    }

    /* get the image filename */
    image_fname = argv[curarg];

    /* create our resource list */
    res_list = new CRcResList();

    /* parse the operations list */
    for (++curarg ; curarg < argc ; ++curarg)
    {
        /* check for an option */
        if (argv[curarg][0] == '-')
        {
            /* see what we have */
            if (strcmp(argv[curarg], "-add"))
            {
                /* set 'add' mode */
                op_mode = RCMAIN_RES_OP_MODE_ADD;
            }
            else if (strcmp(argv[curarg], "-recurse"))
            {
                /* set recursive mode */
                recurse = TRUE;
            }
            else if (strcmp(argv[curarg], "-norecurse"))
            {
                /* set non-recursive mode */
                recurse = FALSE;
            }
            else
            {
                /* invalid option */
                goto show_usage;
            }
        }
        else
        {
            char *p;
            char *alias;
            
            /* check for an alias */
            for (p = argv[curarg] ; *p != '\0' && *p != '=' ; ++p) ;
            if (*p == '=')
            {
                /* 
                 *   overwrite the '=' with a null byte so that the
                 *   filename ends here 
                 */
                *p = '\0';

                /* the alias starts after the '=' */
                alias = p + 1;
            }
            else
            {
                /* there's no alias */
                alias = 0;
            }
            
            /* it's a file - add the file to the operations list */
            res_list->add_file(argv[curarg], alias, recurse);
        }
    }

    /* 
     *   if we're not creating, and the image doesn't exist, try adding
     *   the default image file extension 
     */
    if (!create && osfacc(image_fname))
    {
        strcpy(image_buf, image_fname);
        os_defext(image_buf, "t3");                       /* formerly "t3x" */
        image_fname = image_buf;
    }

    /* we've parsed the arguments - go apply the operations list */
    err = CResCompMain::add_resources(image_fname, res_list,
                                      &hostifc, create, OSFTT3IMG, FALSE);

    /* set the appropriate exit status */
    exit_stat = (err ? OSEXFAIL : OSEXSUCC);

done:
    /* delete the resource list if we created one */
    if (res_list != 0)
        delete res_list;
    
    /* show any unfreed memory (if we're in a debug build) */
    t3_list_memory_blocks(0);

    /* exit with current status */
    return exit_stat;
}