diff options
author | Paul Gilbert | 2019-05-17 14:48:01 -1000 |
---|---|---|
committer | Paul Gilbert | 2019-05-24 18:21:06 -0700 |
commit | f607792fa4e1f024dd8265034ac84425bde4aee7 (patch) | |
tree | 19372287bea6cc0b4cbfdc717510cc9419fd9b04 /engines/glk/tads/tads2/file_io.cpp | |
parent | 105a1b94bd9d5a0f10752e135671f4e9a4b0d8da (diff) | |
download | scummvm-rg350-f607792fa4e1f024dd8265034ac84425bde4aee7.tar.gz scummvm-rg350-f607792fa4e1f024dd8265034ac84425bde4aee7.tar.bz2 scummvm-rg350-f607792fa4e1f024dd8265034ac84425bde4aee7.zip |
GLK: TADS2: More code files implemented
Diffstat (limited to 'engines/glk/tads/tads2/file_io.cpp')
-rw-r--r-- | engines/glk/tads/tads2/file_io.cpp | 1742 |
1 files changed, 1742 insertions, 0 deletions
diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp index 581601fbf0..d82f67abea 100644 --- a/engines/glk/tads/tads2/file_io.cpp +++ b/engines/glk/tads/tads2/file_io.cpp @@ -21,12 +21,1754 @@ */ #include "glk/tads/tads2/file_io.h" +#include "glk/tads/tads2/appctx.h" +#include "glk/tads/tads2/character_map.h" +#include "glk/tads/tads2/error.h" +#include "glk/tads/tads2/memory_cache_heap.h" +#include "glk/tads/tads2/os.h" +#include "glk/tads/tads2/run.h" +#include "glk/tads/tads2/tokenizer.h" +#include "glk/tads/tads2/vocabulary.h" +#include "glk/tads/os_glk.h" namespace Glk { namespace TADS { namespace TADS2 { +/* compare a resource string */ +/* int fioisrsc(uchar *filbuf, char *refnam); */ +#define fioisrsc(filbuf, refnam) \ + (((filbuf)[0] == strlen(refnam)) && \ + !memcmp(filbuf+1, refnam, (size_t)((filbuf)[0]))) + +/* callback to load an object on demand */ +void OS_LOADDS fioldobj(void *ctx0, mclhd handle, uchar *ptr, ushort siz) +{ + fiolcxdef *ctx = (fiolcxdef *)ctx0; + ulong seekpos = (ulong)handle; + osfildef *fp = ctx->fiolcxfp; + char buf[7]; + errcxdef *ec = ctx->fiolcxerr; + uint rdsiz; + + /* figure out what type of object is to be loaded */ + osfseek(fp, seekpos + ctx->fiolcxst, OSFSK_SET); + if (osfrb(fp, buf, 7)) errsig(ec, ERR_LDGAM); + switch(buf[0]) + { + case TOKSTFUNC: + rdsiz = osrp2(buf + 3); + break; + + case TOKSTOBJ: + rdsiz = osrp2(buf + 5); + break; + + case TOKSTFWDOBJ: + case TOKSTFWDFN: + default: + errsig(ec, ERR_UNKOTYP); + } + + if (siz < rdsiz) errsig(ec, ERR_LDBIG); + if (osfrb(fp, ptr, rdsiz)) errsig(ec, ERR_LDGAM); + if (ctx->fiolcxflg & FIOFCRYPT) + fioxor(ptr, rdsiz, ctx->fiolcxseed, ctx->fiolcxinc); +} + +/* shut down load-on-demand subsystem (close load file) */ +void fiorcls(fiolcxdef *ctx) +{ + if (ctx != 0 && ctx->fiolcxfp != 0) + { + /* close the file */ + osfcls(ctx->fiolcxfp); + + /* forget the file object */ + ctx->fiolcxfp = 0; + } +} + +/* + * Read an HTMLRES resource map + */ +static void fiordhtml(errcxdef *ec, osfildef *fp, appctxdef *appctx, + int resfileno, const char *resfilename) +{ + uchar buf[256]; + + /* + * resource map - if the host system is interested, tell it about it + */ + if (appctx != 0) + { + ulong entry_cnt; + ulong i; + + /* read the index table header */ + if (osfrb(fp, buf, 8)) + errsig1(ec, ERR_RDRSC, ERRTSTR, + errstr(ec, resfilename, strlen(resfilename))); + + /* get the number of entries in the table */ + entry_cnt = osrp4(buf); + + /* read the index entries */ + for (i = 0 ; i < entry_cnt ; ++i) + { + ulong res_ofs; + ulong res_siz; + ushort res_namsiz; + + /* read this entry */ + if (osfrb(fp, buf, 10)) + errsig1(ec, ERR_RDRSC, ERRTSTR, + errstr(ec, resfilename, strlen(resfilename))); + + /* get the entry header */ + res_ofs = osrp4(buf); + res_siz = osrp4(buf + 4); + res_namsiz = osrp2(buf + 8); + + /* read this entry's name */ + if (osfrb(fp, buf, res_namsiz)) + errsig1(ec, ERR_RDRSC, ERRTSTR, + errstr(ec, resfilename, strlen(resfilename))); + + /* tell the host system about this entry */ + if (appctx->add_resource) + (*appctx->add_resource)(appctx->add_resource_ctx, + res_ofs, res_siz, + (char *)buf, + (size_t)res_namsiz, + resfileno); + } + + /* tell the host system where the resources start */ + if (appctx->set_resmap_seek != 0) + { + long pos = osfpos(fp); + (*appctx->set_resmap_seek)(appctx->set_resmap_seek_ctx, + pos, resfileno); + } + } +} + +/* + * Read an external resource file. This is a limited version of the + * general file reader that can only read resource files, not full game + * files. + */ +static void fiordrscext(errcxdef *ec, osfildef *fp, appctxdef *appctx, + int resfileno, char *resfilename) +{ + uchar buf[TOKNAMMAX + 50]; + unsigned long endpos; + unsigned long startofs; + + /* note the starting offset */ + startofs = osfpos(fp); + + /* check file and version headers, and get flags and timestamp */ + if (osfrb(fp, buf, (int)(sizeof(FIOFILHDR) + sizeof(FIOVSNHDR) + 2))) + errsig1(ec, ERR_RDRSC, ERRTSTR, + errstr(ec, resfilename, strlen(resfilename))); + if (memcmp(buf, FIOFILHDRRSC, (size_t)sizeof(FIOFILHDRRSC))) + errsig1(ec, ERR_BADHDRRSC, ERRTSTR, + errstr(ec, resfilename, strlen(resfilename))); + if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR, + (size_t)sizeof(FIOVSNHDR)) + && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2, + (size_t)sizeof(FIOVSNHDR2)) + && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3, + (size_t)sizeof(FIOVSNHDR3))) + errsig(ec, ERR_BADVSN); + if (osfrb(fp, buf, (size_t)26)) + errsig1(ec, ERR_RDRSC, ERRTSTR, + errstr(ec, resfilename, strlen(resfilename))); + + /* now read resources from the file */ + for (;;) + { + /* read resource type and next-resource pointer */ + if (osfrb(fp, buf, 1) + || osfrb(fp, buf + 1, (int)(buf[0] + 4))) + errsig1(ec, ERR_RDRSC, ERRTSTR, + errstr(ec, resfilename, strlen(resfilename))); + endpos = osrp4(buf + 1 + buf[0]); + + /* check the resource type */ + if (fioisrsc(buf, "HTMLRES")) + { + /* read the HTML resource map */ + fiordhtml(ec, fp, appctx, resfileno, resfilename); + + /* + * skip the resources - they're entirely for the host + * application's use + */ + osfseek(fp, endpos + startofs, OSFSK_SET); + } + else if (fioisrsc(buf, "$EOF")) + { + /* we're done reading the file */ + break; + } + else + errsig(ec, ERR_UNKRSC); + } +} + +/* + * read a game from a binary file + * + * flags: + * &1 ==> run preinit + * &2 ==> preload objects + */ +static void fiord1(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, + osfildef *fp, const char *fname, + fiolcxdef *setupctx, ulong startofs, + objnum *preinit, uint *flagp, tokpdef *path, + uchar **fmtsp, uint *fmtlp, uint *pcntptr, int flags, + appctxdef *appctx, char *argv0) +{ + int i; + int siz; + uchar buf[TOKNAMMAX + 50]; + errcxdef *ec = vctx->voccxerr; + ulong endpos; + int obj; + ulong curpos; + runxdef *ex; + ulong eof_reset = 0; /* reset here at EOF if non-zero */ +#if 0 // XFCNs are obsolete + int xfcns_done = FALSE; /* already loaded XFCNs */ +#endif + ulong xfcn_pos = 0; /* location of XFCN's if preloadable */ + uint xor_seed = 17; /* seed value for fioxor */ + uint xor_inc = 29; /* increment value for fioxor */ + + /* set up loader callback context */ + setupctx->fiolcxfp = fp; + setupctx->fiolcxerr = ec; + setupctx->fiolcxst = startofs; + setupctx->fiolcxseed = xor_seed; + setupctx->fiolcxinc = xor_inc; + + /* check file and version headers, and get flags and timestamp */ + if (osfrb(fp, buf, (int)(sizeof(FIOFILHDR) + sizeof(FIOVSNHDR) + 2))) + errsig(ec, ERR_RDGAM); + if (memcmp(buf, FIOFILHDR, (size_t)sizeof(FIOFILHDR))) + errsig(ec, ERR_BADHDR); + if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR, + (size_t)sizeof(FIOVSNHDR)) + && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2, + (size_t)sizeof(FIOVSNHDR2)) + && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3, + (size_t)sizeof(FIOVSNHDR3))) + errsig(ec, ERR_BADVSN); + if (osfrb(fp, vctx->voccxtim, (size_t)26)) errsig(ec, ERR_RDGAM); + + /* + * if the game wasn't compiled with 2.2 or later, make a note, + * because we need to ignore certain property flags (due to a bug in + * the old compiler) + */ + if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2, + (size_t)sizeof(FIOVSNHDR2)) == 0 + || memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3, + (size_t)sizeof(FIOVSNHDR3)) == 0) + mctx->mcmcxflg |= MCMCXF_NO_PRP_DEL; + + setupctx->fiolcxflg = + *flagp = osrp2(buf + sizeof(FIOFILHDR) + sizeof(FIOVSNHDR)); + + /* now read resources from the file */ + for (;;) + { + /* read resource type and next-resource pointer */ + if (osfrb(fp, buf, 1) + || osfrb(fp, buf + 1, (int)(buf[0] + 4))) + errsig(ec, ERR_RDGAM); + endpos = osrp4(buf + 1 + buf[0]); + + if (fioisrsc(buf, "OBJ")) + { + /* skip regular objects if fast-load records are included */ + if (*flagp & FIOFFAST) + { + osfseek(fp, endpos + startofs, OSFSK_SET); + continue; + } + + curpos = osfpos(fp) - startofs; + while (curpos != endpos) + { + /* read type and object number */ + if (osfrb(fp, buf, 3)) errsig(ec, ERR_RDGAM); + obj = osrp2(buf+1); + + switch(buf[0]) + { + case TOKSTFUNC: + case TOKSTOBJ: + if (osfrb(fp, buf + 3, 4)) errsig(ec, ERR_RDGAM); + mcmrsrv(mctx, (ushort)osrp2(buf + 3), (mcmon)obj, + (mclhd)curpos); + curpos += osrp2(buf + 5) + 7; + + /* load object if preloading */ + if (flags & 2) + { + (void)mcmlck(mctx, (mcmon)obj); + mcmunlck(mctx, (mcmon)obj); + } + + /* seek past this object */ + osfseek(fp, curpos + startofs, OSFSK_SET); + break; + + case TOKSTFWDOBJ: + case TOKSTFWDFN: + { + ushort bsiz; + uchar *p; + + if (osfrb(fp, buf+3, 2)) errsig(ec, ERR_RDGAM); + bsiz = osrp2(buf+3); + p = mcmalonum(mctx, bsiz, (mcmon)obj); + if (osfrb(fp, p, bsiz)) errsig(ec, ERR_RDGAM); + mcmunlck(mctx, (mcmon)obj); + curpos += 5 + bsiz; + break; + } + + case TOKSTEXTERN: + if (!vctx->voccxrun->runcxext) + errsig(ec, ERR_UNXEXT); + ex = &vctx->voccxrun->runcxext[obj]; + + if (osfrb(fp, buf + 3, 1) + || osfrb(fp, ex->runxnam, (int)buf[3])) + errsig(ec, ERR_RDGAM); + ex->runxnam[buf[3]] = '\0'; + curpos += buf[3] + 4; + break; + + default: + errsig(ec, ERR_UNKOTYP); + } + } + } + else if (fioisrsc(buf, "FST")) + { + uchar *p; + uchar *bufp; + ulong bsiz; + + if (!(*flagp & FIOFFAST)) + { + osfseek(fp, endpos + startofs, OSFSK_SET); + continue; + } + + curpos = osfpos(fp) - startofs; + bsiz = endpos - curpos; + if (bsiz && bsiz < OSMALMAX + && (bufp = p = (uchar *)osmalloc((size_t)bsiz)) != 0) + { + uchar *p1; + ulong siz2; + uint sizcur; + + for (p1 = p, siz2 = bsiz ; siz2 ; siz2 -= sizcur, p1 += sizcur) + { + sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2); + if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM); + } + + while (bsiz) + { + obj = osrp2(p + 1); + switch(*p) + { + case TOKSTFUNC: + case TOKSTOBJ: + mcmrsrv(mctx, (ushort)osrp2(p + 3), (mcmon)obj, + (mclhd)osrp4(p + 7)); + p += 11; + bsiz -= 11; + + /* preload object if desired */ + if (flags & 2) + { + (void)mcmlck(mctx, (mcmon)obj); + mcmunlck(mctx, (mcmon)obj); + } + break; + + case TOKSTEXTERN: + if (!vctx->voccxrun->runcxext) + errsig(ec, ERR_UNXEXT); + ex = &vctx->voccxrun->runcxext[obj]; + + memcpy(ex->runxnam, p + 4, (size_t)p[3]); + ex->runxnam[p[3]] = '\0'; + bsiz -= p[3] + 4; + p += p[3] + 4; + break; + + default: + errsig(ec, ERR_UNKOTYP); + } + } + + /* done with temporary block; free it */ + osfree(bufp); + osfseek(fp, endpos + startofs, OSFSK_SET); + } + else + { + while (curpos != endpos) + { + if (osfrb(fp, buf, 3)) errsig(ec, ERR_RDGAM); + obj = osrp2(buf + 1); + switch(buf[0]) + { + case TOKSTFUNC: + case TOKSTOBJ: + if (osfrb(fp, buf + 3, 8)) errsig(ec, ERR_RDGAM); + mcmrsrv(mctx, (ushort)osrp2(buf + 3), (mcmon)obj, + (mclhd)osrp4(buf + 7)); + curpos += 11; + + /* preload object if desired */ + if (flags & 2) + { + (void)mcmlck(mctx, (mcmon)obj); + mcmunlck(mctx, (mcmon)obj); + osfseek(fp, curpos + startofs, OSFSK_SET); + } + break; + + case TOKSTEXTERN: + if (!vctx->voccxrun->runcxext) + errsig(ec, ERR_UNXEXT); + ex = &vctx->voccxrun->runcxext[obj]; + + if (osfrb(fp, buf + 3, 1) + || osfrb(fp, ex->runxnam, (int)buf[3])) + errsig(ec, ERR_RDGAM); + ex->runxnam[buf[3]] = '\0'; + curpos += buf[3] + 4; + break; + + default: + errsig(ec, ERR_UNKOTYP); + } + } + } + + /* if we can preload xfcn's, do so now */ + if (xfcn_pos) + { + eof_reset = endpos; /* remember to return here when done */ + osfseek(fp, xfcn_pos, OSFSK_SET); /* go to xfcn's */ + } + } + else if (fioisrsc(buf, "XFCN")) + { + if (!vctx->voccxrun->runcxext) errsig(ec, ERR_UNXEXT); + + /* read length and name of resource */ + if (osfrb(fp, buf, 3) || osfrb(fp, buf + 3, (int)buf[2])) + errsig(ec, ERR_RDGAM); + siz = osrp2(buf); + +#if 0 +/* + * external functions are now obsolete - do not load + */ + + /* look for an external function with the same name */ + for (i = vctx->voccxrun->runcxexc, ex = vctx->voccxrun->runcxext + ; i ; ++ex, --i) + { + j = strlen(ex->runxnam); + if (j == buf[2] && !memcmp(buf + 3, ex->runxnam, (size_t)j)) + break; + } + + /* if we found an external function of this name, load it */ + if (i && !xfcns_done) + { + /* load the function */ + ex->runxptr = os_exfld(fp, (unsigned)siz); + } + else + { + /* this XFCN isn't used; don't bother loading it */ + osfseek(fp, endpos + startofs, OSFSK_SET); + } +#else + /* external functions are obsolete; simply skip the data */ + osfseek(fp, endpos + startofs, OSFSK_SET); +#endif + } + else if (fioisrsc(buf, "HTMLRES")) + { + /* read the resources */ + fiordhtml(ec, fp, appctx, 0, fname); + + /* + * skip the resources - they're entirely for the host + * application's use + */ + osfseek(fp, endpos + startofs, OSFSK_SET); + } + else if (fioisrsc(buf, "INH")) + { + uchar *p; + uchar *bufp; + ulong bsiz; + + /* do it in a single file read, if we can, for speed */ + curpos = osfpos(fp) - startofs; + bsiz = endpos - curpos; + if (bsiz && bsiz < OSMALMAX + && (bufp = p = (uchar *)osmalloc((size_t)bsiz)) != 0) + { + uchar *p1; + ulong siz2; + uint sizcur; + + for (p1 = p, siz2 = bsiz ; siz2 ; siz2 -= sizcur, p1 += sizcur) + { + sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2); + if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM); + } + + while (bsiz) + { + i = osrp2(p + 7); + obj = osrp2(p + 1); + + vociadd(vctx, (objnum)obj, (objnum)osrp2(p+3), i, + (objnum *)(p + 9), p[0] | VOCIFXLAT); + vocinh(vctx, obj)->vociilc = osrp2(p + 5); + + p += 9 + (2 * i); + bsiz -= 9 + (2 * i); + } + + /* done with temporary block; free it */ + osfree(bufp); + } + else + { + while (curpos != endpos) + { + if (osfrb(fp, buf, 9)) errsig(ec, ERR_RDGAM); + i = osrp2(buf + 7); /* get number of superclasses */ + obj = osrp2(buf + 1); /* get object number */ + if (i && osfrb(fp, buf + 9, 2 * i)) errsig(ec, ERR_RDGAM); + + vociadd(vctx, (objnum)obj, (objnum)osrp2(buf+3), + i, (objnum *)(buf + 9), buf[0] | VOCIFXLAT); + vocinh(vctx, obj)->vociilc = osrp2(buf + 5); + + curpos += 9 + (2 * i); + } + } + } + else if (fioisrsc(buf, "REQ")) + { + curpos = osfpos(fp) - startofs; + siz = endpos - curpos; + + if (osfrb(fp, buf, (uint)siz)) errsig(ec, ERR_RDGAM); + vctx->voccxme = vctx->voccxme_init = osrp2(buf); + vctx->voccxvtk = osrp2(buf+2); + vctx->voccxstr = osrp2(buf+4); + vctx->voccxnum = osrp2(buf+6); + vctx->voccxprd = osrp2(buf+8); + vctx->voccxvag = osrp2(buf+10); + vctx->voccxini = osrp2(buf+12); + vctx->voccxpre = osrp2(buf+14); + vctx->voccxper = osrp2(buf+16); + + /* if we have a cmdPrompt function, read it */ + if (siz >= 20) + vctx->voccxprom = osrp2(buf + 18); + else + vctx->voccxprom = MCMONINV; + + /* if we have the NLS functions, read them */ + if (siz >= 26) + { + vctx->voccxpdis = osrp2(buf + 20); + vctx->voccxper2 = osrp2(buf + 22); + vctx->voccxpdef = osrp2(buf + 24); + } + else + { + /* the new NLS functions aren't defined in this file */ + vctx->voccxpdis = MCMONINV; + vctx->voccxper2 = MCMONINV; + vctx->voccxpdef = MCMONINV; + } + + /* test for parseAskobj separately, as it was added later */ + if (siz >= 28) + vctx->voccxpask = osrp2(buf + 26); + else + vctx->voccxpask = MCMONINV; + + /* test for preparseCmd separately - it's another late comer */ + if (siz >= 30) + vctx->voccxppc = osrp2(buf + 28); + else + vctx->voccxppc = MCMONINV; + + /* check for parseAskobjActor separately - another late comer */ + if (siz >= 32) + vctx->voccxpask2 = osrp2(buf + 30); + else + vctx->voccxpask2 = MCMONINV; + + /* if we have parseErrorParam, read it as well */ + if (siz >= 34) + { + vctx->voccxperp = osrp2(buf + 32); + } + else + { + /* parseErrorParam isn't defined in this file */ + vctx->voccxperp = MCMONINV; + } + + /* + * if we have commandAfterRead and initRestore, read them as + * well + */ + if (siz >= 38) + { + vctx->voccxpostprom = osrp2(buf + 34); + vctx->voccxinitrestore = osrp2(buf + 36); + } + else + { + /* these new functions aren't defined in this game */ + vctx->voccxpostprom = MCMONINV; + vctx->voccxinitrestore = MCMONINV; + } + + /* check for and read parseUnknownVerb, parseNounPhrase */ + if (siz >= 42) + { + vctx->voccxpuv = osrp2(buf + 38); + vctx->voccxpnp = osrp2(buf + 40); + } + else + { + vctx->voccxpuv = MCMONINV; + vctx->voccxpnp = MCMONINV; + } + + /* check for postAction, endCommand */ + if (siz >= 48) + { + vctx->voccxpostact = osrp2(buf + 42); + vctx->voccxendcmd = osrp2(buf + 44); + vctx->voccxprecmd = osrp2(buf + 46); + } + else + { + vctx->voccxpostact = MCMONINV; + vctx->voccxendcmd = MCMONINV; + vctx->voccxprecmd = MCMONINV; + } + + /* check for parseAskobjIndirect */ + if (siz >= 50) + vctx->voccxpask3 = osrp2(buf + 48); + else + vctx->voccxpask3 = MCMONINV; + + /* check for preparseExt and parseDefaultExt */ + if (siz >= 54) + { + vctx->voccxpre2 = osrp2(buf + 50); + vctx->voccxpdef2 = osrp2(buf + 52); + } + else + { + vctx->voccxpre2 = MCMONINV; + vctx->voccxpdef2 = MCMONINV; + } + } + else if (fioisrsc(buf, "VOC")) + { + uchar *p; + uchar *bufp; + ulong bsiz; + int len1; + int len2; + + /* do it in a single file read, if we can, for speed */ + curpos = osfpos(fp) - startofs; + bsiz = endpos - curpos; + if (bsiz && bsiz < OSMALMAX + && (bufp = p = (uchar *)osmalloc((size_t)bsiz)) != 0) + { + uchar *p1; + ulong siz2; + uint sizcur; + + for (p1 = p, siz2 = bsiz ; siz2 ; siz2 -= sizcur, p1 += sizcur) + { + sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2); + if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM); + } + + while (bsiz) + { + len1 = osrp2(p); + len2 = osrp2(p + 2); + if (*flagp & FIOFCRYPT) + fioxor(p + 10, (uint)(len1 + len2), + xor_seed, xor_inc); + vocadd2(vctx, (prpnum)osrp2(p+4), (objnum)osrp2(p+6), + osrp2(p+8), p + 10, len1, + (len2 ? p + 10 + len1 : (uchar*)0), len2); + + p += 10 + len1 + len2; + bsiz -= 10 + len1 + len2; + } + + /* done with the temporary block; free it up */ + osfree(bufp); + } + else + { + /* can't do it in one file read; do it the slow way */ + while (curpos != endpos) + { + if (osfrb(fp, buf, 10) + || osfrb(fp, buf + 10, + (len1 = osrp2(buf)) + (len2 = osrp2(buf + 2)))) + errsig(ec, ERR_RDGAM); + + if (*flagp & FIOFCRYPT) + fioxor(buf + 10, (uint)(len1 + len2), + xor_seed, xor_inc); + vocadd2(vctx, (prpnum)osrp2(buf+4), (objnum)osrp2(buf+6), + osrp2(buf+8), buf + 10, len1, + (len2 ? buf + 10 + len1 : (uchar*)0), len2); + curpos += 10 + len1 + len2; + } + } + } + else if (fioisrsc(buf, "FMTSTR")) + { + uchar *fmts; + uint fmtl; + + if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM); + fmtl = osrp2(buf); + fmts = mchalo(vctx->voccxerr, fmtl, "fiord1"); + if (osfrb(fp, fmts, fmtl)) errsig(ec, ERR_RDGAM); + if (*flagp & FIOFCRYPT) fioxor(fmts, fmtl, xor_seed, xor_inc); + tiosetfmt(vctx->voccxtio, vctx->voccxrun, fmts, fmtl); + + if (fmtsp) *fmtsp = fmts; + if (fmtlp) *fmtlp = fmtl; + } + else if (fioisrsc(buf, "CMPD")) + { + if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM); + vctx->voccxcpl = osrp2(buf); + vctx->voccxcpp = (char *)mchalo(vctx->voccxerr, + vctx->voccxcpl, "fiord1"); + if (osfrb(fp, vctx->voccxcpp, (uint)vctx->voccxcpl)) + errsig(ec, ERR_RDGAM); + if (*flagp & FIOFCRYPT) + fioxor((uchar *)vctx->voccxcpp, (uint)vctx->voccxcpl, + xor_seed, xor_inc); + } + else if (fioisrsc(buf, "SPECWORD")) + { + if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM); + vctx->voccxspl = osrp2(buf); + vctx->voccxspp = (char *)mchalo(vctx->voccxerr, + vctx->voccxspl, "fiord1"); + if (osfrb(fp, vctx->voccxspp, (uint)vctx->voccxspl)) + errsig(ec, ERR_RDGAM); + if (*flagp & FIOFCRYPT) + fioxor((uchar *)vctx->voccxspp, (uint)vctx->voccxspl, + xor_seed, xor_inc); + } + else if (fioisrsc(buf, "SYMTAB")) + { + tokthdef *symtab; + + /* if there's no debugger context, don't bother with this */ + if (!vctx->voccxrun->runcxdbg) + { + osfseek(fp, endpos + startofs, OSFSK_SET); + continue; + } + + if (!(symtab = vctx->voccxrun->runcxdbg->dbgcxtab)) + { + symtab = (tokthdef *)mchalo(ec, sizeof(tokthdef), + "fiord:symtab"); + tokthini(ec, mctx, (toktdef *)symtab); + vctx->voccxrun->runcxdbg->dbgcxtab = symtab; + } + + /* read symbols until we find a zero-length symbol */ + for (;;) + { + int hash; + + if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM); + if (buf[0] == 0) break; + if (osfrb(fp, buf + 4, (int)buf[0])) errsig(ec, ERR_RDGAM); + buf[4 + buf[0]] = '\0'; + hash = tokhsh((char *)buf + 4); + + (*symtab->tokthsc.toktfadd)((toktdef *)symtab, + (char *)buf + 4, + (int)buf[0], (int)buf[1], + osrp2(buf + 2), hash); + } + } + else if (fioisrsc(buf, "SRC")) + { + /* skip source file id's if there's no debugger context */ + if (vctx->voccxrun->runcxdbg == 0) + { + osfseek(fp, endpos + startofs, OSFSK_SET); + continue; + } + + while ((osfpos(fp) - startofs) != endpos) + { + /* the only thing we know how to read is linfdef's */ + if (linfload(fp, vctx->voccxrun->runcxdbg, ec, path)) + errsig(ec, ERR_RDGAM); + } + } + else if (fioisrsc(buf, "SRC2")) + { + /* + * this is simply a marker indicating that we have new-style + * (line-number-based) source debugging information in the + * file -- set the new-style debug info flag + */ + if (vctx->voccxrun->runcxdbg != 0) + vctx->voccxrun->runcxdbg->dbgcxflg |= DBGCXFLIN2; + + /* the contents are empty - skip the block */ + osfseek(fp, endpos + startofs, OSFSK_SET); + } + else if (fioisrsc(buf, "PREINIT")) + { + if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM); + *preinit = osrp2(buf); + } + else if (fioisrsc(buf, "ERRMSG")) + { + errini(ec, fp); + osfseek(fp, endpos + startofs, OSFSK_SET); + } + else if (fioisrsc(buf, "EXTCNT")) + { + uchar *p; + ushort len; + ulong bsiz; + + curpos = osfpos(fp) - startofs; + bsiz = endpos - curpos; + if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM); + i = osrp2(buf); + + len = i * sizeof(runxdef); + p = mchalo(ec, len, "fiord:runxdef"); + memset(p, 0, (size_t)len); + + vctx->voccxrun->runcxext = (runxdef *)p; + vctx->voccxrun->runcxexc = i; + + /* see if start-of-XFCN information is present */ + if (bsiz >= 6) + { + /* get location of first XFCN, and seek there */ + if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM); + xfcn_pos = osrp4(buf); + } + + /* seek past this resource */ + osfseek(fp, endpos + startofs, OSFSK_SET); + } + else if (fioisrsc(buf, "PRPCNT")) + { + if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM); + if (pcntptr) *pcntptr = osrp2(buf); + } + else if (fioisrsc(buf, "TADSPP") && tctx != 0) + { + tok_read_defines(tctx, fp, ec); + } + else if (fioisrsc(buf, "XSI")) + { + if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM); + setupctx->fiolcxseed = xor_seed = buf[0]; + setupctx->fiolcxinc = xor_inc = buf[1]; + osfseek(fp, endpos + startofs, OSFSK_SET); + } + else if (fioisrsc(buf, "CHRSET")) + { + size_t len; + + /* read the character set ID and LDESC */ + if (osfrb(fp, buf, 6) + || (len = osrp2(buf+4)) > CMAP_LDESC_MAX_LEN + || osfrb(fp, buf+6, len)) + errsig(ec, ERR_RDGAM); + + /* establish this character set mapping */ + buf[4] = '\0'; + cmap_set_game_charset(ec, (char *)buf, (char *)buf + 6, argv0); + } + else if (fioisrsc(buf, "$EOF")) + { + if (eof_reset) + { + osfseek(fp, eof_reset, OSFSK_SET); /* back after EXTCNT */ + eof_reset = 0; /* really done at next EOF */ +#if 0 // XFCNs are obsolete + xfcns_done = TRUE; /* don't do XFCNs again */ +#endif + } + else + break; + } + else + errsig(ec, ERR_UNKRSC); + } +} + +/* read binary file */ +void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, char *fname, + char *exename, fiolcxdef *setupctx, objnum *preinit, uint *flagp, + tokpdef *path, uchar **fmtsp, uint *fmtlp, uint *pcntptr, + int flags, struct appctxdef *appctx, char *argv0) +{ + osfildef *fp; + ulong startofs; + char *display_fname; + + /* presume there will be no need to run preinit */ + *preinit = MCMONINV; + + /* + * get the display filename - use the real filename if one is + * provided, otherwise use the name of the executable file itself + */ + display_fname = (fname != 0 ? fname : exename); + + /* save the filename in G_os_gamename */ + if (display_fname != 0) + { + size_t copylen; + + /* limit the copy to the buffer size */ + if ((copylen = strlen(display_fname)) > sizeof(G_os_gamename) - 1) + copylen = sizeof(G_os_gamename) - 1; + + /* save it */ + memcpy(G_os_gamename, display_fname, copylen); + G_os_gamename[copylen] = '\0'; + } + else + G_os_gamename[0] = '\0'; + + /* open the file and read and check file header */ + fp = (fname != 0 ? osfoprb(fname, OSFTGAME) + : os_exeseek(exename, "TGAM")); + if (fp == 0) + errsig(vctx->voccxerr, ERR_OPRGAM); + + /* + * we've identified the .GAM file source - tell the host system + * about it, if it's interested + */ + if (appctx != 0 && appctx->set_game_name != 0) + (*appctx->set_game_name)(appctx->set_game_name_ctx, display_fname); + + /* remember starting location in file */ + startofs = osfpos(fp); + + ERRBEGIN(vctx->voccxerr) + + /* + * Read the game file. Note that the .GAM file always has resource + * file number zero. + */ + fiord1(mctx, vctx, tctx, fp, display_fname, + setupctx, startofs, preinit, flagp, path, + fmtsp, fmtlp, pcntptr, flags, appctx, argv0); + + /* + * If the host system accepts additional resource files, look for + * additional resource files. These are files in the same directory + * as the .GAM file, with the .GAM suffix replaced by suffixes from + *. RS0 to .RS9. + */ + if (appctx != 0 && appctx->add_resfile != 0) + { + char suffix_lc[4]; + char suffix_uc[4]; + int i; + char *base_name; + + /* use the game or executable filename, as appropriate */ + base_name = display_fname; + + /* build the initial suffixes - try both upper- and lower-case */ + suffix_uc[0] = 'R'; + suffix_uc[1] = 'S'; + suffix_uc[3] = '\0'; + suffix_lc[0] = 'r'; + suffix_lc[1] = 's'; + suffix_lc[3] = '\0'; + + /* loop through each possible suffix (.RS0 through .RS9) */ + for (i = 0 ; i < 9 ; ++i) + { + char resname[OSFNMAX]; + osfildef *fpres; + int resfileno; + + /* + * Build the next resource filename. If there's an explicit + * resource path, use it, otherwise use the same directory + * that contains the .GAM file. + */ + if (appctx->ext_res_path != 0) + { + /* + * There's an explicit resource path - append the root + * (filename-only, minus path) portion of the .GAM file + * name to the resource path. + */ + os_build_full_path(resname, sizeof(resname), + appctx->ext_res_path, + os_get_root_name(base_name)); + } + else + { + /* + * there's no resource path - use the entire .GAM + * filename, including directory, so that we look in the + * same directory that contains the .GAM file + */ + if (base_name != 0) + strcpy(resname, base_name); + else + resname[0] = '\0'; + } + + /* add the current extension (replacing any current extension) */ + os_remext(resname); + suffix_lc[2] = suffix_uc[2] = '0' + i; + os_addext(resname, suffix_lc); + + /* try opening the file */ + fpres = osfoprb(resname, OSFTGAME); + + /* if that didn't work, try the upper-case name */ + if (fpres == 0) + { + /* replace the suffix with the upper-case version */ + os_remext(resname); + os_addext(resname, suffix_uc); + + /* try again with the new name */ + fpres = osfoprb(resname, OSFTGAME); + } + + /* if we opened it successfully, read it */ + if (fpres != 0) + { + /* tell the host system about it */ + resfileno = (*appctx->add_resfile) + (appctx->add_resfile_ctx, resname); + + /* read the file */ + fiordrscext(vctx->voccxerr, fpres, appctx, + resfileno, resname); + + /* we're done with the file, so close it */ + osfcls(fpres); + } + } + } + + ERRCLEAN(vctx->voccxerr) + /* if an error occurs during read, clean up by closing the file */ + osfcls(fp); + ERRENDCLN(vctx->voccxerr); +} + +/* save game header */ +#define FIOSAVHDR "TADS2 save\012\015\032" + +/* save game header prefix - .GAM file information */ +#define FIOSAVHDR_PREFIX "TADS2 save/g\012\015\032" + +/* + * Saved game format version string - note that the length of the + * version string must be fixed, so when this is updated, it must be + * updated to another string of the same length. This should be updated + * whenever a change is made to the format that can't be otherwise + * detected from the data stream in the saved game file. + */ +#define FIOSAVVSN "v2.2.1" + +/* old saved game format version strings */ +#define FIOSAVVSN1 "v2.2.0" + +/* read fuse/daemon/alarm record */ +static int fiorfda(osfildef *fp, vocddef *p, uint cnt) +{ + vocddef *q; + uint i; + uchar buf[14]; + + /* start by clearing out entire record */ + for (i = 0, q = p ; i < cnt ; ++q, ++i) + q->vocdfn = MCMONINV; + + /* now restore all the records from the file */ + for (;;) + { + /* read a record, and quit if it's the last one */ + if (osfrb(fp, buf, 13)) return(TRUE); + if ((i = osrp2(buf)) == 0xffff) return(FALSE); + + /* restore this record */ + q = p + i; + q->vocdfn = osrp2(buf+2); + q->vocdarg.runstyp = buf[4]; + switch(buf[4]) + { + case DAT_NUMBER: + q->vocdarg.runsv.runsvnum = osrp4s(buf+5); + break; + case DAT_OBJECT: + case DAT_FNADDR: + q->vocdarg.runsv.runsvobj = osrp2(buf+5); + break; + case DAT_PROPNUM: + q->vocdarg.runsv.runsvprp = osrp2(buf+5); + break; + } + q->vocdprp = osrp2(buf+9); + q->vocdtim = osrp2(buf+11); + } +} + +/* + * Look in a saved game file to determine if it has information on which + * GAM file created it. If the GAM file information is available, this + * routine returns true and stores the game file name in the given + * buffer; if the information isn't available, we'll return false. + */ +int fiorso_getgame(char *saved_file, char *fnamebuf, size_t buflen) +{ + osfildef *fp; + uint namelen; + char buf[sizeof(FIOSAVHDR_PREFIX) + 2]; + + /* open the input file */ + if (!(fp = osfoprb(saved_file, OSFTSAVE))) + return FALSE; + + /* read the prefix header and check */ + if (osfrb(fp, buf, (int)(sizeof(FIOSAVHDR_PREFIX) + 2)) + || memcmp(buf, FIOSAVHDR_PREFIX, sizeof(FIOSAVHDR_PREFIX)) != 0) + { + /* + * there's no game file information - close the file and + * indicate that we have no information + */ + osfcls(fp); + return FALSE; + } + + /* get the length of the filename */ + namelen = osrp2(buf + sizeof(FIOSAVHDR_PREFIX)); + if (namelen > buflen - 1) + namelen = buflen - 1; + + /* read the filename */ + if (osfrb(fp, fnamebuf, namelen)) + { + osfcls(fp); + return FALSE; + } + + /* null-terminate the string */ + fnamebuf[namelen] = '\0'; + + /* done with the file */ + osfcls(fp); + + /* indicate that we found the information */ + return TRUE; +} + +/* restore game: returns TRUE on failure */ +int fiorso(voccxdef *vctx, char *fname) +{ + osfildef *fp; + objnum obj; + uchar *p; + uchar *mut; + uint mutsiz; + uint oldmutsiz; + int propcnt; + mcmcxdef *mctx = vctx->voccxmem; + uchar buf[sizeof(FIOSAVHDR) + sizeof(FIOSAVVSN)]; + ushort newsiz; + int err = FALSE; + char timestamp[26]; + int version = 0; /* version ID - 0 = current version */ + int result; + + /* presume success */ + result = FIORSO_SUCCESS; + + /* open the input file */ + if (!(fp = osfoprb(fname, OSFTSAVE))) + return FIORSO_FILE_NOT_FOUND; + + /* check for a prefix header - if it's there, skip it */ + if (!osfrb(fp, buf, (int)(sizeof(FIOSAVHDR_PREFIX) + 2)) + && memcmp(buf, FIOSAVHDR_PREFIX, sizeof(FIOSAVHDR_PREFIX)) == 0) + { + ulong skip_len; + + /* + * The prefix header is present - skip it. The 2-byte value + * following the header is the length of the prefix data block + * (not including the header), so simply skip the additional + * number of bytes specified. + */ + skip_len = (ulong)osrp2(buf + sizeof(FIOSAVHDR_PREFIX)); + osfseek(fp, skip_len, OSFSK_CUR); + } + else + { + /* + * there's no prefix header - seek back to the start of the file + * and read the standard header information + */ + osfseek(fp, 0, OSFSK_SET); + } + + + /* read headers and check */ + if (osfrb(fp, buf, (int)(sizeof(FIOSAVHDR) + sizeof(FIOSAVVSN))) + || memcmp(buf, FIOSAVHDR, (size_t)sizeof(FIOSAVHDR))) + { + /* it's not a saved game file */ + result = FIORSO_NOT_SAVE_FILE; + goto ret_error; + } + + /* check the version string */ + if (memcmp(buf + sizeof(FIOSAVHDR), FIOSAVVSN, + (size_t)sizeof(FIOSAVVSN)) == 0) + { + /* it's the current version */ + version = 0; + } + else if (memcmp(buf + sizeof(FIOSAVHDR), FIOSAVVSN1, + (size_t)sizeof(FIOSAVVSN1)) == 0) + { + /* it's old version #1 */ + version = 1; + } + else + { + /* + * this isn't a recognized version - the file must have been + * saved by a newer version of the system, so we can't assume we + * will be able to parse the format + */ + result = FIORSO_BAD_FMT_VSN; + goto ret_error; + } + + /* + * Read timestamp and check - the game must have been saved by the + * same .GAM file that we are now running, because the .SAV file is + * written entirely in terms of the contents of the .GAM file; any + * change in the .GAM file invalidates the .SAV file. + */ + if (osfrb(fp, timestamp, 26) + || memcmp(timestamp, vctx->voccxtim, (size_t)26)) + { + result = FIORSO_BAD_GAME_VSN; + goto ret_error; + } + + /* first revert every object to original (post-compilation) state */ + vocrevert(vctx); + + /* + * the most common error from here on is simply a file read error, + * so presume that this is what will happen; if we are successful or + * encounter a different error, we'll change the status at that + * point + */ + result = FIORSO_READ_ERROR; + + /* go through file and load changed objects */ + for (;;) + { + /* get the header */ + if (osfrb(fp, buf, 7)) + goto ret_error; + + /* get the object number from the header, and stop if we're done */ + obj = osrp2(buf+1); + if (obj == MCMONINV) + break; + + /* if the object was dynamically allocated, recreate it */ + if (buf[0] == 1) + { + int sccnt; + objnum sc; + + /* create the object */ + mutsiz = osrp2(buf + 3); + p = mcmalonum(mctx, (ushort)mutsiz, (mcmon)obj); + + /* read the object's contents */ + if (osfrb(fp, p, mutsiz)) + goto ret_error; + + /* get the superclass data (at most one superclass) */ + sccnt = objnsc(p); + if (sccnt) sc = osrp2(objsc(p)); + + /* create inheritance records for the object */ + vociadd(vctx, obj, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC); + +#if 0 + { + int wrdcnt; + + /* read the object's vocabulary and add it back */ + if (osfrb(fp, buf, 2)) + goto ret_error; + wrdcnt = osrp2(buf); + while (wrdcnt--) + { + int len1; + int len2; + char wrd[80]; + + /* read the header */ + if (osfrb(fp, buf, 6)) + goto ret_error; + len1 = osrp2(buf+2); + len2 = osrp2(buf+4); + + /* read the word text */ + if (osfrb(fp, wrd, len1 + len2)) + goto ret_error; + + /* add the word */ + vocadd2(vctx, buf[0], obj, buf[1], wrd, len1, + wrd + len1, len2); + } + } +#endif + + } + else + { + /* get the remaining data from the header */ + propcnt = osrp2(buf + 3); + mutsiz = osrp2(buf + 5); + + /* expand object if it's not big enough for mutsiz */ + p = mcmlck(mctx, (mcmon)obj); + oldmutsiz = mcmobjsiz(mctx, (mcmon)obj) - objrst(p); + if (oldmutsiz < mutsiz) + { + newsiz = mutsiz - oldmutsiz; + p = (uchar *)objexp(mctx, obj, &newsiz); + } + + /* reset statistics, and read mutable part from file */ + mut = p + objrst(p); + objsnp(p, propcnt); + objsfree(p, mutsiz + objrst(p)); + if (osfrb(fp, mut, mutsiz)) + err = TRUE; + + /* reset ignore flags as needed */ + objsetign(mctx, obj); + } + + /* touch and unlock the object */ + mcmtch(mctx, (mcmon)obj); + mcmunlck(mctx, (mcmon)obj); + if (err) + goto ret_error; + } + + /* read fuses/daemons/alarms */ + if (fiorfda(fp, vctx->voccxdmn, vctx->voccxdmc) + || fiorfda(fp, vctx->voccxfus, vctx->voccxfuc) + || fiorfda(fp, vctx->voccxalm, vctx->voccxalc)) + goto ret_error; + + /* read the dynamically added and deleted vocabulary */ + for (;;) + { + int len1; + int len2; + char wrd[80]; + int flags; + int typ; + + /* read the header */ + if (osfrb(fp, buf, 8)) + goto ret_error; + + typ = buf[0]; + flags = buf[1]; + len1 = osrp2(buf+2); + len2 = osrp2(buf+4); + obj = osrp2(buf+6); + + /* check to see if this is the end marker */ + if (obj == MCMONINV) break; + + /* read the word text */ + if (osfrb(fp, wrd+2, len1)) + goto ret_error; + if (len2) + { + wrd[len1 + 2] = ' '; + if (osfrb(fp, &wrd[len1 + 3], len2)) + goto ret_error; + oswp2(wrd, len1 + len2 + 3); + } + else + oswp2(wrd, len1 + 2); + + /* add or delete the word as appropriate */ + if (flags & VOCFDEL) + vocdel1(vctx, obj, (char *)wrd, (prpnum)typ, FALSE, FALSE, FALSE); + else + vocadd2(vctx, buf[0], obj, buf[1], (uchar *)wrd+2, len1, + (uchar *)wrd+len1, len2); + } + + /* + * the following was added in save format version "v2.2.1", so skip + * it if the save version is older than that + */ + if (version != 1) + { + /* read the current "Me" object */ + if (osfrb(fp, buf, 2)) + goto ret_error; + vctx->voccxme = osrp2(buf); + } + + /* done - close file and return success indication */ + osfcls(fp); + return FIORSO_SUCCESS; + + /* come here on failure - close file and return error indication */ +ret_error: + osfcls(fp); + return result; +} + +/* write fuse/daemon/alarm block */ +static int fiowfda(osfildef *fp, vocddef *p, uint cnt) +{ + uchar buf[14]; + uint i; + + for (i = 0 ; i < cnt ; ++i, ++p) + { + if (p->vocdfn == MCMONINV) continue; /* not set - ignore */ + + oswp2(buf, i); /* element in array to be set */ + oswp2(buf+2, p->vocdfn); /* object number for function/target */ + buf[4] = p->vocdarg.runstyp; /* type of argument */ + switch(buf[4]) + { + case DAT_NUMBER: + oswp4s(buf+5, p->vocdarg.runsv.runsvnum); + break; + case DAT_OBJECT: + case DAT_FNADDR: + oswp2(buf+5, p->vocdarg.runsv.runsvobj); + break; + case DAT_PROPNUM: + oswp2(buf+5, p->vocdarg.runsv.runsvprp); + break; + } + oswp2(buf+9, p->vocdprp); + oswp2(buf+11, p->vocdtim); + + /* write this record to file */ + if (osfwb(fp, buf, 13)) return(TRUE); + } + + /* write end record - -1 for array element number */ + oswp2(buf, 0xffff); + return(osfwb(fp, buf, 13)); +} + +/* context for vocabulary saver callback function */ +struct fiosav_cb_ctx +{ + int err; + osfildef *fp; +}; + +#ifdef NEVER +/* + * callback for vocabulary saver - called by voc_iterate for each word + * defined for a particular object, allowing us to write all the words + * attached to a dynamically allocated object to the save file + */ +static void fiosav_cb(struct fiosav_cb_ctx *ctx, + vocdef *voc, vocwdef *vocw) +{ + char buf[10]; + + /* write the part of speech, flags, and word lengths */ + buf[0] = vocw->vocwtyp; + buf[1] = vocw->vocwflg; + oswp2(buf+2, voc->voclen); + oswp2(buf+4, voc->vocln2); + if (osfwb(ctx->fp, buf, 6)) ctx->err = TRUE; + + /* write the words */ + if (osfwb(ctx->fp, voc->voctxt, voc->voclen + voc->vocln2)) + ctx->err = TRUE; +} +#endif + +/* + * Callback for vocabulary saver - called by voc_iterate for every + * word. We'll write the word if it was dynamically added or deleted, + * so that we can restore that status when the game is restored. + */ +static void fiosav_voc_cb(void *ctx0, vocdef *voc, vocwdef *vocw) +{ + struct fiosav_cb_ctx *ctx = (struct fiosav_cb_ctx *)ctx0; + char buf[10]; + + /* if the word was dynamically allocated or deleted, save it */ + if ((vocw->vocwflg & VOCFNEW) || (vocw->vocwflg & VOCFDEL)) + { + /* write the header information */ + buf[0] = vocw->vocwtyp; + buf[1] = vocw->vocwflg; + oswp2(buf+2, voc->voclen); + oswp2(buf+4, voc->vocln2); + oswp2(buf+6, vocw->vocwobj); + if (osfwb(ctx->fp, buf, 8)) ctx->err = TRUE; + + /* write the words */ + if (osfwb(ctx->fp, voc->voctxt, voc->voclen + voc->vocln2)) + ctx->err = TRUE; + } +} + + +/* save game; returns TRUE on failure */ +int fiosav(voccxdef *vctx, char *fname, char *game_fname) +{ + osfildef *fp; + vocidef ***vpg; + vocidef **v; + int i; + int j; + objnum obj; + uchar *p; + uchar *mut; + uint mutsiz; + int propcnt; + mcmcxdef *mctx = vctx->voccxmem; + uchar buf[8]; + int err = FALSE; + struct fiosav_cb_ctx fnctx; + + /* open the output file */ + if ((fp = osfopwb(fname, OSFTSAVE)) == 0) + return TRUE; + + /* + * If we have game file information, save the game file information + * with the saved game file. This lets the player start the + * run-time and restore the game by specifying only the saved game + * file. + */ + if (game_fname != 0) + { + size_t len; + + /* write the prefix header */ + len = strlen(game_fname); + oswp2(buf, len); + if (osfwb(fp, FIOSAVHDR_PREFIX, (int)sizeof(FIOSAVHDR_PREFIX)) + || osfwb(fp, buf, 2) + || osfwb(fp, game_fname, (int)len)) + goto ret_error; + } + + /* write save game header and timestamp */ + if (osfwb(fp, FIOSAVHDR, (int)sizeof(FIOSAVHDR)) + || osfwb(fp, FIOSAVVSN, (int)sizeof(FIOSAVVSN)) + || osfwb(fp, vctx->voccxtim, 26)) + goto ret_error; + + /* go through each object, and write if it's been changed */ + 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 != 0) + { + /* write object if it's dirty */ + if (mcmobjdirty(mctx, (mcmon)obj)) + { + p = mcmlck(mctx, (mcmon)obj); + mut = p + objrst(p); + propcnt = objnprop(p); + mutsiz = objfree(p) - objrst(p); + if ((objflg(p) & OBJFINDEX) != 0) + mutsiz += propcnt * 4; + + /* + * If the object was dynamically allocated, write + * the whole object. Otherwise, write just the + * mutable part. + */ + if ((*v)->vociflg & VOCIFNEW) + { + /* indicate that the object is dynamic */ + buf[0] = 1; + oswp2(buf + 1, obj); + + /* write the entire object */ + mutsiz = objfree(p); + oswp2(buf + 3, mutsiz); + if (osfwb(fp, buf, 7) + || osfwb(fp, p, mutsiz)) + err = TRUE; + +#ifdef NEVER + { + int wrdcnt; + + /* count the words, and write the count */ + voc_count(vctx, obj, 0, &wrdcnt, (int *)0); + oswp2(buf, wrdcnt); + if (osfwb(fp, buf, 2)) + err = TRUE; + + /* write the words */ + fnctx.err = 0; + fnctx.fp = fp; + voc_iterate(vctx, obj, fiosav_cb, &fnctx); + if (fnctx.err != 0) + err = TRUE; + } +#endif + } + else if (mutsiz) + { + /* write number of properties, size of mut, and mut */ + buf[0] = 0; /* indicate that the object is static */ + oswp2(buf + 1, obj); + oswp2(buf + 3, propcnt); + oswp2(buf + 5, mutsiz); + if (osfwb(fp, buf, 7) + || osfwb(fp, mut, mutsiz)) + err = TRUE; + } + + mcmunlck(mctx, (mcmon)obj); + if (err != 0) + goto ret_error; + } + } + } + } + + /* write end-of-objects indication */ + buf[0] = 0; + oswp2(buf + 1, MCMONINV); + oswp4(buf + 3, 0); + if (osfwb(fp, buf, 7)) + goto ret_error; + + /* write fuses/daemons/alarms */ + if (fiowfda(fp, vctx->voccxdmn, vctx->voccxdmc) + || fiowfda(fp, vctx->voccxfus, vctx->voccxfuc) + || fiowfda(fp, vctx->voccxalm, vctx->voccxalc)) + goto ret_error; + + /* write run-time vocabulary additions and deletions */ + fnctx.fp = fp; + fnctx.err = 0; + voc_iterate(vctx, MCMONINV, fiosav_voc_cb, &fnctx); + if (fnctx.err) + goto ret_error; + + /* write end marker for vocabulary additions and deletions */ + oswp2(buf+6, MCMONINV); + if (osfwb(fp, buf, 8)) + goto ret_error; + + /* write the current "Me" object */ + oswp2(buf, vctx->voccxme); + if (osfwb(fp, buf, 2)) + goto ret_error; + + /* done - close file and return success indication */ + osfcls(fp); + os_settype(fname, OSFTSAVE); + return FALSE; + + /* come here on failure - close file and return error indication */ +ret_error: + osfcls(fp); + return TRUE; +} + } // End of namespace TADS2 } // End of namespace TADS } // End of namespace Glk |