aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/tads/tads2/file_io.cpp
diff options
context:
space:
mode:
authorPaul Gilbert2019-05-17 14:48:01 -1000
committerPaul Gilbert2019-05-24 18:21:06 -0700
commitf607792fa4e1f024dd8265034ac84425bde4aee7 (patch)
tree19372287bea6cc0b4cbfdc717510cc9419fd9b04 /engines/glk/tads/tads2/file_io.cpp
parent105a1b94bd9d5a0f10752e135671f4e9a4b0d8da (diff)
downloadscummvm-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.cpp1742
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