1 #ifdef RCSID
2 static char RCSid[] =
3     "$Header: d:/cvsroot/tads/TADS2/FIO.C,v 1.4 1999/07/11 00:46:29 MJRoberts Exp $";
4 #endif
5 
6 /*
7  *   Copyright (c) 1992, 2002 Michael J. Roberts.  All Rights Reserved.
8  *
9  *   Please see the accompanying license file, LICENSE.TXT, for information
10  *   on using and copying this software.
11  */
12 /*
13 Name
14   fio.c - file i/o functions
15 Function
16   file i/o:  read game, write game, save game, restore game
17 Notes
18   none
19 Modified
20   04/11/99 CNebel        - Include appctx.h, not trd.h.
21   10/23/97 CNebel        - removed now-obsolete Think C hack.
22   04/02/92 MJRoberts     - creation
23 */
24 
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include "os.h"
29 #include "std.h"
30 #include "appctx.h"
31 #include "mch.h"
32 #include "mcm.h"
33 #include "mcl.h"
34 #include "tok.h"
35 #include "obj.h"
36 #include "voc.h"
37 #include "fio.h"
38 #include "dat.h"
39 #include "prs.h"
40 #include "linf.h"
41 #include "cmap.h"
42 
43 
44 /* compare a resource string */
45 /* int fioisrsc(uchar *filbuf, char *refnam); */
46 #define fioisrsc(filbuf, refnam) \
47   (((filbuf)[0] == strlen(refnam)) && \
48    !memcmp(filbuf+1, refnam, (size_t)((filbuf)[0])))
49 
50 /* callback to load an object on demand */
fioldobj(void * ctx0,mclhd handle,uchar * ptr,ushort siz)51 void OS_LOADDS fioldobj(void *ctx0, mclhd handle, uchar *ptr, ushort siz)
52 {
53     fiolcxdef  *ctx = (fiolcxdef *)ctx0;
54     ulong       seekpos = (ulong)handle;
55     osfildef   *fp = ctx->fiolcxfp;
56     char        buf[7];
57     errcxdef   *ec = ctx->fiolcxerr;
58     uint        rdsiz;
59 
60     /* figure out what type of object is to be loaded */
61     osfseek(fp, seekpos + ctx->fiolcxst, OSFSK_SET);
62     if (osfrb(fp, buf, 7)) errsig(ec, ERR_LDGAM);
63     switch(buf[0])
64     {
65     case TOKSTFUNC:
66         rdsiz = osrp2(buf + 3);
67         break;
68 
69     case TOKSTOBJ:
70         rdsiz = osrp2(buf + 5);
71         break;
72 
73     case TOKSTFWDOBJ:
74     case TOKSTFWDFN:
75     default:
76         errsig(ec, ERR_UNKOTYP);
77     }
78 
79     if (siz < rdsiz) errsig(ec, ERR_LDBIG);
80     if (osfrb(fp, ptr, rdsiz)) errsig(ec, ERR_LDGAM);
81     if (ctx->fiolcxflg & FIOFCRYPT)
82         fioxor(ptr, rdsiz, ctx->fiolcxseed, ctx->fiolcxinc);
83 }
84 
85 /* shut down load-on-demand subsystem (close load file) */
fiorcls(fiolcxdef * ctx)86 void fiorcls(fiolcxdef *ctx)
87 {
88     if (ctx != 0 && ctx->fiolcxfp != 0)
89     {
90         /* close the file */
91         osfcls(ctx->fiolcxfp);
92 
93         /* forget the file object */
94         ctx->fiolcxfp = 0;
95     }
96 }
97 
98 /*
99  *   Read an HTMLRES resource map
100  */
fiordhtml(errcxdef * ec,osfildef * fp,appctxdef * appctx,int resfileno,const char * resfilename)101 static void fiordhtml(errcxdef *ec, osfildef *fp, appctxdef *appctx,
102                       int resfileno, const char *resfilename)
103 {
104     uchar buf[256];
105 
106     /*
107      *   resource map - if the host system is interested, tell it about it
108      */
109     if (appctx != 0)
110     {
111         ulong entry_cnt;
112         ulong i;
113 
114         /* read the index table header */
115         if (osfrb(fp, buf, 8))
116             errsig1(ec, ERR_RDRSC, ERRTSTR,
117                     errstr(ec, resfilename, strlen(resfilename)));
118 
119         /* get the number of entries in the table */
120         entry_cnt = osrp4(buf);
121 
122         /* read the index entries */
123         for (i = 0 ; i < entry_cnt ; ++i)
124         {
125             ulong res_ofs;
126             ulong res_siz;
127             ushort res_namsiz;
128 
129             /* read this entry */
130             if (osfrb(fp, buf, 10))
131                 errsig1(ec, ERR_RDRSC, ERRTSTR,
132                         errstr(ec, resfilename, strlen(resfilename)));
133 
134             /* get the entry header */
135             res_ofs = osrp4(buf);
136             res_siz = osrp4(buf + 4);
137             res_namsiz = osrp2(buf + 8);
138 
139             /* read this entry's name */
140             if (osfrb(fp, buf, res_namsiz))
141                 errsig1(ec, ERR_RDRSC, ERRTSTR,
142                         errstr(ec, resfilename, strlen(resfilename)));
143 
144             /* tell the host system about this entry */
145             if (appctx->add_resource)
146                 (*appctx->add_resource)(appctx->add_resource_ctx,
147                                         res_ofs, res_siz,
148                                         (char *)buf,
149                                         (size_t)res_namsiz,
150                                         resfileno);
151         }
152 
153         /* tell the host system where the resources start */
154         if (appctx->set_resmap_seek != 0)
155         {
156             long pos = osfpos(fp);
157             (*appctx->set_resmap_seek)(appctx->set_resmap_seek_ctx,
158                                        pos, resfileno);
159         }
160     }
161 }
162 
163 /*
164  *   Read an external resource file.  This is a limited version of the
165  *   general file reader that can only read resource files, not full game
166  *   files.
167  */
fiordrscext(errcxdef * ec,osfildef * fp,appctxdef * appctx,int resfileno,char * resfilename)168 static void fiordrscext(errcxdef *ec, osfildef *fp, appctxdef *appctx,
169                         int resfileno, char *resfilename)
170 {
171     uchar buf[TOKNAMMAX + 50];
172     unsigned long endpos;
173     unsigned long startofs;
174 
175     /* note the starting offset */
176     startofs = osfpos(fp);
177 
178     /* check file and version headers, and get flags and timestamp */
179     if (osfrb(fp, buf, (int)(sizeof(FIOFILHDR) + sizeof(FIOVSNHDR) + 2)))
180         errsig1(ec, ERR_RDRSC, ERRTSTR,
181                 errstr(ec, resfilename, strlen(resfilename)));
182     if (memcmp(buf, FIOFILHDRRSC, (size_t)sizeof(FIOFILHDRRSC)))
183         errsig1(ec, ERR_BADHDRRSC, ERRTSTR,
184                 errstr(ec, resfilename, strlen(resfilename)));
185     if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR,
186                (size_t)sizeof(FIOVSNHDR))
187         && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2,
188                   (size_t)sizeof(FIOVSNHDR2))
189         && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3,
190                   (size_t)sizeof(FIOVSNHDR3)))
191         errsig(ec, ERR_BADVSN);
192     if (osfrb(fp, buf, (size_t)26))
193         errsig1(ec, ERR_RDRSC, ERRTSTR,
194                 errstr(ec, resfilename, strlen(resfilename)));
195 
196     /* now read resources from the file */
197     for (;;)
198     {
199         /* read resource type and next-resource pointer */
200         if (osfrb(fp, buf, 1)
201             || osfrb(fp, buf + 1, (int)(buf[0] + 4)))
202             errsig1(ec, ERR_RDRSC, ERRTSTR,
203                     errstr(ec, resfilename, strlen(resfilename)));
204         endpos = osrp4(buf + 1 + buf[0]);
205 
206         /* check the resource type */
207         if (fioisrsc(buf, "HTMLRES"))
208         {
209             /* read the HTML resource map */
210             fiordhtml(ec, fp, appctx, resfileno, resfilename);
211 
212             /*
213              *   skip the resources - they're entirely for the host
214              *   application's use
215              */
216             osfseek(fp, endpos + startofs, OSFSK_SET);
217         }
218         else if (fioisrsc(buf, "$EOF"))
219         {
220             /* we're done reading the file */
221             break;
222         }
223         else
224             errsig(ec, ERR_UNKRSC);
225     }
226 }
227 
228 /*
229  *   read a game from a binary file
230  *
231  *   flags:
232  *      &1 ==> run preinit
233  *      &2 ==> preload objects
234  */
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)235 static void fiord1(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx,
236                    osfildef *fp, const char *fname,
237                    fiolcxdef *setupctx, ulong startofs,
238                    objnum *preinit, uint *flagp, tokpdef *path,
239                    uchar **fmtsp, uint *fmtlp, uint *pcntptr, int flags,
240                    appctxdef *appctx, char *argv0)
241 {
242     int         i;
243     int         siz;
244     uchar       buf[TOKNAMMAX + 50];
245     errcxdef   *ec = vctx->voccxerr;
246     ulong       endpos;
247     int         obj;
248     ulong       curpos;
249     runxdef    *ex;
250     ulong       eof_reset = 0;             /* reset here at EOF if non-zero */
251     int         xfcns_done = FALSE;                /* already loaded XFCN's */
252     ulong       xfcn_pos = 0;          /* location of XFCN's if preloadable */
253     uint        xor_seed = 17;                     /* seed value for fioxor */
254     uint        xor_inc = 29;                 /* increment value for fioxor */
255 
256     /* set up loader callback context */
257     setupctx->fiolcxfp = fp;
258     setupctx->fiolcxerr = ec;
259     setupctx->fiolcxst = startofs;
260     setupctx->fiolcxseed = xor_seed;
261     setupctx->fiolcxinc = xor_inc;
262 
263     /* check file and version headers, and get flags and timestamp */
264     if (osfrb(fp, buf, (int)(sizeof(FIOFILHDR) + sizeof(FIOVSNHDR) + 2)))
265         errsig(ec, ERR_RDGAM);
266     if (memcmp(buf, FIOFILHDR, (size_t)sizeof(FIOFILHDR)))
267          errsig(ec, ERR_BADHDR);
268     if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR,
269                (size_t)sizeof(FIOVSNHDR))
270         && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2,
271                   (size_t)sizeof(FIOVSNHDR2))
272         && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3,
273                   (size_t)sizeof(FIOVSNHDR3)))
274         errsig(ec, ERR_BADVSN);
275     if (osfrb(fp, vctx->voccxtim, (size_t)26)) errsig(ec, ERR_RDGAM);
276 
277     /*
278      *   if the game wasn't compiled with 2.2 or later, make a note,
279      *   because we need to ignore certain property flags (due to a bug in
280      *   the old compiler)
281      */
282     if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2,
283                (size_t)sizeof(FIOVSNHDR2)) == 0
284         || memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3,
285                   (size_t)sizeof(FIOVSNHDR3)) == 0)
286         mctx->mcmcxflg |= MCMCXF_NO_PRP_DEL;
287 
288     setupctx->fiolcxflg =
289         *flagp = osrp2(buf + sizeof(FIOFILHDR) + sizeof(FIOVSNHDR));
290 
291     /* now read resources from the file */
292     for (;;)
293     {
294         /* read resource type and next-resource pointer */
295         if (osfrb(fp, buf, 1)
296             || osfrb(fp, buf + 1, (int)(buf[0] + 4)))
297             errsig(ec, ERR_RDGAM);
298         endpos = osrp4(buf + 1 + buf[0]);
299 
300         if (fioisrsc(buf, "OBJ"))
301         {
302             /* skip regular objects if fast-load records are included */
303             if (*flagp & FIOFFAST)
304             {
305                 osfseek(fp, endpos + startofs, OSFSK_SET);
306                 continue;
307             }
308 
309             curpos = osfpos(fp) - startofs;
310             while (curpos != endpos)
311             {
312                 /* read type and object number */
313                 if (osfrb(fp, buf, 3)) errsig(ec, ERR_RDGAM);
314                 obj = osrp2(buf+1);
315 
316                 switch(buf[0])
317                 {
318                 case TOKSTFUNC:
319                 case TOKSTOBJ:
320                     if (osfrb(fp, buf + 3, 4)) errsig(ec, ERR_RDGAM);
321                     mcmrsrv(mctx, (ushort)osrp2(buf + 3), (mcmon)obj,
322                             (mclhd)curpos);
323                     curpos += osrp2(buf + 5) + 7;
324 
325                     /* load object if preloading */
326                     if (flags & 2)
327                     {
328                         (void)mcmlck(mctx, (mcmon)obj);
329                         mcmunlck(mctx, (mcmon)obj);
330                     }
331 
332                     /* seek past this object */
333                     osfseek(fp, curpos + startofs, OSFSK_SET);
334                     break;
335 
336                 case TOKSTFWDOBJ:
337                 case TOKSTFWDFN:
338                 {
339                     ushort  siz;
340                     uchar  *p;
341 
342                     if (osfrb(fp, buf+3, 2)) errsig(ec, ERR_RDGAM);
343                     siz = osrp2(buf+3);
344                     p = mcmalonum(mctx, siz, (mcmon)obj);
345                     if (osfrb(fp, p, siz)) errsig(ec, ERR_RDGAM);
346                     mcmunlck(mctx, (mcmon)obj);
347                     curpos += 5 + siz;
348                     break;
349                 }
350 
351                 case TOKSTEXTERN:
352                     if (!vctx->voccxrun->runcxext)
353                         errsig(ec, ERR_UNXEXT);
354                     ex = &vctx->voccxrun->runcxext[obj];
355 
356                     if (osfrb(fp, buf + 3, 1)
357                         || osfrb(fp, ex->runxnam, (int)buf[3]))
358                         errsig(ec, ERR_RDGAM);
359                     ex->runxnam[buf[3]] = '\0';
360                     curpos += buf[3] + 4;
361                     break;
362 
363                 default:
364                     errsig(ec, ERR_UNKOTYP);
365                 }
366             }
367         }
368         else if (fioisrsc(buf, "FST"))
369         {
370             uchar *p;
371             uchar *bufp;
372             ulong  siz;
373 
374             if (!(*flagp & FIOFFAST))
375             {
376                 osfseek(fp, endpos + startofs, OSFSK_SET);
377                 continue;
378             }
379 
380             curpos = osfpos(fp) - startofs;
381             siz = endpos - curpos;
382             if (siz && siz < OSMALMAX
383                 && (bufp = p = (uchar *)osmalloc((size_t)siz)) != 0)
384             {
385                 uchar *p1;
386                 ulong  siz2;
387                 uint   sizcur;
388 
389                 for (p1 = p, siz2 = siz ; siz2 ; siz2 -= sizcur, p1 += sizcur)
390                 {
391                     sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2);
392                     if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM);
393                 }
394 
395                 while (siz)
396                 {
397                     obj = osrp2(p + 1);
398                     switch(*p)
399                     {
400                     case TOKSTFUNC:
401                     case TOKSTOBJ:
402                         mcmrsrv(mctx, (ushort)osrp2(p + 3), (mcmon)obj,
403                                 (mclhd)osrp4(p + 7));
404                         p += 11;
405                         siz -= 11;
406 
407                         /* preload object if desired */
408                         if (flags & 2)
409                         {
410                             (void)mcmlck(mctx, (mcmon)obj);
411                             mcmunlck(mctx, (mcmon)obj);
412                         }
413                         break;
414 
415                     case TOKSTEXTERN:
416                         if (!vctx->voccxrun->runcxext)
417                             errsig(ec, ERR_UNXEXT);
418                         ex = &vctx->voccxrun->runcxext[obj];
419 
420                         memcpy(ex->runxnam, p + 4, (size_t)p[3]);
421                         ex->runxnam[p[3]] = '\0';
422                         siz -= p[3] + 4;
423                         p += p[3] + 4;
424                         break;
425 
426                     default:
427                         errsig(ec, ERR_UNKOTYP);
428                     }
429                 }
430 
431                 /* done with temporary block; free it */
432                 osfree(bufp);
433                 osfseek(fp, endpos + startofs, OSFSK_SET);
434             }
435             else
436             {
437                 while (curpos != endpos)
438                 {
439                     if (osfrb(fp, buf, 3)) errsig(ec, ERR_RDGAM);
440                     obj = osrp2(buf + 1);
441                     switch(buf[0])
442                     {
443                     case TOKSTFUNC:
444                     case TOKSTOBJ:
445                         if (osfrb(fp, buf + 3, 8)) errsig(ec, ERR_RDGAM);
446                         mcmrsrv(mctx, (ushort)osrp2(buf + 3), (mcmon)obj,
447                                 (mclhd)osrp4(buf + 7));
448                         curpos += 11;
449 
450                         /* preload object if desired */
451                         if (flags & 2)
452                         {
453                             (void)mcmlck(mctx, (mcmon)obj);
454                             mcmunlck(mctx, (mcmon)obj);
455                             osfseek(fp, curpos + startofs, OSFSK_SET);
456                         }
457                         break;
458 
459                     case TOKSTEXTERN:
460                         if (!vctx->voccxrun->runcxext)
461                             errsig(ec, ERR_UNXEXT);
462                         ex = &vctx->voccxrun->runcxext[obj];
463 
464                         if (osfrb(fp, buf + 3, 1)
465                             || osfrb(fp, ex->runxnam, (int)buf[3]))
466                             errsig(ec, ERR_RDGAM);
467                         ex->runxnam[buf[3]] = '\0';
468                         curpos += buf[3] + 4;
469                         break;
470 
471                     default:
472                         errsig(ec, ERR_UNKOTYP);
473                     }
474                 }
475             }
476 
477             /* if we can preload xfcn's, do so now */
478             if (xfcn_pos)
479             {
480                 eof_reset = endpos;    /* remember to return here when done */
481                 osfseek(fp, xfcn_pos, OSFSK_SET);           /* go to xfcn's */
482             }
483         }
484         else if (fioisrsc(buf, "XFCN"))
485         {
486             if (!vctx->voccxrun->runcxext) errsig(ec, ERR_UNXEXT);
487 
488             /* read length and name of resource */
489             if (osfrb(fp, buf, 3) || osfrb(fp, buf + 3, (int)buf[2]))
490                 errsig(ec, ERR_RDGAM);
491             siz = osrp2(buf);
492 
493 #if 0
494 /*
495  *   external functions are now obsolete - do not load
496  */
497 
498             /* look for an external function with the same name */
499             for (i = vctx->voccxrun->runcxexc,  ex = vctx->voccxrun->runcxext
500                  ; i ; ++ex, --i)
501             {
502                 j = strlen(ex->runxnam);
503                 if (j == buf[2] && !memcmp(buf + 3, ex->runxnam, (size_t)j))
504                     break;
505             }
506 
507             /* if we found an external function of this name, load it */
508             if (i && !xfcns_done)
509             {
510                 /* load the function */
511                 ex->runxptr = os_exfld(fp, (unsigned)siz);
512             }
513             else
514             {
515                 /* this XFCN isn't used; don't bother loading it */
516                 osfseek(fp, endpos + startofs, OSFSK_SET);
517             }
518 #else
519             /* external functions are obsolete; simply skip the data */
520             osfseek(fp, endpos + startofs, OSFSK_SET);
521 #endif
522         }
523         else if (fioisrsc(buf, "HTMLRES"))
524         {
525             /* read the resources */
526             fiordhtml(ec, fp, appctx, 0, fname);
527 
528             /*
529              *   skip the resources - they're entirely for the host
530              *   application's use
531              */
532             osfseek(fp, endpos + startofs, OSFSK_SET);
533         }
534         else if (fioisrsc(buf, "INH"))
535         {
536             uchar *p;
537             uchar *bufp;
538             ulong  siz;
539 
540             /* do it in a single file read, if we can, for speed */
541             curpos = osfpos(fp) - startofs;
542             siz = endpos - curpos;
543             if (siz && siz < OSMALMAX
544                 && (bufp = p = (uchar *)osmalloc((size_t)siz)) != 0)
545             {
546                 uchar *p1;
547                 ulong  siz2;
548                 uint   sizcur;
549 
550                 for (p1 = p, siz2 = siz ; siz2 ; siz2 -= sizcur, p1 += sizcur)
551                 {
552                     sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2);
553                     if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM);
554                 }
555 
556                 while (siz)
557                 {
558                     i = osrp2(p + 7);
559                     obj = osrp2(p + 1);
560 
561                     vociadd(vctx, (objnum)obj, (objnum)osrp2(p+3), i,
562                             (objnum *)(p + 9), p[0] | VOCIFXLAT);
563                     vocinh(vctx, obj)->vociilc = osrp2(p + 5);
564 
565                     p += 9 + (2 * i);
566                     siz -= 9 + (2 * i);
567                 }
568 
569                 /* done with temporary block; free it */
570                 osfree(bufp);
571             }
572             else
573             {
574                 while (curpos != endpos)
575                 {
576                     if (osfrb(fp, buf, 9)) errsig(ec, ERR_RDGAM);
577                     i = osrp2(buf + 7);       /* get number of superclasses */
578                     obj = osrp2(buf + 1);              /* get object number */
579                     if (i && osfrb(fp, buf + 9, 2 * i)) errsig(ec, ERR_RDGAM);
580 
581                     vociadd(vctx, (objnum)obj, (objnum)osrp2(buf+3),
582                             i, (objnum *)(buf + 9), buf[0] | VOCIFXLAT);
583                     vocinh(vctx, obj)->vociilc = osrp2(buf + 5);
584 
585                     curpos += 9 + (2 * i);
586                 }
587             }
588         }
589         else if (fioisrsc(buf, "REQ"))
590         {
591             curpos = osfpos(fp) - startofs;
592             siz = endpos - curpos;
593 
594             if (osfrb(fp, buf, (uint)siz)) errsig(ec, ERR_RDGAM);
595             vctx->voccxme  = vctx->voccxme_init = osrp2(buf);
596             vctx->voccxvtk = osrp2(buf+2);
597             vctx->voccxstr = osrp2(buf+4);
598             vctx->voccxnum = osrp2(buf+6);
599             vctx->voccxprd = osrp2(buf+8);
600             vctx->voccxvag = osrp2(buf+10);
601             vctx->voccxini = osrp2(buf+12);
602             vctx->voccxpre = osrp2(buf+14);
603             vctx->voccxper = osrp2(buf+16);
604 
605             /* if we have a cmdPrompt function, read it */
606             if (siz >= 20)
607                 vctx->voccxprom = osrp2(buf + 18);
608             else
609                 vctx->voccxprom = MCMONINV;
610 
611             /* if we have the NLS functions, read them */
612             if (siz >= 26)
613             {
614                 vctx->voccxpdis = osrp2(buf + 20);
615                 vctx->voccxper2 = osrp2(buf + 22);
616                 vctx->voccxpdef = osrp2(buf + 24);
617             }
618             else
619             {
620                 /* the new NLS functions aren't defined in this file */
621                 vctx->voccxpdis = MCMONINV;
622                 vctx->voccxper2 = MCMONINV;
623                 vctx->voccxpdef = MCMONINV;
624             }
625 
626             /* test for parseAskobj separately, as it was added later */
627             if (siz >= 28)
628                 vctx->voccxpask = osrp2(buf + 26);
629             else
630                 vctx->voccxpask = MCMONINV;
631 
632             /* test for preparseCmd separately - it's another late comer */
633             if (siz >= 30)
634                 vctx->voccxppc = osrp2(buf + 28);
635             else
636                 vctx->voccxppc = MCMONINV;
637 
638             /* check for parseAskobjActor separately - another late comer */
639             if (siz >= 32)
640                 vctx->voccxpask2 = osrp2(buf + 30);
641             else
642                 vctx->voccxpask2 = MCMONINV;
643 
644             /* if we have parseErrorParam, read it as well */
645             if (siz >= 34)
646             {
647                 vctx->voccxperp = osrp2(buf + 32);
648             }
649             else
650             {
651                 /* parseErrorParam isn't defined in this file */
652                 vctx->voccxperp = MCMONINV;
653             }
654 
655             /*
656              *   if we have commandAfterRead and initRestore, read them as
657              *   well
658              */
659             if (siz >= 38)
660             {
661                 vctx->voccxpostprom = osrp2(buf + 34);
662                 vctx->voccxinitrestore = osrp2(buf + 36);
663             }
664             else
665             {
666                 /* these new functions aren't defined in this game */
667                 vctx->voccxpostprom = MCMONINV;
668                 vctx->voccxinitrestore = MCMONINV;
669             }
670 
671             /* check for and read parseUnknownVerb, parseNounPhrase */
672             if (siz >= 42)
673             {
674                 vctx->voccxpuv = osrp2(buf + 38);
675                 vctx->voccxpnp = osrp2(buf + 40);
676             }
677             else
678             {
679                 vctx->voccxpuv = MCMONINV;
680                 vctx->voccxpnp = MCMONINV;
681             }
682 
683             /* check for postAction, endCommand */
684             if (siz >= 48)
685             {
686                 vctx->voccxpostact = osrp2(buf + 42);
687                 vctx->voccxendcmd = osrp2(buf + 44);
688                 vctx->voccxprecmd = osrp2(buf + 46);
689             }
690             else
691             {
692                 vctx->voccxpostact = MCMONINV;
693                 vctx->voccxendcmd = MCMONINV;
694                 vctx->voccxprecmd = MCMONINV;
695             }
696 
697             /* check for parseAskobjIndirect */
698             if (siz >= 50)
699                 vctx->voccxpask3 = osrp2(buf + 48);
700             else
701                 vctx->voccxpask3 = MCMONINV;
702 
703             /* check for preparseExt and parseDefaultExt */
704             if (siz >= 54)
705             {
706                 vctx->voccxpre2 = osrp2(buf + 50);
707                 vctx->voccxpdef2 = osrp2(buf + 52);
708             }
709             else
710             {
711                 vctx->voccxpre2 = MCMONINV;
712                 vctx->voccxpdef2 = MCMONINV;
713             }
714         }
715         else if (fioisrsc(buf, "VOC"))
716         {
717             uchar *p;
718             uchar *bufp;
719             ulong  siz;
720             int    len1;
721             int    len2;
722 
723             /* do it in a single file read, if we can, for speed */
724             curpos = osfpos(fp) - startofs;
725             siz = endpos - curpos;
726             if (siz && siz < OSMALMAX
727                 && (bufp = p = (uchar *)osmalloc((size_t)siz)) != 0)
728             {
729                 uchar *p1;
730                 ulong  siz2;
731                 uint   sizcur;
732 
733                 for (p1 = p, siz2 = siz ; siz2 ; siz2 -= sizcur, p1 += sizcur)
734                 {
735                     sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2);
736                     if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM);
737                 }
738 
739                 while (siz)
740                 {
741                     len1 = osrp2(p);
742                     len2 = osrp2(p + 2);
743                     if (*flagp & FIOFCRYPT)
744                         fioxor(p + 10, (uint)(len1 + len2),
745                                xor_seed, xor_inc);
746                     vocadd2(vctx, (prpnum)osrp2(p+4), (objnum)osrp2(p+6),
747                             osrp2(p+8), p + 10, len1,
748                             (len2 ? p + 10 + len1 : (uchar*)0), len2);
749 
750                     p += 10 + len1 + len2;
751                     siz -= 10 + len1 + len2;
752                 }
753 
754                 /* done with the temporary block; free it up */
755                 osfree(bufp);
756             }
757             else
758             {
759                 /* can't do it in one file read; do it the slow way */
760                 while (curpos != endpos)
761                 {
762                     if (osfrb(fp, buf, 10)
763                         || osfrb(fp, buf + 10,
764                                (len1 = osrp2(buf)) + (len2 = osrp2(buf + 2))))
765                         errsig(ec, ERR_RDGAM);
766 
767                     if (*flagp & FIOFCRYPT)
768                         fioxor(buf + 10, (uint)(len1 + len2),
769                                xor_seed, xor_inc);
770                     vocadd2(vctx, (prpnum)osrp2(buf+4), (objnum)osrp2(buf+6),
771                             osrp2(buf+8), buf + 10, len1,
772                             (len2 ? buf + 10 + len1 : (uchar*)0), len2);
773                     curpos += 10 + len1 + len2;
774                 }
775             }
776         }
777         else if (fioisrsc(buf, "FMTSTR"))
778         {
779             uchar *fmts;
780             uint   fmtl;
781 
782             if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
783             fmtl = osrp2(buf);
784             fmts = mchalo(vctx->voccxerr, (ushort)fmtl, "fiord1");
785             if (osfrb(fp, fmts, fmtl)) errsig(ec, ERR_RDGAM);
786             if (*flagp & FIOFCRYPT) fioxor(fmts, fmtl, xor_seed, xor_inc);
787             tiosetfmt(vctx->voccxtio, vctx->voccxrun, fmts, fmtl);
788 
789             if (fmtsp) *fmtsp = fmts;
790             if (fmtlp) *fmtlp = fmtl;
791         }
792         else if (fioisrsc(buf, "CMPD"))
793         {
794             if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
795             vctx->voccxcpl = osrp2(buf);
796             vctx->voccxcpp = (char *)mchalo(vctx->voccxerr,
797                                             (ushort)vctx->voccxcpl, "fiord1");
798             if (osfrb(fp, vctx->voccxcpp, (uint)vctx->voccxcpl))
799                 errsig(ec, ERR_RDGAM);
800             if (*flagp & FIOFCRYPT)
801                 fioxor((uchar *)vctx->voccxcpp, (uint)vctx->voccxcpl,
802                        xor_seed, xor_inc);
803         }
804         else if (fioisrsc(buf, "SPECWORD"))
805         {
806             if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
807             vctx->voccxspl = osrp2(buf);
808             vctx->voccxspp = (char *)mchalo(vctx->voccxerr,
809                                             (ushort)vctx->voccxspl, "fiord1");
810             if (osfrb(fp, vctx->voccxspp, (uint)vctx->voccxspl))
811                 errsig(ec, ERR_RDGAM);
812             if (*flagp & FIOFCRYPT)
813                 fioxor((uchar *)vctx->voccxspp, (uint)vctx->voccxspl,
814                        xor_seed, xor_inc);
815         }
816         else if (fioisrsc(buf, "SYMTAB"))
817         {
818             tokthdef *symtab;
819 
820             /* if there's no debugger context, don't bother with this */
821             if (!vctx->voccxrun->runcxdbg)
822             {
823                 osfseek(fp, endpos + startofs, OSFSK_SET);
824                 continue;
825             }
826 
827             if (!(symtab = vctx->voccxrun->runcxdbg->dbgcxtab))
828             {
829                 symtab = (tokthdef *)mchalo(ec, (ushort)sizeof(tokthdef),
830                                             "fiord:symtab");
831                 tokthini(ec, mctx, (toktdef *)symtab);
832                 vctx->voccxrun->runcxdbg->dbgcxtab = symtab;
833             }
834 
835             /* read symbols until we find a zero-length symbol */
836             for (;;)
837             {
838                 int hash;
839 
840                 if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM);
841                 if (buf[0] == 0) break;
842                 if (osfrb(fp, buf + 4, (int)buf[0])) errsig(ec, ERR_RDGAM);
843                 buf[4 + buf[0]] = '\0';
844                 hash = tokhsh((char *)buf + 4);
845 
846                 (*symtab->tokthsc.toktfadd)((toktdef *)symtab,
847                                             (char *)buf + 4,
848                                             (int)buf[0], (int)buf[1],
849                                             osrp2(buf + 2), hash);
850             }
851         }
852         else if (fioisrsc(buf, "SRC"))
853         {
854             /* skip source file id's if there's no debugger context */
855             if (vctx->voccxrun->runcxdbg == 0)
856             {
857                 osfseek(fp, endpos + startofs, OSFSK_SET);
858                 continue;
859             }
860 
861             while ((osfpos(fp) - startofs) != endpos)
862             {
863                 /* the only thing we know how to read is linfdef's */
864                 if (linfload(fp, vctx->voccxrun->runcxdbg, ec, path))
865                     errsig(ec, ERR_RDGAM);
866             }
867         }
868         else if (fioisrsc(buf, "SRC2"))
869         {
870             /*
871              *   this is simply a marker indicating that we have new-style
872              *   (line-number-based) source debugging information in the
873              *   file -- set the new-style debug info flag
874              */
875             if (vctx->voccxrun->runcxdbg != 0)
876                 vctx->voccxrun->runcxdbg->dbgcxflg |= DBGCXFLIN2;
877 
878             /* the contents are empty - skip the block */
879             osfseek(fp, endpos + startofs, OSFSK_SET);
880         }
881         else if (fioisrsc(buf, "PREINIT"))
882         {
883             if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
884             *preinit = osrp2(buf);
885         }
886         else if (fioisrsc(buf, "ERRMSG"))
887         {
888             errini(ec, fp);
889             osfseek(fp, endpos + startofs, OSFSK_SET);
890         }
891         else if (fioisrsc(buf, "EXTCNT"))
892         {
893             uchar  *p;
894             ushort  len;
895             ulong   siz;
896 
897             curpos = osfpos(fp) - startofs;
898             siz = endpos - curpos;
899             if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
900             i = osrp2(buf);
901 
902             len = i * sizeof(runxdef);
903             p = mchalo(ec, len, "fiord:runxdef");
904             memset(p, 0, (size_t)len);
905 
906             vctx->voccxrun->runcxext = (runxdef *)p;
907             vctx->voccxrun->runcxexc = i;
908 
909             /* see if start-of-XFCN information is present */
910             if (siz >= 6)
911             {
912                 /* get location of first XFCN, and seek there */
913                 if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM);
914                 xfcn_pos = osrp4(buf);
915             }
916 
917             /* seek past this resource */
918             osfseek(fp, endpos + startofs, OSFSK_SET);
919         }
920         else if (fioisrsc(buf, "PRPCNT"))
921         {
922             if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
923             if (pcntptr) *pcntptr = osrp2(buf);
924         }
925         else if (fioisrsc(buf, "TADSPP") && tctx != 0)
926         {
927             tok_read_defines(tctx, fp, ec);
928         }
929         else if (fioisrsc(buf, "XSI"))
930         {
931             if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
932             setupctx->fiolcxseed = xor_seed = buf[0];
933             setupctx->fiolcxinc = xor_inc = buf[1];
934             osfseek(fp, endpos + startofs, OSFSK_SET);
935         }
936         else if (fioisrsc(buf, "CHRSET"))
937         {
938             size_t len;
939 
940             /* read the character set ID and LDESC */
941             if (osfrb(fp, buf, 6)
942                 || (len = osrp2(buf+4)) > CMAP_LDESC_MAX_LEN
943                 || osfrb(fp, buf+6, len))
944                 errsig(ec, ERR_RDGAM);
945 
946             /* establish this character set mapping */
947             buf[4] = '\0';
948             cmap_set_game_charset(ec, (char *)buf, (char *)buf + 6, argv0);
949         }
950         else if (fioisrsc(buf, "$EOF"))
951         {
952             if (eof_reset)
953             {
954                 osfseek(fp, eof_reset, OSFSK_SET);     /* back after EXTCNT */
955                 eof_reset = 0;                   /* really done at next EOF */
956                 xfcns_done = TRUE;                 /* don't do XFCN's again */
957             }
958             else
959                 break;
960         }
961         else
962             errsig(ec, ERR_UNKRSC);
963     }
964 }
965 
966 /* read binary file */
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)967 void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, char *fname,
968            char *exename, fiolcxdef *setupctx, objnum *preinit, uint *flagp,
969            tokpdef *path, uchar **fmtsp, uint *fmtlp, uint *pcntptr,
970            int flags, struct appctxdef *appctx, char *argv0)
971 {
972     osfildef *fp;
973     ulong     startofs;
974     char     *display_fname;
975 
976     /* presume there will be no need to run preinit */
977     *preinit = MCMONINV;
978 
979     /*
980      *   get the display filename - use the real filename if one is
981      *   provided, otherwise use the name of the executable file itself
982      */
983     display_fname = (fname != 0 ? fname : exename);
984 
985     /* open the file and read and check file header */
986     fp = (fname != 0 ? osfoprb(fname, OSFTGAME)
987                      : os_exeseek(exename, "TGAM"));
988     if (fp == 0)
989         errsig(vctx->voccxerr, ERR_OPRGAM);
990 
991     /*
992      *   we've identified the .GAM file source - tell the host system
993      *   about it, if it's interested
994      */
995     if (appctx != 0 && appctx->set_game_name != 0)
996         (*appctx->set_game_name)(appctx->set_game_name_ctx, display_fname);
997 
998     /* remember starting location in file */
999     startofs = osfpos(fp);
1000 
1001     ERRBEGIN(vctx->voccxerr)
1002 
1003     /*
1004      *   Read the game file.  Note that the .GAM file always has resource
1005      *   file number zero.
1006      */
1007     fiord1(mctx, vctx, tctx, fp, display_fname,
1008            setupctx, startofs, preinit, flagp, path,
1009            fmtsp, fmtlp, pcntptr, flags, appctx, argv0);
1010 
1011     /*
1012      *   If the host system accepts additional resource files, look for
1013      *   additional resource files.  These are files in the same directory
1014      *   as the .GAM file, with the .GAM suffix replaced by suffixes from
1015      *.  RS0 to .RS9.
1016      */
1017     if (appctx != 0 && appctx->add_resfile != 0)
1018     {
1019         char suffix_lc[4];
1020         char suffix_uc[4];
1021         int i;
1022         char *base_name;
1023 
1024         /* use the game or executable filename, as appropriate */
1025         base_name = display_fname;
1026 
1027         /* build the initial suffixes - try both upper- and lower-case */
1028         suffix_uc[0] = 'R';
1029         suffix_uc[1] = 'S';
1030         suffix_uc[3] = '\0';
1031         suffix_lc[0] = 'r';
1032         suffix_lc[1] = 's';
1033         suffix_lc[3] = '\0';
1034 
1035         /* loop through each possible suffix (.RS0 through .RS9) */
1036         for (i = 0 ; i < 9 ; ++i)
1037         {
1038             char resname[OSFNMAX];
1039             osfildef *fpres;
1040             int resfileno;
1041 
1042             /*
1043              *   Build the next resource filename.  If there's an explicit
1044              *   resource path, use it, otherwise use the same directory
1045              *   that contains the .GAM file.
1046              */
1047             if (appctx->ext_res_path != 0)
1048             {
1049                 /*
1050                  *   There's an explicit resource path - append the root
1051                  *   (filename-only, minus path) portion of the .GAM file
1052                  *   name to the resource path.
1053                  */
1054                 os_build_full_path(resname, sizeof(resname),
1055                                    appctx->ext_res_path,
1056                                    os_get_root_name(base_name));
1057             }
1058             else
1059             {
1060                 /*
1061                  *   there's no resource path - use the entire .GAM
1062                  *   filename, including directory, so that we look in the
1063                  *   same directory that contains the .GAM file
1064                  */
1065                 if (base_name != 0)
1066                     strcpy(resname, base_name);
1067                 else
1068                     resname[0] = '\0';
1069             }
1070 
1071             /* add the current extension (replacing any current extension) */
1072             os_remext(resname);
1073             suffix_lc[2] = suffix_uc[2] = '0' + i;
1074             os_addext(resname, suffix_lc);
1075 
1076             /* try opening the file */
1077             fpres = osfoprb(resname, OSFTGAME);
1078 
1079             /* if that didn't work, try the upper-case name */
1080             if (fpres == 0)
1081             {
1082                 /* replace the suffix with the upper-case version */
1083                 os_remext(resname);
1084                 os_addext(resname, suffix_uc);
1085 
1086                 /* try again with the new name */
1087                 fpres = osfoprb(resname, OSFTGAME);
1088             }
1089 
1090             /* if we opened it successfully, read it */
1091             if (fpres != 0)
1092             {
1093                 /* tell the host system about it */
1094                 resfileno = (*appctx->add_resfile)
1095                             (appctx->add_resfile_ctx, resname);
1096 
1097                 /* read the file */
1098                 fiordrscext(vctx->voccxerr, fpres, appctx,
1099                             resfileno, resname);
1100 
1101                 /* we're done with the file, so close it */
1102                 osfcls(fpres);
1103             }
1104         }
1105     }
1106 
1107     ERRCLEAN(vctx->voccxerr)
1108         /* if an error occurs during read, clean up by closing the file */
1109         osfcls(fp);
1110     ERRENDCLN(vctx->voccxerr);
1111 }
1112 
1113 /* save game header */
1114 #define FIOSAVHDR "TADS2 save\012\015\032"
1115 
1116 /* save game header prefix - .GAM file information */
1117 #define FIOSAVHDR_PREFIX "TADS2 save/g\012\015\032"
1118 
1119 /*
1120  *   Saved game format version string - note that the length of the
1121  *   version string must be fixed, so when this is updated, it must be
1122  *   updated to another string of the same length.  This should be updated
1123  *   whenever a change is made to the format that can't be otherwise
1124  *   detected from the data stream in the saved game file.
1125  */
1126 #define FIOSAVVSN "v2.2.1"
1127 
1128 /* old saved game format version strings */
1129 #define FIOSAVVSN1 "v2.2.0"
1130 
1131 /* read fuse/daemon/alarm record */
fiorfda(osfildef * fp,vocddef * p,uint cnt)1132 static int fiorfda(osfildef *fp, vocddef *p, uint cnt)
1133 {
1134     vocddef *q;
1135     uint     i;
1136     uchar    buf[14];
1137 
1138     /* start by clearing out entire record */
1139     for (i = 0, q = p ; i < cnt ; ++q, ++i)
1140         q->vocdfn = MCMONINV;
1141 
1142     /* now restore all the records from the file */
1143     for (;;)
1144     {
1145         /* read a record, and quit if it's the last one */
1146         if (osfrb(fp, buf, 13)) return(TRUE);
1147         if ((i = osrp2(buf)) == 0xffff) return(FALSE);
1148 
1149         /* restore this record */
1150         q = p + i;
1151         q->vocdfn = osrp2(buf+2);
1152         q->vocdarg.runstyp = buf[4];
1153         switch(buf[4])
1154         {
1155         case DAT_NUMBER:
1156             q->vocdarg.runsv.runsvnum = osrp4(buf+5);
1157             break;
1158         case DAT_OBJECT:
1159         case DAT_FNADDR:
1160             q->vocdarg.runsv.runsvobj = osrp2(buf+5);
1161             break;
1162         case DAT_PROPNUM:
1163             q->vocdarg.runsv.runsvprp = osrp2(buf+5);
1164             break;
1165         }
1166         q->vocdprp = osrp2(buf+9);
1167         q->vocdtim = osrp2(buf+11);
1168     }
1169 }
1170 
1171 /*
1172  *   Look in a saved game file to determine if it has information on which
1173  *   GAM file created it.  If the GAM file information is available, this
1174  *   routine returns true and stores the game file name in the given
1175  *   buffer; if the information isn't available, we'll return false.
1176  */
fiorso_getgame(char * saved_file,char * fnamebuf,size_t buflen)1177 int fiorso_getgame(char *saved_file, char *fnamebuf, size_t buflen)
1178 {
1179     osfildef *fp;
1180     uint      namelen;
1181     char      buf[sizeof(FIOSAVHDR_PREFIX) + 2];
1182 
1183     /* open the input file */
1184     if (!(fp = osfoprb(saved_file, OSFTSAVE)))
1185         return FALSE;
1186 
1187     /* read the prefix header and check */
1188     if (osfrb(fp, buf, (int)(sizeof(FIOSAVHDR_PREFIX) + 2))
1189         || memcmp(buf, FIOSAVHDR_PREFIX, sizeof(FIOSAVHDR_PREFIX)) != 0)
1190     {
1191         /*
1192          *   there's no game file information - close the file and
1193          *   indicate that we have no information
1194          */
1195         osfcls(fp);
1196         return FALSE;
1197     }
1198 
1199     /* get the length of the filename */
1200     namelen = osrp2(buf + sizeof(FIOSAVHDR_PREFIX));
1201     if (namelen > buflen - 1)
1202         namelen = buflen - 1;
1203 
1204     /* read the filename */
1205     osfrb(fp, fnamebuf, namelen);
1206 
1207     /* null-terminate the string */
1208     fnamebuf[namelen] = '\0';
1209 
1210     /* done with the file */
1211     osfcls(fp);
1212 
1213     /* indicate that we found the information */
1214     return TRUE;
1215 }
1216 
1217 /* restore game: returns TRUE on failure */
fiorso(voccxdef * vctx,char * fname)1218 int fiorso(voccxdef *vctx, char *fname)
1219 {
1220     osfildef   *fp;
1221     objnum      obj;
1222     uchar      *p;
1223     uchar      *mut;
1224     uint        mutsiz;
1225     uint        oldmutsiz;
1226     int         propcnt;
1227     mcmcxdef   *mctx = vctx->voccxmem;
1228     uchar       buf[sizeof(FIOSAVHDR) + sizeof(FIOSAVVSN)];
1229     ushort      newsiz;
1230     int         err = FALSE;
1231     char        timestamp[26];
1232     int         version = 0;            /* version ID - 0 = current version */
1233     int         result;
1234 
1235     /* presume success */
1236     result = FIORSO_SUCCESS;
1237 
1238     /* open the input file */
1239     if (!(fp = osfoprb(fname, OSFTSAVE)))
1240         return FIORSO_FILE_NOT_FOUND;
1241 
1242     /* check for a prefix header - if it's there, skip it */
1243     if (!osfrb(fp, buf, (int)(sizeof(FIOSAVHDR_PREFIX) + 2))
1244         && memcmp(buf, FIOSAVHDR_PREFIX, sizeof(FIOSAVHDR_PREFIX)) == 0)
1245     {
1246         ulong skip_len;
1247 
1248         /*
1249          *   The prefix header is present - skip it.  The 2-byte value
1250          *   following the header is the length of the prefix data block
1251          *   (not including the header), so simply skip the additional
1252          *   number of bytes specified.
1253          */
1254         skip_len = (ulong)osrp2(buf + sizeof(FIOSAVHDR_PREFIX));
1255         osfseek(fp, skip_len, OSFSK_CUR);
1256     }
1257     else
1258     {
1259         /*
1260          *   there's no prefix header - seek back to the start of the file
1261          *   and read the standard header information
1262          */
1263         osfseek(fp, 0, OSFSK_SET);
1264     }
1265 
1266 
1267     /* read headers and check */
1268     if (osfrb(fp, buf, (int)(sizeof(FIOSAVHDR) + sizeof(FIOSAVVSN)))
1269         || memcmp(buf, FIOSAVHDR, (size_t)sizeof(FIOSAVHDR)))
1270     {
1271         /* it's not a saved game file */
1272         result = FIORSO_NOT_SAVE_FILE;
1273         goto ret_error;
1274     }
1275 
1276     /* check the version string */
1277     if (memcmp(buf + sizeof(FIOSAVHDR), FIOSAVVSN,
1278                (size_t)sizeof(FIOSAVVSN)) == 0)
1279     {
1280         /* it's the current version */
1281         version = 0;
1282     }
1283     else if (memcmp(buf + sizeof(FIOSAVHDR), FIOSAVVSN1,
1284                     (size_t)sizeof(FIOSAVVSN1)) == 0)
1285     {
1286         /* it's old version #1 */
1287         version = 1;
1288     }
1289     else
1290     {
1291         /*
1292          *   this isn't a recognized version - the file must have been
1293          *   saved by a newer version of the system, so we can't assume we
1294          *   will be able to parse the format
1295          */
1296         result = FIORSO_BAD_FMT_VSN;
1297         goto ret_error;
1298     }
1299 
1300     /*
1301      *   Read timestamp and check - the game must have been saved by the
1302      *   same .GAM file that we are now running, because the .SAV file is
1303      *   written entirely in terms of the contents of the .GAM file; any
1304      *   change in the .GAM file invalidates the .SAV file.
1305      */
1306     if (osfrb(fp, timestamp, 26)
1307         || memcmp(timestamp, vctx->voccxtim, (size_t)26))
1308     {
1309         result = FIORSO_BAD_GAME_VSN;
1310         goto ret_error;
1311     }
1312 
1313     /* first revert every object to original (post-compilation) state */
1314     vocrevert(vctx);
1315 
1316     /*
1317      *   the most common error from here on is simply a file read error,
1318      *   so presume that this is what will happen; if we are successful or
1319      *   encounter a different error, we'll change the status at that
1320      *   point
1321      */
1322     result = FIORSO_READ_ERROR;
1323 
1324     /* go through file and load changed objects */
1325     for (;;)
1326     {
1327         /* get the header */
1328         if (osfrb(fp, buf, 7))
1329             goto ret_error;
1330 
1331         /* get the object number from the header, and stop if we're done */
1332         obj = osrp2(buf+1);
1333         if (obj == MCMONINV)
1334             break;
1335 
1336         /* if the object was dynamically allocated, recreate it */
1337         if (buf[0] == 1)
1338         {
1339             int     sccnt;
1340             objnum  sc;
1341 
1342             /* create the object */
1343             mutsiz = osrp2(buf + 3);
1344             p = mcmalonum(mctx, (ushort)mutsiz, (mcmon)obj);
1345 
1346             /* read the object's contents */
1347             if (osfrb(fp, p, mutsiz))
1348                 goto ret_error;
1349 
1350             /* get the superclass data (at most one superclass) */
1351             sccnt = objnsc(p);
1352             if (sccnt) sc = osrp2(objsc(p));
1353 
1354             /* create inheritance records for the object */
1355             vociadd(vctx, obj, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC);
1356 
1357 #if 0
1358             {
1359                 int     wrdcnt;
1360 
1361                 /* read the object's vocabulary and add it back */
1362                 if (osfrb(fp, buf, 2))
1363                     goto ret_error;
1364                 wrdcnt = osrp2(buf);
1365                 while (wrdcnt--)
1366                 {
1367                     int   len1;
1368                     int   len2;
1369                     char  wrd[80];
1370 
1371                     /* read the header */
1372                     if (osfrb(fp, buf, 6))
1373                         goto ret_error;
1374                     len1 = osrp2(buf+2);
1375                     len2 = osrp2(buf+4);
1376 
1377                     /* read the word text */
1378                     if (osfrb(fp, wrd, len1 + len2))
1379                         goto ret_error;
1380 
1381                     /* add the word */
1382                     vocadd2(vctx, buf[0], obj, buf[1], wrd, len1,
1383                             wrd + len1, len2);
1384                 }
1385             }
1386 #endif
1387 
1388         }
1389         else
1390         {
1391             /* get the remaining data from the header */
1392             propcnt = osrp2(buf + 3);
1393             mutsiz = osrp2(buf + 5);
1394 
1395             /* expand object if it's not big enough for mutsiz */
1396             p = mcmlck(mctx, (mcmon)obj);
1397             oldmutsiz = mcmobjsiz(mctx, (mcmon)obj) - objrst(p);
1398             if (oldmutsiz < mutsiz)
1399             {
1400                 newsiz = mutsiz - oldmutsiz;
1401                 p = (uchar *)objexp(mctx, obj, &newsiz);
1402             }
1403 
1404             /* reset statistics, and read mutable part from file */
1405             mut = p + objrst(p);
1406             objsnp(p, propcnt);
1407             objsfree(p, mutsiz + objrst(p));
1408             if (osfrb(fp, mut, mutsiz))
1409                 err = TRUE;
1410 
1411             /* reset ignore flags as needed */
1412             objsetign(mctx, obj);
1413         }
1414 
1415         /* touch and unlock the object */
1416         mcmtch(mctx, (mcmon)obj);
1417         mcmunlck(mctx, (mcmon)obj);
1418         if (err)
1419             goto ret_error;
1420     }
1421 
1422     /* read fuses/daemons/alarms */
1423     if (fiorfda(fp, vctx->voccxdmn, vctx->voccxdmc)
1424         || fiorfda(fp, vctx->voccxfus, vctx->voccxfuc)
1425         || fiorfda(fp, vctx->voccxalm, vctx->voccxalc))
1426         goto ret_error;
1427 
1428     /* read the dynamically added and deleted vocabulary */
1429     for (;;)
1430     {
1431         int     len1;
1432         int     len2;
1433         char    wrd[80];
1434         int     flags;
1435         int     typ;
1436 
1437         /* read the header */
1438         if (osfrb(fp, buf, 8))
1439             goto ret_error;
1440 
1441         typ = buf[0];
1442         flags = buf[1];
1443         len1 = osrp2(buf+2);
1444         len2 = osrp2(buf+4);
1445         obj = osrp2(buf+6);
1446 
1447         /* check to see if this is the end marker */
1448         if (obj == MCMONINV) break;
1449 
1450         /* read the word text */
1451         if (osfrb(fp, wrd+2, len1))
1452             goto ret_error;
1453         if (len2)
1454         {
1455             wrd[len1 + 2] = ' ';
1456             if (osfrb(fp, &wrd[len1 + 3], len2))
1457                 goto ret_error;
1458             oswp2(wrd, len1 + len2 + 3);
1459         }
1460         else
1461             oswp2(wrd, len1 + 2);
1462 
1463         /* add or delete the word as appropriate */
1464         if (flags & VOCFDEL)
1465             vocdel1(vctx, obj, (char *)wrd, (prpnum)typ, FALSE, FALSE, FALSE);
1466         else
1467             vocadd2(vctx, buf[0], obj, buf[1], (uchar *)wrd+2, len1,
1468                     (uchar *)wrd+len1, len2);
1469     }
1470 
1471     /*
1472      *   the following was added in save format version "v2.2.1", so skip
1473      *   it if the save version is older than that
1474      */
1475     if (version != 1)
1476     {
1477         /* read the current "Me" object */
1478         if (osfrb(fp, buf, 2))
1479             goto ret_error;
1480         vctx->voccxme = osrp2(buf);
1481     }
1482 
1483     /* done - close file and return success indication */
1484     osfcls(fp);
1485     return FIORSO_SUCCESS;
1486 
1487     /* come here on failure - close file and return error indication */
1488 ret_error:
1489     osfcls(fp);
1490     return result;
1491 }
1492 
1493 /* write fuse/daemon/alarm block */
fiowfda(osfildef * fp,vocddef * p,uint cnt)1494 static int fiowfda(osfildef *fp, vocddef *p, uint cnt)
1495 {
1496     uchar buf[14];
1497     uint  i;
1498 
1499     for (i = 0 ; i < cnt ; ++i, ++p)
1500     {
1501         if (p->vocdfn == MCMONINV) continue;            /* not set - ignore */
1502 
1503         oswp2(buf, i);                        /* element in array to be set */
1504         oswp2(buf+2, p->vocdfn);       /* object number for function/target */
1505         buf[4] = p->vocdarg.runstyp;                    /* type of argument */
1506         switch(buf[4])
1507         {
1508         case DAT_NUMBER:
1509             oswp4(buf+5, p->vocdarg.runsv.runsvnum);
1510             break;
1511         case DAT_OBJECT:
1512         case DAT_FNADDR:
1513             oswp2(buf+5, p->vocdarg.runsv.runsvobj);
1514             break;
1515         case DAT_PROPNUM:
1516             oswp2(buf+5, p->vocdarg.runsv.runsvprp);
1517             break;
1518         }
1519         oswp2(buf+9, p->vocdprp);
1520         oswp2(buf+11, p->vocdtim);
1521 
1522         /* write this record to file */
1523         if (osfwb(fp, buf, 13)) return(TRUE);
1524     }
1525 
1526     /* write end record - -1 for array element number */
1527     oswp2(buf, 0xffff);
1528     return(osfwb(fp, buf, 13));
1529 }
1530 
1531 /* context for vocabulary saver callback function */
1532 struct fiosav_cb_ctx
1533 {
1534     int       err;
1535     osfildef *fp;
1536 };
1537 
1538 #ifdef NEVER
1539 /*
1540  *   callback for vocabulary saver - called by voc_iterate for each word
1541  *   defined for a particular object, allowing us to write all the words
1542  *   attached to a dynamically allocated object to the save file
1543  */
fiosav_cb(struct fiosav_cb_ctx * ctx,vocdef * voc,vocwdef * vocw)1544 static void fiosav_cb(struct fiosav_cb_ctx *ctx,
1545                       vocdef *voc, vocwdef *vocw)
1546 {
1547     char buf[10];
1548 
1549     /* write the part of speech, flags, and word lengths */
1550     buf[0] = vocw->vocwtyp;
1551     buf[1] = vocw->vocwflg;
1552     oswp2(buf+2, voc->voclen);
1553     oswp2(buf+4, voc->vocln2);
1554     if (osfwb(ctx->fp, buf, 6)) ctx->err = TRUE;
1555 
1556     /* write the words */
1557     if (osfwb(ctx->fp, voc->voctxt, voc->voclen + voc->vocln2))
1558         ctx->err = TRUE;
1559 }
1560 #endif
1561 
1562 /*
1563  *   Callback for vocabulary saver - called by voc_iterate for every
1564  *   word.  We'll write the word if it was dynamically added or deleted,
1565  *   so that we can restore that status when the game is restored.
1566  */
fiosav_voc_cb(void * ctx0,vocdef * voc,vocwdef * vocw)1567 static void fiosav_voc_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
1568 {
1569     struct fiosav_cb_ctx *ctx = (struct fiosav_cb_ctx *)ctx0;
1570     char buf[10];
1571 
1572     /* if the word was dynamically allocated or deleted, save it */
1573     if ((vocw->vocwflg & VOCFNEW) || (vocw->vocwflg & VOCFDEL))
1574     {
1575         /* write the header information */
1576         buf[0] = vocw->vocwtyp;
1577         buf[1] = vocw->vocwflg;
1578         oswp2(buf+2, voc->voclen);
1579         oswp2(buf+4, voc->vocln2);
1580         oswp2(buf+6, vocw->vocwobj);
1581         if (osfwb(ctx->fp, buf, 8)) ctx->err = TRUE;
1582 
1583         /* write the words */
1584         if (osfwb(ctx->fp, voc->voctxt, voc->voclen + voc->vocln2))
1585             ctx->err = TRUE;
1586     }
1587 }
1588 
1589 
1590 /* save game; returns TRUE on failure */
fiosav(voccxdef * vctx,char * fname,char * game_fname)1591 int fiosav(voccxdef *vctx, char *fname, char *game_fname)
1592 {
1593     osfildef   *fp;
1594     vocidef  ***vpg;
1595     vocidef   **v;
1596     int         i;
1597     int         j;
1598     objnum      obj;
1599     uchar      *p;
1600     uchar      *mut;
1601     uint        mutsiz;
1602     int         propcnt;
1603     mcmcxdef   *mctx = vctx->voccxmem;
1604     uchar       buf[7];
1605     int         err = FALSE;
1606     struct fiosav_cb_ctx  fnctx;
1607 
1608     /* open the output file */
1609     if ((fp = osfopwb(fname, OSFTSAVE)) == 0)
1610         return TRUE;
1611 
1612     /*
1613      *   If we have game file information, save the game file information
1614      *   with the saved game file.  This lets the player start the
1615      *   run-time and restore the game by specifying only the saved game
1616      *   file.
1617      */
1618     if (game_fname != 0)
1619     {
1620         size_t len;
1621 
1622         /* write the prefix header */
1623         len = strlen(game_fname);
1624         oswp2(buf, len);
1625         if (osfwb(fp, FIOSAVHDR_PREFIX, (int)sizeof(FIOSAVHDR_PREFIX))
1626             || osfwb(fp, buf, 2)
1627             || osfwb(fp, game_fname, (int)len))
1628             goto ret_error;
1629     }
1630 
1631     /* write save game header and timestamp */
1632     if (osfwb(fp, FIOSAVHDR, (int)sizeof(FIOSAVHDR))
1633         || osfwb(fp, FIOSAVVSN, (int)sizeof(FIOSAVVSN))
1634         || osfwb(fp, vctx->voccxtim, 26))
1635         goto ret_error;
1636 
1637     /* go through each object, and write if it's been changed */
1638     for (vpg = vctx->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i)
1639     {
1640         if (!*vpg) continue;
1641         for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j)
1642         {
1643             if (*v != 0)
1644             {
1645                 /* write object if it's dirty */
1646                 if (mcmobjdirty(mctx, (mcmon)obj))
1647                 {
1648                     p = mcmlck(mctx, (mcmon)obj);
1649                     mut = p + objrst(p);
1650                     propcnt = objnprop(p);
1651                     mutsiz = objfree(p) - objrst(p);
1652                     if ((objflg(p) & OBJFINDEX) != 0)
1653                         mutsiz += propcnt * 4;
1654 
1655                     /*
1656                      *   If the object was dynamically allocated, write
1657                      *   the whole object.  Otherwise, write just the
1658                      *   mutable part.
1659                      */
1660                     if ((*v)->vociflg & VOCIFNEW)
1661                     {
1662                         /* indicate that the object is dynamic */
1663                         buf[0] = 1;
1664                         oswp2(buf + 1, obj);
1665 
1666                         /* write the entire object */
1667                         mutsiz = objfree(p);
1668                         oswp2(buf + 3, mutsiz);
1669                         if (osfwb(fp, buf, 7)
1670                             || osfwb(fp, p, mutsiz))
1671                             err = TRUE;
1672 
1673 #ifdef NEVER
1674                         {
1675                             int         wrdcnt;
1676 
1677                             /* count the words, and write the count */
1678                             voc_count(vctx, obj, 0, &wrdcnt, (int *)0);
1679                             oswp2(buf, wrdcnt);
1680                             if (osfwb(fp, buf, 2))
1681                                 err = TRUE;
1682 
1683                             /* write the words */
1684                             fnctx.err = 0;
1685                             fnctx.fp = fp;
1686                             voc_iterate(vctx, obj, fiosav_cb, &fnctx);
1687                             if (fnctx.err != 0)
1688                                 err = TRUE;
1689                         }
1690 #endif
1691                     }
1692                     else if (mutsiz)
1693                     {
1694                         /* write number of properties, size of mut, and mut */
1695                         buf[0] = 0;   /* indicate that the object is static */
1696                         oswp2(buf + 1, obj);
1697                         oswp2(buf + 3, propcnt);
1698                         oswp2(buf + 5, mutsiz);
1699                         if (osfwb(fp, buf, 7)
1700                             || osfwb(fp, mut, mutsiz))
1701                             err = TRUE;
1702                     }
1703 
1704                     mcmunlck(mctx, (mcmon)obj);
1705                     if (err != 0)
1706                         goto ret_error;
1707                 }
1708             }
1709         }
1710     }
1711 
1712     /* write end-of-objects indication */
1713     buf[0] = 0;
1714     oswp2(buf + 1, MCMONINV);
1715     oswp4(buf + 3, 0);
1716     if (osfwb(fp, buf, 7))
1717         goto ret_error;
1718 
1719     /* write fuses/daemons/alarms */
1720     if (fiowfda(fp, vctx->voccxdmn, vctx->voccxdmc)
1721         || fiowfda(fp, vctx->voccxfus, vctx->voccxfuc)
1722         || fiowfda(fp, vctx->voccxalm, vctx->voccxalc))
1723         goto ret_error;
1724 
1725     /* write run-time vocabulary additions and deletions */
1726     fnctx.fp = fp;
1727     fnctx.err = 0;
1728     voc_iterate(vctx, MCMONINV, fiosav_voc_cb, &fnctx);
1729     if (fnctx.err)
1730         goto ret_error;
1731 
1732     /* write end marker for vocabulary additions and deletions */
1733     oswp2(buf+6, MCMONINV);
1734     if (osfwb(fp, buf, 8))
1735         goto ret_error;
1736 
1737     /* write the current "Me" object */
1738     oswp2(buf, vctx->voccxme);
1739     if (osfwb(fp, buf, 2))
1740         goto ret_error;
1741 
1742     /* done - close file and return success indication */
1743     osfcls(fp);
1744     os_settype(fname, OSFTSAVE);
1745     return FALSE;
1746 
1747     /* come here on failure - close file and return error indication */
1748 ret_error:
1749     osfcls(fp);
1750     return TRUE;
1751 }
1752