/* close a resource by writing next-res pointer */ static void fiowcls(osfildef *fp, errcxdef *ec, ulong respos) { ulong endpos; char buf[4]; endpos = osfpos(fp); osfseek(fp, respos, OSFSK_SET); oswp4(buf, endpos); if (osfwb(fp, buf, 4)) errsig(ec, ERR_WRTGAM); osfseek(fp, endpos, OSFSK_SET); }
/* * Find a multimedia resource with the given name in the given file. The * file must be positioned at the start of the tads game file when we're * invoked - this allows searching for a resource within a game file that * is embedded in a larger file stream, since we don't care where within * the osfildef stream the tads game file starts. * * Fills in the resource information structure with the seek offset and * size of the resource in the file and returns true if the resource is * found; returns false if a resource with the given name doesn't exist in * the file. */ int tads_find_resource_fp(osfildef *fp, const char *resname, tads_resinfo *info) { char buf[12]; /* read the signature */ if (!osfrb(fp, buf, 12)) { /* seek back to the start of the header */ osfseek(fp, -12, OSFSK_CUR); /* check which signature we have */ if (memcmp(buf, T2_SIG, strlen(T2_SIG)) == 0) { /* it's a tads 2 game file - read it accordingly */ return t2_find_res(fp, resname, info); } else if (memcmp(buf, T3_SIG, strlen(T3_SIG)) == 0) { /* it's a t3 image file - read it accordingly */ return t3_find_res(fp, resname, info); } } /* * if we get here, it means either that we couldn't read the * signature, or that we didn't recognize the signature - in either * case, we can't parse the file at all, so we can't find the resource */ return FALSE; }
/* * os_exfld - load in an external function from an open file, given the * size of the function (in bytes). Returns a pointer to the newly * allocated memory block containing the function in memory. */ int (*os_exfld(osfildef *fp, unsigned len))(void *) { void *extfn; unsigned alo; #ifdef MSOS2 /* for OS/2, don't load anything, but seek past the resource */ # ifdef MICROSOFT if (_osmode == OS2_MODE) # endif /* MICROSOFT */ { osfseek(fp, (long)len, OSFSK_SET); return((int (*)(void))0); } #endif /* MSOS2 */ #ifdef __DPMI16__ HANDLE selector; selector = GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, len); if (!selector || !(extfn = GlobalLock(selector))) return 0; /* read the file */ osfrb(fp, extfn, len); /* change the selector to a code segment */ _asm { mov bx, selector /* get selector */ lar ax, bx /* get current access rights */ mov cl, ah or cl, 8 /* set the CODE bit in the descriptor */ mov ax, 9 /* function = set descriptor rights */ mov ch, 0 int 31h } /* close the file and return the pointer to the function in memory */ return((int (*)(void *))extfn); #else /* __DPMI16 __ */ /* figure out how much memory is needed and allocate it */ alo = ((len + 0xf) & ~0xf) + 16; /* round to mult of 16, plus 1 page */ extfn = canon(malloc(alo));/* allocate the memory, canonicalize pointer */ if (!extfn) return((int (*)(void *))0 ); /* read the file */ osfrb(fp, extfn, len); /* close the file and return the pointer to the function in memory */ return((int (*)(void *))extfn); #endif /* __DPMI16__ */ }
/* * Try loading a resource from the executable file */ osfildef *CResLoader::open_exe_res(const char *respath, const char *restype) { osfildef *exe_fp; /* * if we don't have an executable filename stored, or we don't have an * executable resource type ID, we can't load the resource */ if (exe_filename_ == 0 || restype == 0) return 0; /* find the executable file's resources */ exe_fp = os_exeseek(exe_filename_, restype); /* if we found something, try loading from that file */ if (exe_fp != 0) { CVmImageLoaderMres_resload res_ifc(respath); /* try loading the resources */ CVmImageLoader:: load_resources_from_fp(exe_fp, exe_filename_, &res_ifc); /* check to see if we found it */ if (res_ifc.found_resource()) { /* check the type */ if (res_ifc.get_link_fname() != 0) { /* * it's a linked local file - close the exe file and open * the local file instead */ osfcls(exe_fp); exe_fp = osfoprb(res_ifc.get_link_fname(), OSFOPRB); } else { /* we got an exe resource - seek to the starting byte */ osfseek(exe_fp, res_ifc.get_resource_seek(), OSFSK_SET); } } else { /* didn't find it - close and forget the executable file */ osfcls(exe_fp); exe_fp = 0; } } /* return the executable file pointer, if we found the resource */ return exe_fp; }
/* * Try loading a resource from a resource library */ osfildef *CResLoader::open_lib_res(const char *libfile, const char *respath) { osfildef *fp; /* try opening the file */ fp = osfoprb(libfile, OSFTT3IMG); /* if we couldn't open the file, we can't load the resource */ if (fp == 0) return 0; /* set up a resource finder for our resource */ CVmImageLoaderMres_resload res_ifc(respath); /* load the file, so that we can try finding the resource */ CVmImageLoader::load_resources_from_fp(fp, libfile, &res_ifc); /* check to see if we found it */ if (res_ifc.found_resource()) { /* we got it - check the type */ if (res_ifc.get_link_fname() != 0) { /* * linked local file - close the library file and open the * local file instead */ osfcls(fp); fp = osfoprb(res_ifc.get_link_fname(), OSFOPRB); } else { /* embedded resource - seek to the first byte */ osfseek(fp, res_ifc.get_resource_seek(), OSFSK_SET); } /* return the library file handle */ return fp; } else { /* didn't find the resource - close the library */ osfcls(fp); /* tell the caller we didn't find the resource */ return 0; } }
/* copy a resource from the input file to the output file */ static void copyres(osfildef *fpin, osfildef *fpout, ulong siz, uint endpos_ofs) { ulong startpos; ulong endpos; uchar buf[4]; /* note the starting position */ startpos = osfpos(fpout); /* copy the bytes of the resource */ copybytes(fpin, fpout, siz); /* note the ending position */ endpos = osfpos(fpout); /* write the ending position at the appropriate point */ osfseek(fpout, (ulong)(startpos + endpos_ofs), OSFSK_SET); oswp4(buf, endpos); if (osfwb(fpout, buf, 4)) errexit("error writing resource", 1); /* seek back to the end of the output file */ osfseek(fpout, endpos, OSFSK_SET); }
/** * fileSeek * */ IEC_UINT fileSeek(IEC_UDINT hFile, IEC_UDINT ulOffset, IEC_INT iSet) { IEC_INT iOrigin; IEC_UINT uRes; switch (iSet) { case FSK_SEEK_CUR: iOrigin = VMF_SEEK_CUR; break; case FSK_SEEK_END: iOrigin = VMF_SEEK_END; break; case FSK_SEEK_SET: iOrigin = VMF_SEEK_SET; break; default: RETURN(ERR_INVALID_PARAM); } uRes = (IEC_UINT)(osfseek((VMF_FILE)hFile, ulOffset, iOrigin) == VMF_RET_OK ? OK : ERR_FILE_SEEK); RETURN(uRes); }
/* * Open a source file */ CTcSrcFile *CTcSrcFile::open_source(const char *filename, class CResLoader *res_loader, const char *default_charset, int *charset_error, int *default_charset_error) { char buf[275]; size_t siz; osfildef *fp; long startofs; CCharmapToUni *mapper; /* presume we won't find an invalid #charset directive */ *charset_error = FALSE; /* presume we'll have no problem with the default character set */ *default_charset_error = FALSE; /* * open the file in binary mode, so that we can scan the first few * bytes to see if we can detect the character set from information * at the beginning of the file */ fp = osfoprb(filename, OSFTTEXT); /* if we couldn't open the file, return failure */ if (fp == 0) return 0; /* note the starting offset in the file */ startofs = osfpos(fp); /* read the first few bytes of the file */ siz = osfrbc(fp, buf, sizeof(buf)); /* check for a 3-byte UTF-8 byte-order marker */ if (siz >= 3 && (uchar)buf[0] == 0xEF && (uchar)buf[1] == 0xBB && (uchar)buf[2] == 0xBF) { char *p; size_t rem; uint skip; /* skip at least the three-byte marker sequence */ skip = 3; /* * check for a #charset marker for utf-8 - this would be redundant, * but we'll allow it */ p = buf + 3; rem = siz - 3; if (rem > 9 && memcmp(p, "#charset ", 9) == 0) { /* skip spaces */ for (p += 9, rem -= 9 ; rem != 0 && (*p == ' ' || *p == '\t') ; ++p, --rem); /* check for valid character set markers */ if (rem >= 7 && memicmp(p, "\"utf-8\"", 7) == 0) { /* skip the whole sequence */ skip = (p + 7) - buf; } else if (rem >= 6 && memicmp(p, "\"utf8\"", 6) == 0) { /* skip the whole sequence */ skip = (p + 6) - buf; } } /* seek past the character set markers */ osfseek(fp, startofs + skip, OSFSK_SET); /* return a new utf-8 decoder */ return new CTcSrcFile(fp, new CCharmapToUniUTF8()); } /* if we read at least two bytes, try auto-detecting unicode */ if (siz >= 2) { CTcSrcFile *srcf; const char *const *cs_names; int bige; /* presume we won't find a byte-order marker */ srcf = 0; /* if the first bytes are 0xFF 0xFE, it's UCS-2 low-byte first */ if ((unsigned char)buf[0] == 0xFF && (unsigned char)buf[1] == 0xFE) { static const char *names[] = { "unicodel", "utf-16le", 0 }; /* create a UCS-2 little-endian reader */ srcf = new CTcSrcFile(fp, new CCharmapToUniUcs2Little()); bige = FALSE; cs_names = names; } /* if the first bytes are 0xFE 0xFF, it's UCS-2 high-byte first */ if ((unsigned char)buf[0] == 0xFE && (unsigned char)buf[1] == 0xFF) { static const char *names[] = { "unicodeb", "utf-16be", 0 }; /* create a UCS-2 little-endian reader */ srcf = new CTcSrcFile(fp, new CCharmapToUniUcs2Big()); bige = TRUE; cs_names = names; } /* if we found the byte-order marker, we know the character set */ if (srcf != 0) { uint skip; /* we at least want to skip the byte-order marker */ skip = 2; /* check to see if we have a '#charset' directive */ if (ucs_str_starts_with(buf + 2, siz - 2, "#charset ", bige, FALSE)) { char *p; size_t rem; /* scan past following spaces */ for (p = buf + 2 + 18, rem = siz - 2 - 18 ; rem >= 2 && (ucs_char_eq(p, ' ', bige, FALSE) || ucs_char_eq(p, '\t', bige, FALSE)) ; p += 2, rem -= 2) ; /* check for a '"' */ if (rem >= 2 && ucs_char_eq(p, '"', bige, FALSE)) { const char *const *n; /* skip the '"' */ p += 2; rem -= 2; /* * check for a match to any of the valid names for this * character set */ for (n = cs_names ; *n != 0 ; ++n) { /* if it's a match, stop scanning */ if (ucs_str_starts_with(p, rem, *n, bige, TRUE)) { size_t l; /* get the length of the name */ l = strlen(*n) * 2; /* check for a close quote */ if (rem >= l + 2 && ucs_char_eq(p + l, '"', bige, FALSE)) { /* skip the name and the quote */ p += l + 2; rem -= l + 2; /* skip the source text to this point */ skip = p - buf; /* stop scanning */ break; } } } } } /* seek just past the character set indicators */ osfseek(fp, startofs + skip, OSFSK_SET); /* return the file */ return srcf; } } /* * It doesn't appear to use UCS-2 encoding (at least, the file * doesn't start with a byte-order sensing sequence). Check to see * if the file starts with "#charset " in ASCII single-byte * characters. */ if (siz >= 9 && memcmp(buf, "#charset ", 9) == 0) { char *p; size_t rem; /* skip the #charset string and any following spaces */ for (p = buf + 9, rem = siz - 9 ; rem > 0 && (*p == ' ' || *p == '\t') ; ++p, --rem) ; /* make sure we're looking at a '"' */ if (rem != 0 && *p == '"') { char *charset_name; /* skip the open quote */ ++p; --rem; /* remember where the character set name starts */ charset_name = p; /* * find the closing quote, which must occur before a CR or * LF character */ for ( ; rem > 0 && *p != '"' && *p != 10 && *p != 13 ; ++p, --rem) ; /* make sure we found a matching quote */ if (rem != 0 && *p == '"') { /* seek just past the #charset string */ osfseek(fp, startofs + (p - buf) + 1, OSFSK_SET); /* * put a null terminator at the end of the character set * name */ *p = '\0'; /* create a mapper */ mapper = CCharmapToUni::load(res_loader, charset_name); /* * if that succeeded, return a reader for the mapper; * otherwise, simply proceed as though no #charset had * been present, so that we create a default mapper */ if (mapper != 0) { /* success - return a reader */ return new CTcSrcFile(fp, mapper); } else { /* tell the caller the #charset was invalid */ *charset_error = TRUE; } } } } /* * we didn't find any sensing codes, so seek back to the start of * the file */ osfseek(fp, startofs, OSFSK_SET); /* * We couldn't identify the file's character set based on anything * in the file, so create a mapper for the given default character * set. If there's not even a default character set defined, create * a plain ASCII mapper. */ if (default_charset != 0) mapper = CCharmapToUni::load(res_loader, default_charset); else mapper = new CCharmapToUniASCII(); /* check to see if we created a mapper */ if (mapper != 0) { /* return a source file reader based on the mapper */ return new CTcSrcFile(fp, mapper); } else { /* * we failed to create a mapper for the default character set - * flag the problem */ *default_charset_error = TRUE; /* close the input file */ osfcls(fp); /* return failure */ return 0; } }
/* * Parse a file */ int CTadsGameInfo::parse_file(osfildef *fp, unsigned long res_seek_pos, unsigned long res_size) { /* find the tail of the existing list */ tads_valinfo *last_val; for (last_val = first_val_ ; last_val != 0 && last_val->nxt != 0 ; last_val = last_val->nxt) ; /* we found the resource - seek to it in the file */ if (osfseek(fp, res_seek_pos, OSFSK_SET)) return FALSE; /* * Allocate a block of memory for loading the game information. The * game information resource is typically fairly small, so for * simplicity we'll just allocate a single block of memory and load * the whole resource into the block. Allocate space for one extra * byte, so that we can ensure we have a newline at the end of the * buffer. */ buf_ = (char *)osmalloc(res_size + 1); if (buf_ == 0) return FALSE; /* read the data */ if (osfrb(fp, buf_, res_size)) return FALSE; /* * store an extra newline at the end of the buffer, so that we can be * certain that the last line ends in a newline - at worst, this will * add an extra blank line to the end, but since we ignore blank lines * this will do no harm */ buf_[res_size++] = '\n'; /* parse the data */ utf8_ptr p(buf_); for (size_t rem = res_size ; rem != 0 ; ) { /* skip any leading whitespace */ while (rem != 0 && is_space(p.getch())) p.inc(&rem); /* if the line starts with '#', it's a comment, so skip it */ if (rem != 0 && p.getch() == '#') { /* skip the entire line, and go back for the next one */ skip_to_next_line(&p, &rem); continue; } /* we must have the start of a name - note it */ utf8_ptr name_start = p; /* skip ahead to a space or colon */ while (rem != 0 && p.getch() != ':' && !is_hspace(p.getch())) p.inc(&rem); /* note the length of the name */ size_t name_len = p.getptr() - name_start.getptr(); /* skip any whitespace before the presumed colon */ while (rem != 0 && is_hspace(p.getch())) p.inc(&rem); /* if we're not at a colon, the line is ill-formed, so skip it */ if (rem == 0 || p.getch() != ':') { /* skip the entire line, and go back for the next one */ skip_to_next_line(&p, &rem); continue; } /* skip the colon and any whitespace immediately after it */ for (p.inc(&rem) ; rem != 0 && is_hspace(p.getch()) ; p.inc(&rem)) ; /* * Whatever terminated the name, replace it with a null character * - this is safe, since we at least have a colon character to * replace, and we've already skipped it so we can overwrite it * now. A null character in utf-8 is simply a single zero byte, * so we can store our byte directly without worrying about any * utf-8 multi-byte strangeness. */ *(name_start.getptr() + name_len) = '\0'; /* note where the value starts */ utf8_ptr val_start = p; /* set up to write to the buffer at the current point */ utf8_ptr dst = p; /* * Now find the end of the value. The value can span multiple * lines; if we find a newline followed by a space, it means that * the next line is a continuation of the current value, and that * the entire sequence of newlines and immediately following * whitespace should be converted to a single space in the value. * * Note that we copy the transformed value directly over the old * version of the value in the buffer. This is safe because the * transformation can only remove characters - we merely collapse * each newline-whitespace sequence into a single space. */ while (rem != 0) { /* get this character */ wchar_t ch = p.getch(); /* check for a newline */ if (is_vspace(ch)) { /* * it's a newline - skip it (and any other characters in * the newline sequence) */ skip_newline(&p, &rem); /* * if there's no leading whitespace on the next line, * we've reached the end of this value */ if (rem == 0 || !is_hspace(p.getch())) { /* * no whitespace -> end of the value - stop scanning * the value */ break; } /* skip leading whitespace on the line */ while (rem != 0 && is_hspace(p.getch())) p.inc(&rem); /* * add a single whitespace character to the output for the * entire sequence of the newline plus the leading * whitespace on the line */ dst.setch(' '); } else if (ch == 0) { /* change null bytes to spaces */ dst.setch(' '); /* skip this character */ p.inc(&rem); } else { /* it's not a newline - simply copy it out and keep going */ dst.setch(ch); /* skip this character */ p.inc(&rem); } } /* * Store a null terminator at the end of the value (it's safe to * write this to the buffer because at worst it'll overwrite the * newline at the end of the last line, which we've already * skipped in the source). Note that we obtain the length of the * value string before storing the null terminator, because we * don't want to count the null byte in the value's length. */ dst.setch('\0'); /* * Create a new value list entry. Point the entry directly into * our buffer, since we're keeping the buffer around as long as * the value list is around. */ tads_valinfo *val_info = (tads_valinfo *)osmalloc(sizeof(tads_valinfo)); val_info->name = name_start.getptr(); val_info->name_len = name_len; val_info->val = store_value(val_start.getptr(), dst.getptr() - val_start.getptr()); val_info->nxt = 0; /* link the new value at the end of our list */ if (last_val != 0) last_val->nxt = val_info; else first_val_ = val_info; last_val = val_info; } /* success */ return TRUE; }
/* process an operation */ static void procop(osfildef *fpout, opdef *op, ulong *first_xfcn) { osfildef *fpin; char buf[128]; uint fsiz; ulong sizpos; ulong endpos; /* remember location of first resource if necessary */ if (fpout && *first_xfcn == 0) *first_xfcn = osfpos(fpout); fpin = osfoprb(op->opfile, OSFTGAME); if (fpin == 0) { rscptf("%s: ", op->opfile); errexit("unable to open file", 1); } /* get file size */ osfseek(fpin, 0L, OSFSK_END); fsiz = (uint)osfpos(fpin); osfseek(fpin, 0L, OSFSK_SET); /* set up the resource type part of the header */ switch(op->oprestype) { case RESTYPE_XFCN: buf[0] = 4; memcpy(buf + 1, "XFCN", 4); break; case RESTYPE_HTML: buf[0] = 4; memcpy(buf + 1, "HTML", 3); break; } /* set up the rest of the header */ if (osfwb(fpout, buf, buf[0] + 1)) errexit("error writing resource", 1); sizpos = osfpos(fpout); /* remember where size field goes */ oswp4(buf, 0); if (osfwb(fpout, buf, 4)) errexit("error writing resource", 1); /* set up the header */ oswp2(buf, fsiz); buf[2] = strlen(op->opres); strcpy(buf + 3, op->opres); if (osfwb(fpout, buf, (uint)(buf[2] + 3))) errexit("error writing resource", 1); /* copy the resource to the output */ copybytes(fpin, fpout, fsiz); /* write end position in the resource header */ endpos = osfpos(fpout); oswp4(buf, endpos); osfseek(fpout, sizpos, OSFSK_SET); if (osfwb(fpout, buf, 4)) errexit("error writing resource", 1); /* seek back to the end of the resource in the output file */ osfseek(fpout, endpos, OSFSK_SET); /* done with the input file */ osfcls(fpin); }
/* * Find a resource in a T3 image file */ static int t3_find_res(osfildef *fp, const char *resname, tads_resinfo *info) { char buf[256]; size_t resname_len; /* note the length of the name we're seeking */ resname_len = strlen(resname); /* * skip the file header - 11 bytes for the signature, 2 bytes for the * format version, 32 reserved bytes, and 24 bytes for the timestamp */ osfseek(fp, 11 + 2 + 32 + 24, OSFSK_CUR); /* scan the data blocks */ for (;;) { unsigned long siz; /* read the block header */ if (osfrb(fp, buf, 10)) return FALSE; /* get the block size */ siz = t3rp4u(buf + 4); /* check the type */ if (memcmp(buf, "MRES", 4) == 0) { unsigned long base_ofs; unsigned int entry_cnt; unsigned int i; /* * remember the current seek position - the data seek location * for each index entry is given as an offset from this * location */ base_ofs = osfpos(fp); /* read the number of entries */ if (osfrb(fp, buf, 2)) return FALSE; /* parse the entry count */ entry_cnt = osrp2(buf); /* read the entries */ for (i = 0 ; i < entry_cnt ; ++i) { unsigned long entry_ofs; unsigned long entry_siz; unsigned int entry_name_len; char *p; size_t rem; /* read this index entry's header */ if (osfrb(fp, buf, 9)) return FALSE; /* parse the header */ entry_ofs = t3rp4u(buf); entry_siz = t3rp4u(buf + 4); entry_name_len = (unsigned char)buf[8]; /* read the entry's name */ if (osfrb(fp, buf, entry_name_len)) return FALSE; /* XOR the bytes of the name with 0xFF */ for (p = buf, rem = entry_name_len ; rem != 0 ; ++p, --rem) *p ^= 0xFF; /* if this is the one we're looking for, return it */ if (entry_name_len == resname_len && memicmp(resname, buf, resname_len) == 0) { /* * fill in the return information - note that the * entry offset given in the header is an offset from * data block's starting location, so fix this up to * an absolute seek location for the return value */ info->seek_pos = base_ofs + entry_ofs; info->siz = entry_siz; /* return success */ return TRUE; } } } else if (memcmp(buf, "EOF ", 4) == 0) { /* * end of file - we've finished without finding the resource, * so return failure */ return FALSE; } else { /* * we don't care about anything else - just skip this block * and keep going; to skip the block, simply seek ahead by the * size of the block's contents as given in the block header */ osfseek(fp, siz, OSFSK_CUR); } } }
/* write game to binary file */ static void fiowrt1(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tokctx, tokthdef *tab, uchar *fmts, uint fmtl, osfildef *fp, uint flags, objnum preinit, int extc, uint prpcnt, char *filever) { int i; int j; int k; uchar buf[TOKNAMMAX + 50]; errcxdef *ec = vctx->voccxerr; ulong fpos; int obj; vocidef ***vpg; vocidef **v; objnum *sc; vocdef *voc; vocwdef *vw; vocdef **vhsh; struct tm *tblock; time_t timer; fiowcxdef cbctx; /* callback context for toktheach */ uint xor_seed = 63; uint xor_inc = 64; char *vsnhdr; uint vsnlen; char fastnamebuf[OSFNMAX]; /* fast-load record temp file name */ long flag_seek; /* generate appropriate file version */ switch(filever[0]) { case 'a': /* generate .GAM compatible with pre-2.0.15 runtimes */ vsnhdr = FIOVSNHDR2; vsnlen = sizeof(FIOVSNHDR2); xor_seed = 17; /* use old xor values */ xor_inc = 29; break; case 'b': /* generate 2.0.15+ format */ vsnhdr = FIOVSNHDR3; vsnlen = sizeof(FIOVSNHDR3); break; case 'c': case '*': /* current version */ vsnhdr = FIOVSNHDR; vsnlen = sizeof(FIOVSNHDR); break; default: errsig(ec, ERR_WRTVSN); } /* write file header and version header */ if (osfwb(fp, FIOFILHDR, sizeof(FIOFILHDR)) || osfwb(fp, vsnhdr, vsnlen)) errsig(ec, ERR_WRTGAM); /* * write the flags - remember where we wrote it in case we need to * change the flags later */ flag_seek = osfpos(fp); oswp2(buf, flags); if (osfwb(fp, buf, 2)) errsig(ec, ERR_WRTGAM); /* write the timestamp */ timer = time(NULL); tblock = localtime(&timer); strcpy(vctx->voccxtim, asctime(tblock)); if (osfwb(fp, vctx->voccxtim, (size_t)26)) errsig(ec, ERR_WRTGAM); /* write xor parameters if generating post 2.0.15 .GAM file */ if (filever[0] != 'a') { fpos = fiowhd(fp, ec, "\003XSI"); buf[0] = xor_seed; buf[1] = xor_inc; if (osfwb(fp, buf, 2)) errsig(ec, ERR_WRTGAM); fiowcls(fp, ec, fpos); } /* write count of external functions */ if (extc) { fpos = fiowhd(fp, ec, "\006EXTCNT"); oswp2(buf, extc); /* write the external function count */ if (osfwb(fp, buf, 2)) errsig(ec, ERR_WRTGAM); /* write XFCN-seek-location header if post 2.0.15 file version */ if (filever[0] != 'a') { oswp4(buf, 0); /* placeholder - TADSRSC sets this up later */ if (osfwb(fp, buf, 4)) errsig(ec, ERR_WRTGAM); } /* close the resource */ fiowcls(fp, ec, fpos); } /* * write the character set information, if a character set was * specified */ if (G_cmap_id[0] != '\0') { size_t len; /* this is not allowed with explicit file versions a, b, or c */ if (filever[0] == 'a' || filever[0] == 'b' || filever[0] == 'c') errsig(ec, ERR_VNOCTAB); /* write out the CHRSET resource header */ fpos = fiowhd(fp, ec, "\006CHRSET"); /* write out the ID and LDESC of the internal character set */ len = strlen(G_cmap_ldesc) + 1; oswp2(buf, len); if (osfwb(fp, G_cmap_id, 4) || osfwb(fp, buf, 2) || osfwb(fp, G_cmap_ldesc, len)) errsig(ec, ERR_WRTGAM); /* close the resource */ fiowcls(fp, ec, fpos); } /* write OBJ resource header */ fpos = fiowhd(fp, ec, "\003OBJ"); /* set up the symbol table callback context for writing the objects */ cbctx.fiowcxmem = mctx; cbctx.fiowcxerr = ec; cbctx.fiowcxfp = fp; cbctx.fiowcxund = 0; cbctx.fiowcxseed = xor_seed; cbctx.fiowcxinc = xor_inc; cbctx.fiowcxdebug = (flags & FIOFSYM); if (flags & FIOFFAST) { /* try creating the temp file */ cbctx.fiowcxffp = os_create_tempfile(0, fastnamebuf); /* if that failed, we don't have a fast-load table after all */ if (cbctx.fiowcxffp == 0) { long curpos; char flag_buf[2]; /* clear the fast-load flag */ flags &= ~FIOFFAST; /* * go back to the flags we wrote to the .gam file header, and * re-write the new flags without the fast-load bit - since * we're not going to write a fast-load table, we don't want * readers thinking they're going to find one */ /* first, remember where we are right now */ curpos = osfpos(fp); /* seek back to the flags and re-write the new flags */ osfseek(fp, flag_seek, OSFSK_SET); oswp2(flag_buf, flags); if (osfwb(fp, flag_buf, 2)) errsig(ec, ERR_WRTGAM); /* seek back to where we started */ osfseek(fp, curpos, OSFSK_SET); } } else cbctx.fiowcxffp = (osfildef *)0; cbctx.fiowcxflg = flags; /* go through symbol table, and write each object */ toktheach((toktdef *)tab, fiowrtobj, &cbctx); /* also write all objects created with 'new' */ for (vpg = vctx->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i) { objnum obj; if (!*vpg) continue; for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j) { /* if the object was dynamically allocated, write it out */ if (*v && (*v)->vociflg & VOCIFNEW) { toksdef t; /* clear the 'new' flag, as this is a static object now */ (*v)->vociflg &= ~VOCIFNEW; /* set up a toksdef, and write it out */ t.tokstyp = TOKSTOBJ; t.toksval = obj; fiowrtobj(&cbctx, &t); } } } /* close the resource */ fiowcls(fp, ec, fpos); /* copy fast-load records to output file if applicable */ if (cbctx.fiowcxffp) { osfildef *fp2 = cbctx.fiowcxffp; char copybuf[1024]; ulong len; ulong curlen; /* start with resource header for fast-load records */ fpos = fiowhd(fp, ec, "\003FST"); /* get length of temp file, then rewind it */ len = osfpos(fp2); osfseek(fp2, 0L, OSFSK_SET); /* copy the whole thing to the output file */ while (len) { curlen = (len > sizeof(copybuf) ? sizeof(copybuf) : len); if (osfrb(fp2, copybuf, (size_t)curlen) || osfwb(fp, copybuf, (size_t)curlen)) errsig(ec, ERR_WRTGAM); len -= curlen; } /* close the fast-load resource */ fiowcls(fp, ec, fpos); /* close and delete temp file */ osfcls(fp2); osfdel_temp(fastnamebuf); } /* write vocabulary inheritance records - start with header */ fpos = fiowhd(fp, ec, "\003INH"); /* go through inheritance records and write to file */ for (vpg = vctx->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i) { if (!*vpg) continue; for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j) { if (*v) { buf[0] = (*v)->vociflg; oswp2(buf + 1, obj); oswp2(buf + 3, (*v)->vociloc); oswp2(buf + 5, (*v)->vociilc); oswp2(buf + 7, (*v)->vocinsc); for (k = 0, sc = (*v)->vocisc ; k < (*v)->vocinsc ; ++sc, ++k) { if (k + 9 >= sizeof(buf)) errsig(ec, ERR_FIOMSC); oswp2(buf + 9 + (k << 1), (*sc)); } if (osfwb(fp, buf, 9 + (2 * (*v)->vocinsc))) errsig(ec, ERR_WRTGAM); } } } /* close resource */ fiowcls(fp, ec, fpos); /* write format strings */ if (fmtl) { fpos = fiowhd(fp, ec, "\006FMTSTR"); oswp2(buf, fmtl); if (flags & FIOFCRYPT) fioxor(fmts, fmtl, xor_seed, xor_inc); if (osfwb(fp, buf, 2) || osfwb(fp, fmts, fmtl)) errsig(ec, ERR_WRTGAM); fiowcls(fp, ec, fpos); } /* write preinit if preinit object was specified */ if (flags & FIOFPRE) { fpos = fiowhd(fp, ec, "\007PREINIT"); oswp2(buf, preinit); if (osfwb(fp, buf, 2)) errsig(ec, ERR_WRTGAM); fiowcls(fp, ec, fpos); } /* write required objects out of voccxdef */ fpos = fiowhd(fp, ec, "\003REQ"); fiowrq(ec, fp, vctx->voccxme); fiowrq(ec, fp, vctx->voccxvtk); fiowrq(ec, fp, vctx->voccxstr); fiowrq(ec, fp, vctx->voccxnum); fiowrq(ec, fp, vctx->voccxprd); fiowrq(ec, fp, vctx->voccxvag); fiowrq(ec, fp, vctx->voccxini); fiowrq(ec, fp, vctx->voccxpre); fiowrq(ec, fp, vctx->voccxper); fiowrq(ec, fp, vctx->voccxprom); fiowrq(ec, fp, vctx->voccxpdis); fiowrq(ec, fp, vctx->voccxper2); fiowrq(ec, fp, vctx->voccxpdef); fiowrq(ec, fp, vctx->voccxpask); fiowrq(ec, fp, vctx->voccxppc); fiowrq(ec, fp, vctx->voccxpask2); fiowrq(ec, fp, vctx->voccxperp); fiowrq(ec, fp, vctx->voccxpostprom); fiowrq(ec, fp, vctx->voccxinitrestore); fiowrq(ec, fp, vctx->voccxpuv); fiowrq(ec, fp, vctx->voccxpnp); fiowrq(ec, fp, vctx->voccxpostact); fiowrq(ec, fp, vctx->voccxendcmd); fiowrq(ec, fp, vctx->voccxprecmd); fiowrq(ec, fp, vctx->voccxpask3); fiowrq(ec, fp, vctx->voccxpre2); fiowrq(ec, fp, vctx->voccxpdef2); fiowcls(fp, ec, fpos); /* write compound words */ if (vctx->voccxcpl) { fpos = fiowhd(fp, ec, "\004CMPD"); oswp2(buf, vctx->voccxcpl); if (flags & FIOFCRYPT) fioxor((uchar *)vctx->voccxcpp, (uint)vctx->voccxcpl, xor_seed, xor_inc); if (osfwb(fp, buf, 2) || osfwb(fp, vctx->voccxcpp, (uint)vctx->voccxcpl)) errsig(ec, ERR_WRTGAM); fiowcls(fp, ec, fpos); } /* write special words */ if (vctx->voccxspl) { fpos = fiowhd(fp, ec, "\010SPECWORD"); oswp2(buf, vctx->voccxspl); if (flags & FIOFCRYPT) fioxor((uchar *)vctx->voccxspp, (uint)vctx->voccxspl, xor_seed, xor_inc); if (osfwb(fp, buf, 2) || osfwb(fp, vctx->voccxspp, (uint)vctx->voccxspl)) errsig(ec, ERR_WRTGAM); fiowcls(fp, ec, fpos); } /* write vocabulary */ fpos = fiowhd(fp, ec, "\003VOC"); /* go through each hash chain */ for (i = 0, vhsh = vctx->voccxhsh ; i < VOCHASHSIZ ; ++i, ++vhsh) { /* go through each word in this hash chain */ for (voc = *vhsh ; voc ; voc = voc->vocnxt) { /* encrypt the word if desired */ if (flags & FIOFCRYPT) fioxor(voc->voctxt, (uint)(voc->voclen + voc->vocln2), xor_seed, xor_inc); /* go through each object relation attached to the word */ for (vw = vocwget(vctx, voc->vocwlst) ; vw ; vw = vocwget(vctx, vw->vocwnxt)) { /* clear the 'new' flag, as this is a static object now */ vw->vocwflg &= ~VOCFNEW; /* write out this object relation */ oswp2(buf, voc->voclen); oswp2(buf+2, voc->vocln2); oswp2(buf+4, vw->vocwtyp); oswp2(buf+6, vw->vocwobj); oswp2(buf+8, vw->vocwflg); if (osfwb(fp, buf, 10) || osfwb(fp, voc->voctxt, voc->voclen + voc->vocln2)) errsig(ec, ERR_WRTGAM); } } } fiowcls(fp, ec, fpos); /* write the symbol table, if desired */ if (flags & FIOFSYM) { fpos = fiowhd(fp, ec, "\006SYMTAB"); toktheach((toktdef *)tab, fiowrtsym, &cbctx); /* indicate last symbol with a length of zero */ buf[0] = 0; if (osfwb(fp, buf, 4)) errsig(ec, ERR_WRTGAM); fiowcls(fp, ec, fpos); } /* write line source chain, if desired */ if ((flags & (FIOFLIN | FIOFLIN2)) != 0 && vctx->voccxrun->runcxdbg != 0) { lindef *lin; /* write the SRC header */ fpos = fiowhd(fp, ec, "\003SRC"); /* write the line records */ for (lin = vctx->voccxrun->runcxdbg->dbgcxlin ; lin ; lin = lin->linnxt) { /* write this record */ if (linwrt(lin, fp)) errsig(ec, ERR_WRTGAM); } /* done with this section */ fiowcls(fp, ec, fpos); /* * if we have new-style line records, put a SRC2 header (with no * block contents) in the file, so that the debugger realizes * that it has new-style line records (with line numbers rather * than seek offsets) */ if ((flags & FIOFLIN2) != 0) { fpos = fiowhd(fp, ec, "\004SRC2"); fiowcls(fp, ec, fpos); } } /* if writing a precompiled header, write some more information */ if (flags & FIOFBIN) { /* write property count */ fpos = fiowhd(fp, ec, "\006PRPCNT"); oswp2(buf, prpcnt); if (osfwb(fp, buf, 2)) errsig(ec, ERR_WRTGAM); fiowcls(fp, ec, fpos); /* write preprocessor symbol table */ fpos = fiowhd(fp, ec, "\006TADSPP"); tok_write_defines(tokctx, fp, ec); fiowcls(fp, ec, fpos); } /* write end-of-file resource header */ (void)fiowhd(fp, ec, "\004$EOF"); osfcls(fp); /* if there are undefined functions/objects, signal an error */ if (cbctx.fiowcxund) errsig(ec, ERR_UNDEF); }
/* process changes to resource file (or just list it) */ static opdef *rscproc(osfildef *fp, osfildef *fpout, opdef *oplist) { char buf[128]; ulong siz; char datebuf[27]; ulong endpos; uchar nambuf[40]; uint rsiz; opdef *op; int copyrsc; ulong startpos; uint endpos_ofs; ulong first_xfcn = 0; ulong extcnt_pos = 0; int found_user_rsc = FALSE; int showed_heading = FALSE; int found_htmlres = FALSE; char *file_type; /* * if we're reading an existing file, check it header; otherwise, * write out a brand new file header */ if (fp != 0) { /* * the input file exists -- check file and version headers, and * get flags and timestamp */ if (osfrb(fp, buf, (int)(sizeof(FIOFILHDR) + sizeof(FIOVSNHDR) + 2))) listexit(fp, "error reading file header"); /* check the file type */ if (memcmp(buf, FIOFILHDR, sizeof(FIOFILHDR)) == 0) file_type = "game"; else if (memcmp(buf, FIOFILHDRRSC, sizeof(FIOFILHDRRSC)) == 0) file_type = "resource"; else listexit(fp, "invalid resource file header"); /* check the version header */ if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR, sizeof(FIOVSNHDR)) && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2, sizeof(FIOVSNHDR2)) && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3, sizeof(FIOVSNHDR3))) listexit(fp, "incompatible resource file version"); /* get the timestamp */ if (osfrb(fp, datebuf, (size_t)26)) listexit(fp, "error reading file"); datebuf[26] = '\0'; } else { struct tm *tblock; time_t timer; /* construct a new file header */ memcpy(buf, FIOFILHDRRSC, sizeof(FIOFILHDRRSC)); memcpy(buf + sizeof(FIOFILHDR), FIOVSNHDR, sizeof(FIOVSNHDR)); /* clear the flags */ oswp2(buf + sizeof(FIOFILHDR) + sizeof(FIOVSNHDR), 0); /* construct the timestamp */ timer = time(0); tblock = localtime(&timer); strcpy(datebuf, asctime(tblock)); } if (fpout) { if (osfwb(fpout, buf, (int)(sizeof(FIOFILHDR) + sizeof(FIOVSNHDR) + 2)) || osfwb(fpout, datebuf, (size_t)26)) listexit(fp, "error writing header"); } /* if listing, show file creation timestamp */ if (fpout == 0) rscptf("\n" "File type: TADS %s file\n" "Date compiled: %s\n", file_type, datebuf); /* * Process the input file, if there is one */ for ( ; fp != 0 ; ) { /* assume this resource will be copied to the output */ copyrsc = TRUE; startpos = osfpos(fp); if (osfrb(fp, buf, 1) || osfrb(fp, buf + 1, (int)(buf[0] + 4))) listexit(fp, "error reading file"); memcpy(nambuf, buf + 1, (size_t)buf[0]); nambuf[buf[0]] = '\0'; endpos_ofs = 1 + buf[0]; endpos = osrp4(buf + endpos_ofs); siz = endpos - startpos; /* see what kind of resource we have, and do the right thing */ if (!strcmp((char *)nambuf, "$EOF")) { /* end of file marker - quit here */ break; } else if (!strcmp((char *)nambuf, "HTMLRES")) { /* if we've already found an HTMLRES list, it's an error */ if (found_htmlres) { rscptf("error: multiple HTMLRES maps found in file\n" " -- redundant entries have been deleted.\n"); copyrsc = FALSE; } else { /* go process it */ oplist = prochtmlres(fp, fpout, oplist, ©rsc, &showed_heading, TRUE); } /* note that we've found a resource */ found_user_rsc = TRUE; found_htmlres = TRUE; } else if (!strcmp((char *)nambuf, "EXTCNT")) { /* place to write start of XFCN's? */ if (siz >= 17 && fpout) extcnt_pos = osfpos(fpout); } else if (!strcmp((char *)nambuf, "XFCN")) { if (osfrb(fp, buf, 3) || osfrb(fp, buf + 3, (int)buf[2])) listexit(fp, "error reading file"); rsiz = osrp2(buf); buf[3 + buf[2]] = '\0'; if (fpout) { /* see if this resource is in the list */ for (op = oplist ; op ; op = op->opnxt) { if (!(op->opflag & OPFDONE) && op->oprestype == RESTYPE_XFCN && !stricmp((char *)buf + 3, op->opres)) { /* note that this resource has now been processed */ op->opflag |= OPFDONE; /* * if it's already here, and we're not deleting * it, warn that the old one will stay around */ if (!(op->opflag & OPFDEL)) { /* warn that we're going to ignore it */ rscptf("warning: XFCN resource \"%s\" already in " "file\n -- the old resource will be kept " "(use -replace to replace it)\n", op->opres); /* don't add it */ op->opflag &= ~OPFADD; } else { /* * we're deleting this resource; if adding * it back in (i.e., replacing it), process * the add operation now */ if (op->opflag & OPFADD) { /* show what we're doing */ show_op("replacing", op->opres, strlen(op->opres), op->oprestype); /* * add the external file, replacing the * one in the input file */ procop(fpout, op, &first_xfcn); } else { /* note that we're deleting it */ show_op("deleting", op->opres, strlen(op->opres), op->oprestype); } /* don't copy the one out of the file */ copyrsc = FALSE; } break; } } } else { /* no output file - just list the resource */ show_list_item(&showed_heading, "XFCN", (ulong)rsiz, buf + 3, (size_t)buf[2]); } /* note that we've found a user resource */ found_user_rsc = TRUE; } if (fpout != 0 && copyrsc) { osfseek(fp, startpos, OSFSK_SET); copyres(fp, fpout, siz, endpos_ofs); } /* skip to the next resource */ osfseek(fp, endpos, OSFSK_SET); } /* add the HTML resources if we haven't already */ if (!found_htmlres) oplist = prochtmlres(fp, fpout, oplist, ©rsc, &showed_heading, FALSE); /* now go through what's left, and add new non-HTML resources */ if (fpout != 0) { for (op = oplist ; op != 0 ; op = op->opnxt) { if (!(op->opflag & OPFDONE) && (op->opflag & OPFADD) && op->oprestype != RESTYPE_HTML) { /* show what we're doing */ show_op("adding", op->opres, strlen(op->opres), op->oprestype); /* add the file */ procop(fpout, op, &first_xfcn); /* mark the operation as completed */ op->opflag |= OPFDONE; } } } /* if just listing, and we didn't find anything, tell the user so */ if (fpout == 0 && !found_user_rsc) rscptf("No user resources found.\n"); /* done reading the file - finish it up */ if (fpout != 0) { /* write EOF resource */ if (osfwb(fpout, "\004$EOF\0\0\0\0", 9)) errexit("error writing resource", 1); /* write first_xfcn value to EXTCNT resource */ if (extcnt_pos) { osfseek(fpout, extcnt_pos + 13, OSFSK_SET); oswp4(buf, first_xfcn); osfwb(fpout, buf, 4); } } /* * return the final oplist (it may have been changed during * processing) */ return oplist; }
/* * Process an HTML resource list. If 'old_htmlres' is true, it * indicates that the input file is pointing to an old resource map; * otherwise, we need to construct a brand new one. */ static opdef *prochtmlres(osfildef *fp, osfildef *fpout, opdef *oplist, int *copyrsc, int *showed_heading, int old_htmlres) { opdef *op; opdef *add_list = 0; opdef *prev_op; opdef *next_op; int found; char buf[512]; ulong in_entry_cnt; ulong in_table_siz; ulong out_hdr_siz; ulong out_hdr_pos; ulong i; ulong out_res_cnt; ulong out_total_name_len; ulong rem; struct idx_t **in_list = 0, *out_list = 0, **p, *cur; ulong in_res_base, out_res_base; ulong out_endpos; /* * Scan the oplist for an HTML resource. If there aren't any, we * don't need to modify the HTMLRES list, so tell the caller to copy * this resource unchanged. */ for (op = oplist, found = FALSE ; op != 0 ; op = op->opnxt) { /* if this is an HTML resource, note it and stop looking */ if (op->oprestype == RESTYPE_HTML) { found = TRUE; break; } } /* * If we didn't find any operations on this resource, and we're not * simply listing resources or we don't have an old resource to * list, tell the caller to copy it unchanged. */ if (!found && (fpout != 0 || !old_htmlres)) { *copyrsc = TRUE; return oplist; } /* we'll be handling the resource - tell the caller not to copy it */ *copyrsc = FALSE; /* if there's an old HTMLRES resource, read it */ if (old_htmlres) { /* read the index entry count and size */ if (osfrb(fp, buf, 8)) listexit(fp, "unable to read HTMLRES header"); in_entry_cnt = osrp4(buf); in_table_siz = osrp4(buf + 4); /* allocate space for pointers to all of the entries */ if (in_entry_cnt != 0) { in_list = (struct idx_t **) malloc(in_entry_cnt * sizeof(struct idx_t *)); if (in_list == 0) listexit(fp, "unable to allocate space for HTMLRES entries"); } /* read the index table entries */ for (i = 0, p = in_list ; i < in_entry_cnt ; ++i, ++p) { ushort name_siz; ulong res_siz; ulong res_ofs; /* read the entry information */ if (osfrb(fp, buf, 10)) listexit(fp, "unable to read HTMLRES index table entry (prefix)"); /* get the resource size */ res_ofs = osrp4(buf); res_siz = osrp4(buf + 4); /* read the name */ name_siz = osrp2(buf + 8); if (name_siz > sizeof(buf)) listexit(fp, "name too large in HTMLRES index table entry"); if (osfrb(fp, buf, name_siz)) listexit(fp, "unable to read HTMLRES index table entry (name)"); /* build this entry */ *p = alloc_idx_entry(res_ofs, res_siz, buf, name_siz, 0, 0); } /* if we don't have an output file, list the HTMLRES contents */ if (fpout == 0) { /* display all of the entries */ for (i = 0, p = in_list ; i < in_entry_cnt ; ++i, ++p) show_list_item(showed_heading, "HTML", (*p)->siz, (*p)->nam, (*p)->namlen); /* there's no more processing to do */ goto done; } /* * The resources start at the end of the index table - note the * location of the end of the input table, since it's the base * address relative to which the resource offsets are stated. */ in_res_base = osfpos(fp); /* * Go through the resource table in the input file. Find each * one in the op list. If it's not in the op list, we'll copy * it to the output file. */ for (i = 0, p = in_list ; i < in_entry_cnt ; ++i, ++p) { int remove_res = FALSE; int add_res = FALSE; /* see if we can find this entry in the op list */ for (prev_op = 0, op = oplist ; op != 0 ; prev_op = op, op = op->opnxt) { /* if this one matches, note it */ if (op->oprestype == RESTYPE_HTML && strlen(op->opres) == (*p)->namlen && !memicmp(op->opres, (*p)->nam, (*p)->namlen)) { /* * if we're adding this resource (not replacing it), * warn that it's already in the file, and ignore * this op; if we're removing it or replacing it, * simply delete this entry from the input list so * it doesn't get copied to the output. */ if (!(op->opflag & OPFDEL)) { /* warn that the old one will stay */ rscptf("warning: HTML resource \"%s\" already " "present\n" " -- old version will be kept (use -replace " "to replace it)\n", op->opres); /* remove it from the processing list */ remove_res = TRUE; } else { /* we are deleting it; see if we're also adding it */ if (op->opflag & OPFADD) { /* * we're replacing this resource - take this * op out of the main list and put it into * the add list */ remove_res = TRUE; add_res = TRUE; /* note the addition */ show_op("replacing", op->opres, strlen(op->opres), op->oprestype); } else { /* note the deletion */ show_op("deleting", op->opres, strlen(op->opres), op->oprestype); /* just remove it */ remove_res = TRUE; } /* get rid of this item from the input list */ free(*p); *p = 0; } /* no need to look further in the operations list */ break; } } /* * If desired, remove this resource from the main list, and * add it into the list of resources to add. */ if (remove_res) { /* unlink it from the main list */ if (prev_op == 0) oplist = op->opnxt; else prev_op->opnxt = op->opnxt; /* if desired, add it to the additions list */ if (add_res) { /* we're adding it - put it in the additions list */ op->opnxt = add_list; add_list = op; } else { /* this item has been processed - delete it */ free(op); } } } } else { /* there are no entries in the input file */ in_entry_cnt = 0; in_table_siz = 0; } /* * Move all of the HTML resources marked as additions in the main * operations list into the additions list. */ for (prev_op = 0, op = oplist ; op != 0 ; op = next_op) { /* note the next op, in case we move this one to the other list */ next_op = op->opnxt; /* * if it's an HTML resource to be added, move it to the * additions list */ if (op->oprestype == RESTYPE_HTML && (op->opflag & OPFADD) != 0) { /* show what we're doing */ show_op("adding", op->opres, strlen(op->opres), op->oprestype); /* unlink it from the main list */ if (prev_op == 0) oplist = op->opnxt; else prev_op->opnxt = op->opnxt; /* add it to the additions list */ op->opnxt = add_list; add_list = op; /* * note that we don't want to advance the 'prev_op' pointer, * since we just removed this item - the previous item is * still the same as it was on the last iteration */ } else { /* * we're leaving this op in the original list - it's now the * previous op in the main list */ prev_op = op; } } /* * Figure out what we'll be putting in the HTMLRES list: we'll add * each surviving entry from the input file, plus all of the items * in the add list, plus all of the HTML items in the main list that * are marked for addition. */ out_res_cnt = 0; out_total_name_len = 0; /* count input file entries that we're going to copy */ for (i = 0, p = in_list ; i < in_entry_cnt ; ++i, ++p) { if (*p != 0) add_idx_entry(&out_list, &out_res_cnt, &out_total_name_len, 0, 0, (*p)->nam, (*p)->namlen, 0, *p); } /* * Count items in the additions list. Note that every HTML resource * marked for addition is in the additions list, since we moved all * such resources out of the main list and into the additions list * earlier. */ for (op = add_list ; op != 0 ; op = op->opnxt) add_idx_entry(&out_list, &out_res_cnt, &out_total_name_len, 0, 0, op->opres, (ushort)strlen(op->opres), op, 0); /* write the resource header */ if (osfwb(fpout, "\007HTMLRES\0\0\0\0", 12)) listexit(fp, "unable to write HTMLRES type header"); out_hdr_pos = osfpos(fpout); /* * Reserve space in the output file for the index table. We need * eight bytes for the index table prefix, then ten bytes per entry * plus the name sizes. */ out_hdr_siz = 8 + (10 * out_res_cnt) + out_total_name_len; /* write the index table prefix */ oswp4(buf, out_res_cnt); oswp4(buf + 4, out_hdr_siz); if (osfwb(fpout, buf, 8)) listexit(fp, "unable to write HTMLRES prefix"); /* * Reserve space for the headers. Don't actually write them yet, * since we don't know the actual locations and sizes of the * entries; for now, simply reserve the space, so that we can come * back here later and write the actual headers. Note that we * deduct the eight bytes we've already written from the amount of * filler to put in. */ for (rem = out_hdr_siz - 8 ; rem != 0 ; ) { ulong amt; /* write out a buffer full */ amt = (rem > sizeof(buf) ? sizeof(buf) : rem); if (osfwb(fpout, buf, amt)) listexit(fp, "unable to write HTMLRES header"); /* deduct the amount we wrote from the remainder */ rem -= amt; } /* * note the current position in the output file - this is the base * address of the resources */ out_res_base = osfpos(fpout); /* * Write the resources. */ for (cur = out_list ; cur != 0 ; cur = cur->nxt) { /* * note the current file position as an offset from the resource * base in the output file - this is the offset that we need to * store in the index entry for this object */ cur->ofs = osfpos(fpout) - out_res_base; /* * Copy the resource to the output. If it comes from the input * file, copy from there, otherwise go out and find the external * file and copy its contents. */ if (cur->src_op != 0) { osfildef *fpext; ulong fsiz; /* it comes from an external file - open the file */ fpext = osfoprb(cur->src_op->opfile, OSFTGAME); if (fpext == 0) { rscptf("%s: ", cur->src_op->opfile); errexit("unable to open file", 1); } /* figure the size of the file */ osfseek(fpext, 0L, OSFSK_END); fsiz = osfpos(fpext); osfseek(fpext, 0L, OSFSK_SET); /* copy the contents of the external file to the output */ copybytes(fpext, fpout, fsiz); /* the size is the same as the external file's size */ cur->siz = fsiz; /* done with the file */ osfcls(fpext); } else { /* * it comes from the input resource file - seek to the start * of the resource in the input file, and copy the data to * the output file */ osfseek(fp, in_res_base + cur->src_idx->ofs, OSFSK_SET); copybytes(fp, fpout, cur->src_idx->siz); /* the size is the same as in the input file */ cur->siz = cur->src_idx->siz; } } /* note the current output position - this is the end of the resource */ out_endpos = osfpos(fpout); /* * Now that we've written all of the resources and know their actual * layout in the file, we can go back and write the index table. */ osfseek(fpout, out_hdr_pos + 8, OSFSK_SET); for (cur = out_list ; cur != 0 ; cur = cur->nxt) { /* build this object's index table entry */ oswp4(buf, cur->ofs); oswp4(buf + 4, cur->siz); oswp2(buf + 8, cur->namlen); /* write the entry */ if (osfwb(fpout, buf, 10) || osfwb(fpout, cur->nam, cur->namlen)) listexit(fp, "unable to write HTML index table entry"); } /* * We're done building the resource; now all we need to do is go * back and write the ending position of the resource in the * resource header. */ osfseek(fpout, out_hdr_pos - 4, OSFSK_SET); oswp4(buf, out_endpos); if (osfwb(fpout, buf, 4)) errexit("error writing resource", 1); /* seek back to the end of the resource in the output file */ osfseek(fpout, out_endpos, OSFSK_SET); done: /* if we have an input list, free it */ if (in_list != 0) { /* delete all of the entries in the input table */ for (i = 0, p = in_list ; i < in_entry_cnt ; ++i, ++p) { /* delete this entry if we haven't already done so */ if (*p != 0) free(*p); } /* delete the input pointer list itself */ free(in_list); } /* * delete everything in the additions list, since we're done with * them now */ for (op = add_list ; op != 0 ; op = next_op) { /* note the next entry in the list */ next_op = op->opnxt; /* delete this entry */ free(op); } /* return the op list in its current form */ return oplist; }
/* * Open a plain ASCII file, with no #charset marker. */ CTcSrcFile *CTcSrcFile::open_plain(const char *filename) { osfildef *fp; char buf[5]; size_t siz; /* * open the file in binary mode, since we do all of the newline * interpretation explicitly */ if ((fp = osfoprb(filename, OSFTTEXT)) == 0) return 0; /* read the first few bytes of the file */ siz = osfrbc(fp, buf, sizeof(buf)); /* check for a 3-byte UTF-8 marker */ if (siz >= 3 && (uchar)buf[0] == 0xEF && (uchar)buf[1] == 0xBB && (uchar)buf[2] == 0xBF) { /* * seek to the byte after the marker, so that our caller won't see * the marker */ osfseek(fp, 3, OSFSK_SET); /* return a source file reader with a utf-8 mapper */ return new CTcSrcFile(fp, new CCharmapToUniUTF8()); } /* if we read at least two bytes, try auto-detecting UCS-2 */ if (siz >= 2) { /* if the first bytes are 0xFF 0xFE, it's UCS-2 low-byte first */ if ((unsigned char)buf[0] == 0xFF && (unsigned char)buf[1] == 0xFE) { /* seek to the byte after the marker */ osfseek(fp, 2, OSFSK_SET); /* return a reader with a little-endian mapper */ return new CTcSrcFile(fp, new CCharmapToUniUcs2Little()); } /* if the first bytes are 0xFE 0xFF, it's UCS-2 high-byte first */ if ((unsigned char)buf[0] == 0xFE && (unsigned char)buf[1] == 0xFF) { /* seek to the byte after the marker */ osfseek(fp, 2, OSFSK_SET); /* return a reader with a little-endian mapper */ return new CTcSrcFile(fp, new CCharmapToUniUcs2Big()); } } /* * there are no Unicode markers, so our only remaining option is plain * ASCII - return a source file object with a plain ASCII mapper */ return new CTcSrcFile(fp, new CCharmapToUniASCII()); }
int (*os_exfld(osfildef *fp, unsigned len))(void *) { /* NOT IMPLEMENTED - scan past the resource and fail */ osfseek(fp, (long)len, OSFSK_CUR); return 0; }
int main(int argc, char **argv) { osfildef *fp; const char *fname; const char *resname; tads_resinfo res_info; unsigned long rem; /* check usage */ if (argc != 3) { fprintf(stderr, "usage: resfind <filename> <resname>\n"); exit(2); } /* get the arguments */ fname = argv[1]; resname = argv[2]; /* open the file */ if ((fp = osfoprb(fname, OSFTGAME)) == 0 && (fp = osfoprb(fname, OSFTT3IMG)) == 0) { fprintf(stderr, "unable to open file \"%s\"\n", fname); exit(2); } /* find the resource */ if (!tads_find_resource_fp(fp, resname, &res_info)) { fprintf(stderr, "unable to find resource \"%s\"\n", resname); osfcls(fp); exit(1); } /* seek to the resource */ osfseek(fp, res_info.seek_pos, OSFSK_SET); /* copy the resource to standard output */ for (rem = res_info.siz ; rem != 0 ; ) { char buf[1024]; size_t cur; /* * read up to the buffer size or up to the resource size * remaining, whichever is smaller */ cur = sizeof(buf); if (cur > rem) cur = (size_t)rem; /* read the chunk */ if (osfrb(fp, buf, cur)) { fprintf(stderr, "error reading %u bytes from file\n", (unsigned)cur); osfcls(fp); exit(2); } /* copy the chunk to standard output */ fwrite(buf, cur, 1, stdout); /* deduct the amount we just read from the size remaining */ rem -= cur; } /* done with the file - close it */ osfcls(fp); /* success */ exit(0); return 0; }
/* * Given a saved game file, try to identify the game file that created the * saved game. */ static int vm_get_game_file_from_savefile(const char *savefile, char *fname, size_t fnamelen) { osfildef *fp; char buf[128]; int ret; size_t len; /* open the saved game file */ fp = osfoprb(savefile, OSFTBIN); /* if that failed, there's no way to read the game file name */ if (fp == 0) return FALSE; /* read the first few bytes */ if (osfrb(fp, buf, 16)) { /* * we couldn't even read that much, so it must not really be a * saved game file */ ret = FALSE; } else { /* check for a saved game signature we recognize */ if (memcmp(buf, "TADS2 save/g\012\015\032\000", 16) == 0) { /* * It's a TADS 2 saved game with embedded .GAM file * information. The filename immediately follows the signature * (the 15 bytes we just matched), with a two-byte length * prefix. Seek to the length prefix and read it. */ osfseek(fp, 16, OSFSK_SET); osfrb(fp, buf, 2); len = osrp2(buf); /* limit the read length to our caller's available buffer */ if (len > fnamelen - 1) len = fnamelen - 1; /* read the filename and null-terminate it */ osfrb(fp, fname, len); fname[len] = '\0'; /* success */ ret = TRUE; } else if (memcmp(buf, "T3-state-v", 10) == 0) { /* * It's a T3 saved state file. The image filename is always * embedded in this type of file, so seek back to the start of * the file and read the filename. * * Note that restore_get_image() returns zero on success, so we * want to return true if and only if that routine returns * zero. */ osfseek(fp, 0, OSFSK_SET); ret = (CVmSaveFile::restore_get_image(fp, fname, fnamelen) == 0); } else { /* * it's not a signature we know, so it must not be a saved * state file (at least not one we can deal with) */ ret = FALSE; } } /* we're done with the file now, so close it */ osfcls(fp); /* return the result */ return ret; }
/* * Find a resource in a tads 2 game file */ static int t2_find_res(osfildef *fp, const char *resname, tads_resinfo *info) { char buf[300]; unsigned long startpos; size_t resname_len; int found; /* we haven't found what we're looking for yet */ found = FALSE; /* note the length of the name we're seeking */ resname_len = strlen(resname); /* * note the seek location of the start of the tads 2 game file stream * within the file - if the game file is embedded in a larger file * stream, the seek locations we find within the file are relative to * this starting location */ startpos = osfpos(fp); /* * skip past the tads 2 file header (13 bytes for the signature, 7 * bytes for the version header, 2 bytes for the flags, 26 bytes for * the timestamp) */ osfseek(fp, 13 + 7 + 2 + 26, OSFSK_CUR); /* * scan the sections in the file; stop on $EOF, and skip everything * else but HTMLRES, which is the section type that */ for (;;) { unsigned long endofs; /* read the section type and next-section pointer */ if (osfrb(fp, buf, 1) || osfrb(fp, buf + 1, (int)((unsigned char)buf[0] + 4))) { /* failed to read it - give up */ return FALSE; } /* note the ending position of this section */ endofs = t3rp4u(buf + 1 + (unsigned char)buf[0]); /* check the type */ if (buf[0] == 7 && memcmp(buf+1, "HTMLRES", 7) == 0) { unsigned long entry_cnt; unsigned long i; /* * It's a multimedia resource block. Read the index table * header (which contains the number of entries and a reserved * uint32). */ if (osfrb(fp, buf, 8)) return FALSE; /* get the number of entries from the header */ entry_cnt = t3rp4u(buf); /* read the entries */ for (i = 0 ; i < entry_cnt ; ++i) { unsigned long res_ofs; unsigned long res_siz; unsigned short name_len; /* read the entry header */ if (osfrb(fp, buf, 10)) return FALSE; /* parse the header */ res_ofs = t3rp4u(buf); res_siz = t3rp4u(buf + 4); name_len = osrp2(buf + 8); /* read the entry's name */ if (name_len > sizeof(buf) || osfrb(fp, buf, name_len)) return FALSE; /* * if it matches the name we're looking for, note that we * found it */ if (name_len == resname_len && memicmp(resname, buf, name_len) == 0) { /* * note that we found it, and note its resource size * and offset in the return structure - but keep * scanning the rest of the directory, since we need * to know where the directory ends to know where the * actual resources begin */ found = TRUE; info->seek_pos = res_ofs; info->siz = res_siz; } } /* * if we found our resource, the current seek position is the * base of the offset we found in the directory; so fix up the * offset to give the actual file location */ if (found) { /* fix up the offset with the actual file location */ info->seek_pos += osfpos(fp); /* tell the caller we found it */ return TRUE; } /* we didn't find it - seek to the end of this section */ osfseek(fp, endofs + startpos, OSFSK_SET); } else if (buf[0] == 4 && memcmp(buf+1, "$EOF", 4) == 0) { /* * that's the end of the file - we've finished without finding * the resource, so return failure */ return FALSE; } else { /* * this isn't a section we're interested in - skip to the end * of the section and keep going */ osfseek(fp, endofs + startpos, OSFSK_SET); } } }