1780 lines
45 KiB
C++
1780 lines
45 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#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 = 0;
|
|
|
|
/* 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 != nullptr && ctx->fiolcxfp != nullptr)
|
|
{
|
|
/* close the file */
|
|
osfcls(ctx->fiolcxfp);
|
|
|
|
/* forget the file object */
|
|
ctx->fiolcxfp = nullptr;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 != nullptr)
|
|
{
|
|
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 != nullptr)
|
|
{
|
|
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)) != nullptr)
|
|
{
|
|
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)) != nullptr)
|
|
{
|
|
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 = 0;
|
|
int len2 = 0;
|
|
|
|
/* 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)) != nullptr)
|
|
{
|
|
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*)nullptr), 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*)nullptr), 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 == nullptr)
|
|
{
|
|
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 != nullptr)
|
|
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 != nullptr)
|
|
{
|
|
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, const char *fname,
|
|
const 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;
|
|
const 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 != nullptr ? fname : exename);
|
|
|
|
/* save the filename in G_os_gamename */
|
|
if (display_fname != nullptr)
|
|
{
|
|
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 != nullptr ? osfoprb(fname, OSFTGAME)
|
|
: os_exeseek(exename, "TGAM"));
|
|
if (fp == nullptr)
|
|
errsig(vctx->voccxerr, ERR_OPRGAM);
|
|
|
|
/*
|
|
* we've identified the .GAM file source - tell the host system
|
|
* about it, if it's interested
|
|
*/
|
|
if (appctx != nullptr && appctx->set_game_name != nullptr)
|
|
(*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 != nullptr && appctx->add_resfile != nullptr)
|
|
{
|
|
char suffix_lc[4];
|
|
char suffix_uc[4];
|
|
int i;
|
|
const 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 != nullptr)
|
|
{
|
|
/*
|
|
* 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 != nullptr)
|
|
Common::strcpy_s(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 == nullptr)
|
|
{
|
|
/* 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 != nullptr)
|
|
{
|
|
/* 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)) == nullptr)
|
|
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 != nullptr)
|
|
{
|
|
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 != nullptr)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
void fioxor(uchar *p, uint siz, uint seed, uint inc)
|
|
{
|
|
for (; siz; seed += inc, --siz)
|
|
*p++ ^= (uchar)seed;
|
|
}
|
|
|
|
} // End of namespace TADS2
|
|
} // End of namespace TADS
|
|
} // End of namespace Glk
|