OSDN Git Service

add translation
[jnethack/source.git] / src / files.c
1 /* NetHack 3.6  files.c $NHDT-Date: 1449296293 2015/12/05 06:18:13 $  $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.192 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed.  See license for details. */
4
5 /* JNetHack Copyright */
6 /* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000  */
7 /* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2016            */
8 /* JNetHack may be freely redistributed.  See license for details. */
9
10 #include "hack.h"
11 #include "dlb.h"
12
13 #ifdef TTY_GRAPHICS
14 #include "wintty.h" /* more() */
15 #endif
16
17 #if (!defined(MAC) && !defined(O_WRONLY) && !defined(AZTEC_C)) \
18     || defined(USE_FCNTL)
19 #include <fcntl.h>
20 #endif
21
22 #include <errno.h>
23 #ifdef _MSC_VER /* MSC 6.0 defines errno quite differently */
24 #if (_MSC_VER >= 600)
25 #define SKIP_ERRNO
26 #endif
27 #else
28 #ifdef NHSTDC
29 #define SKIP_ERRNO
30 #endif
31 #endif
32 #ifndef SKIP_ERRNO
33 #ifdef _DCC
34 const
35 #endif
36     extern int errno;
37 #endif
38
39 #ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
40 #include "zlib.h"
41 #ifndef COMPRESS_EXTENSION
42 #define COMPRESS_EXTENSION ".gz"
43 #endif
44 #endif
45
46 #if defined(UNIX) && defined(QT_GRAPHICS)
47 #include <sys/types.h>
48 #include <dirent.h>
49 #include <stdlib.h>
50 #endif
51
52 #if defined(UNIX) || defined(VMS) || !defined(NO_SIGNAL)
53 #include <signal.h>
54 #endif
55
56 #if defined(MSDOS) || defined(OS2) || defined(TOS) || defined(WIN32)
57 #ifndef GNUDOS
58 #include <sys\stat.h>
59 #else
60 #include <sys/stat.h>
61 #endif
62 #endif
63 #ifndef O_BINARY /* used for micros, no-op for others */
64 #define O_BINARY 0
65 #endif
66
67 #ifdef PREFIXES_IN_USE
68 #define FQN_NUMBUF 4
69 static char fqn_filename_buffer[FQN_NUMBUF][FQN_MAX_FILENAME];
70 #endif
71
72 #if !defined(MFLOPPY) && !defined(VMS) && !defined(WIN32)
73 char bones[] = "bonesnn.xxx";
74 char lock[PL_NSIZ + 14] = "1lock"; /* long enough for uid+name+.99 */
75 #else
76 #if defined(MFLOPPY)
77 char bones[FILENAME]; /* pathname of bones files */
78 char lock[FILENAME];  /* pathname of level files */
79 #endif
80 #if defined(VMS)
81 char bones[] = "bonesnn.xxx;1";
82 char lock[PL_NSIZ + 17] = "1lock"; /* long enough for _uid+name+.99;1 */
83 #endif
84 #if defined(WIN32)
85 char bones[] = "bonesnn.xxx";
86 char lock[PL_NSIZ + 25]; /* long enough for username+-+name+.99 */
87 #endif
88 #endif
89
90 #if defined(UNIX) || defined(__BEOS__)
91 #define SAVESIZE (PL_NSIZ + 13) /* save/99999player.e */
92 #else
93 #ifdef VMS
94 #define SAVESIZE (PL_NSIZ + 22) /* [.save]<uid>player.e;1 */
95 #else
96 #if defined(WIN32)
97 #define SAVESIZE (PL_NSIZ + 40) /* username-player.NetHack-saved-game */
98 #else
99 #define SAVESIZE FILENAME /* from macconf.h or pcconf.h */
100 #endif
101 #endif
102 #endif
103
104 #if !defined(SAVE_EXTENSION)
105 #ifdef MICRO
106 #define SAVE_EXTENSION ".sav"
107 #endif
108 #ifdef WIN32
109 #define SAVE_EXTENSION ".NetHack-saved-game"
110 #endif
111 #endif
112
113 char SAVEF[SAVESIZE]; /* holds relative path of save file from playground */
114 #ifdef MICRO
115 char SAVEP[SAVESIZE]; /* holds path of directory for save file */
116 #endif
117
118 #ifdef HOLD_LOCKFILE_OPEN
119 struct level_ftrack {
120     int init;
121     int fd;    /* file descriptor for level file     */
122     int oflag; /* open flags                         */
123     boolean nethack_thinks_it_is_open; /* Does NetHack think it's open? */
124 } lftrack;
125 #if defined(WIN32)
126 #include <share.h>
127 #endif
128 #endif /*HOLD_LOCKFILE_OPEN*/
129
130 #define WIZKIT_MAX 128
131 static char wizkit[WIZKIT_MAX];
132 STATIC_DCL FILE *NDECL(fopen_wizkit_file);
133 STATIC_DCL void FDECL(wizkit_addinv, (struct obj *));
134
135 #ifdef AMIGA
136 extern char PATH[]; /* see sys/amiga/amidos.c */
137 extern char bbs_id[];
138 static int lockptr;
139 #ifdef __SASC_60
140 #include <proto/dos.h>
141 #endif
142
143 #include <libraries/dos.h>
144 extern void FDECL(amii_set_text_font, (char *, int));
145 #endif
146
147 #if defined(WIN32) || defined(MSDOS)
148 static int lockptr;
149 #ifdef MSDOS
150 #define Delay(a) msleep(a)
151 #endif
152 #define Close close
153 #ifndef WIN_CE
154 #define DeleteFile unlink
155 #endif
156 #endif
157
158 #ifdef MAC
159 #undef unlink
160 #define unlink macunlink
161 #endif
162
163 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) \
164     || defined(__MWERKS__)
165 #define PRAGMA_UNUSED
166 #endif
167
168 #ifdef USER_SOUNDS
169 extern char *sounddir;
170 #endif
171
172 extern int n_dgns; /* from dungeon.c */
173
174 #if defined(UNIX) && defined(QT_GRAPHICS)
175 #define SELECTSAVED
176 #endif
177
178 #ifdef SELECTSAVED
179 STATIC_PTR int FDECL(CFDECLSPEC strcmp_wrap, (const void *, const void *));
180 #endif
181 STATIC_DCL char *FDECL(set_bonesfile_name, (char *, d_level *));
182 STATIC_DCL char *NDECL(set_bonestemp_name);
183 #ifdef COMPRESS
184 STATIC_DCL void FDECL(redirect, (const char *, const char *, FILE *,
185                                  BOOLEAN_P));
186 #endif
187 #if defined(COMPRESS) || defined(ZLIB_COMP)
188 STATIC_DCL void FDECL(docompress_file, (const char *, BOOLEAN_P));
189 #endif
190 #if defined(ZLIB_COMP)
191 STATIC_DCL boolean FDECL(make_compressed_name, (const char *, char *));
192 #endif
193 #ifndef USE_FCNTL
194 STATIC_DCL char *FDECL(make_lockname, (const char *, char *));
195 #endif
196 STATIC_DCL FILE *FDECL(fopen_config_file, (const char *, int));
197 STATIC_DCL int FDECL(get_uchars, (FILE *, char *, char *, uchar *, BOOLEAN_P,
198                                   int, const char *));
199 int FDECL(parse_config_line, (FILE *, char *, int));
200 STATIC_DCL FILE *NDECL(fopen_sym_file);
201 STATIC_DCL void FDECL(set_symhandling, (char *, int));
202 #ifdef NOCWD_ASSUMPTIONS
203 STATIC_DCL void FDECL(adjust_prefix, (char *, int));
204 #endif
205 #ifdef SELF_RECOVER
206 STATIC_DCL boolean FDECL(copy_bytes, (int, int));
207 #endif
208 #ifdef HOLD_LOCKFILE_OPEN
209 STATIC_DCL int FDECL(open_levelfile_exclusively, (const char *, int, int));
210 #endif
211
212 /*
213  * fname_encode()
214  *
215  *   Args:
216  *      legal       zero-terminated list of acceptable file name characters
217  *      quotechar   lead-in character used to quote illegal characters as
218  *                  hex digits
219  *      s           string to encode
220  *      callerbuf   buffer to house result
221  *      bufsz       size of callerbuf
222  *
223  *   Notes:
224  *      The hex digits 0-9 and A-F are always part of the legal set due to
225  *      their use in the encoding scheme, even if not explicitly included in
226  *      'legal'.
227  *
228  *   Sample:
229  *      The following call:
230  *  (void)fname_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
231  *                     '%', "This is a % test!", buf, 512);
232  *      results in this encoding:
233  *          "This%20is%20a%20%25%20test%21"
234  */
235 char *
236 fname_encode(legal, quotechar, s, callerbuf, bufsz)
237 const char *legal;
238 char quotechar;
239 char *s, *callerbuf;
240 int bufsz;
241 {
242     char *sp, *op;
243     int cnt = 0;
244     static char hexdigits[] = "0123456789ABCDEF";
245
246     sp = s;
247     op = callerbuf;
248     *op = '\0';
249
250     while (*sp) {
251         /* Do we have room for one more character or encoding? */
252         if ((bufsz - cnt) <= 4)
253             return callerbuf;
254
255         if (*sp == quotechar) {
256             (void) sprintf(op, "%c%02X", quotechar, *sp);
257             op += 3;
258             cnt += 3;
259         } else if ((index(legal, *sp) != 0) || (index(hexdigits, *sp) != 0)) {
260             *op++ = *sp;
261             *op = '\0';
262             cnt++;
263 #if 1 /*JP*/
264         } else if (is_kanji1(s, sp - s)) {
265             *op++ = *sp++;
266             *op++ = *sp;
267             *op = '\0';
268             cnt += 2;
269 #endif
270         } else {
271             (void) sprintf(op, "%c%02X", quotechar, *sp);
272             op += 3;
273             cnt += 3;
274         }
275         sp++;
276     }
277     return callerbuf;
278 }
279
280 /*
281  * fname_decode()
282  *
283  *   Args:
284  *      quotechar   lead-in character used to quote illegal characters as
285  *                  hex digits
286  *      s           string to decode
287  *      callerbuf   buffer to house result
288  *      bufsz       size of callerbuf
289  */
290 char *
291 fname_decode(quotechar, s, callerbuf, bufsz)
292 char quotechar;
293 char *s, *callerbuf;
294 int bufsz;
295 {
296     char *sp, *op;
297     int k, calc, cnt = 0;
298     static char hexdigits[] = "0123456789ABCDEF";
299
300     sp = s;
301     op = callerbuf;
302     *op = '\0';
303     calc = 0;
304
305     while (*sp) {
306         /* Do we have room for one more character? */
307         if ((bufsz - cnt) <= 2)
308             return callerbuf;
309         if (*sp == quotechar) {
310             sp++;
311             for (k = 0; k < 16; ++k)
312                 if (*sp == hexdigits[k])
313                     break;
314             if (k >= 16)
315                 return callerbuf; /* impossible, so bail */
316             calc = k << 4;
317             sp++;
318             for (k = 0; k < 16; ++k)
319                 if (*sp == hexdigits[k])
320                     break;
321             if (k >= 16)
322                 return callerbuf; /* impossible, so bail */
323             calc += k;
324             sp++;
325             *op++ = calc;
326             *op = '\0';
327         } else {
328             *op++ = *sp++;
329             *op = '\0';
330         }
331         cnt++;
332     }
333     return callerbuf;
334 }
335
336 #ifdef PREFIXES_IN_USE
337 #define UNUSED_if_not_PREFIXES_IN_USE /*empty*/
338 #else
339 #define UNUSED_if_not_PREFIXES_IN_USE UNUSED
340 #endif
341
342 /*ARGSUSED*/
343 const char *
344 fqname(basenam, whichprefix, buffnum)
345 const char *basenam;
346 int whichprefix UNUSED_if_not_PREFIXES_IN_USE;
347 int buffnum UNUSED_if_not_PREFIXES_IN_USE;
348 {
349 #ifndef PREFIXES_IN_USE
350     return basenam;
351 #else
352     if (!basenam || whichprefix < 0 || whichprefix >= PREFIX_COUNT)
353         return basenam;
354     if (!fqn_prefix[whichprefix])
355         return basenam;
356     if (buffnum < 0 || buffnum >= FQN_NUMBUF) {
357         impossible("Invalid fqn_filename_buffer specified: %d", buffnum);
358         buffnum = 0;
359     }
360     if (strlen(fqn_prefix[whichprefix]) + strlen(basenam)
361         >= FQN_MAX_FILENAME) {
362         impossible("fqname too long: %s + %s", fqn_prefix[whichprefix],
363                    basenam);
364         return basenam; /* XXX */
365     }
366     Strcpy(fqn_filename_buffer[buffnum], fqn_prefix[whichprefix]);
367     return strcat(fqn_filename_buffer[buffnum], basenam);
368 #endif
369 }
370
371 int
372 validate_prefix_locations(reasonbuf)
373 char *reasonbuf; /* reasonbuf must be at least BUFSZ, supplied by caller */
374 {
375 #if defined(NOCWD_ASSUMPTIONS)
376     FILE *fp;
377     const char *filename;
378     int prefcnt, failcount = 0;
379     char panicbuf1[BUFSZ], panicbuf2[BUFSZ];
380     const char *details;
381 #endif
382
383     if (reasonbuf)
384         reasonbuf[0] = '\0';
385 #if defined(NOCWD_ASSUMPTIONS)
386     for (prefcnt = 1; prefcnt < PREFIX_COUNT; prefcnt++) {
387         /* don't test writing to configdir or datadir; they're readonly */
388         if (prefcnt == SYSCONFPREFIX || prefcnt == CONFIGPREFIX
389             || prefcnt == DATAPREFIX)
390             continue;
391         filename = fqname("validate", prefcnt, 3);
392         if ((fp = fopen(filename, "w"))) {
393             fclose(fp);
394             (void) unlink(filename);
395         } else {
396             if (reasonbuf) {
397                 if (failcount)
398                     Strcat(reasonbuf, ", ");
399                 Strcat(reasonbuf, fqn_prefix_names[prefcnt]);
400             }
401             /* the paniclog entry gets the value of errno as well */
402             Sprintf(panicbuf1, "Invalid %s", fqn_prefix_names[prefcnt]);
403 #if defined(NHSTDC) && !defined(NOTSTDC)
404             if (!(details = strerror(errno)))
405 #endif
406                 details = "";
407             Sprintf(panicbuf2, "\"%s\", (%d) %s", fqn_prefix[prefcnt], errno,
408                     details);
409             paniclog(panicbuf1, panicbuf2);
410             failcount++;
411         }
412     }
413     if (failcount)
414         return 0;
415     else
416 #endif
417         return 1;
418 }
419
420 /* fopen a file, with OS-dependent bells and whistles */
421 /* NOTE: a simpler version of this routine also exists in util/dlb_main.c */
422 FILE *
423 fopen_datafile(filename, mode, prefix)
424 const char *filename, *mode;
425 int prefix;
426 {
427     FILE *fp;
428
429     filename = fqname(filename, prefix, prefix == TROUBLEPREFIX ? 3 : 0);
430     fp = fopen(filename, mode);
431     return fp;
432 }
433
434 /* ----------  BEGIN LEVEL FILE HANDLING ----------- */
435
436 #ifdef MFLOPPY
437 /* Set names for bones[] and lock[] */
438 void
439 set_lock_and_bones()
440 {
441     if (!ramdisk) {
442         Strcpy(levels, permbones);
443         Strcpy(bones, permbones);
444     }
445     append_slash(permbones);
446     append_slash(levels);
447 #ifdef AMIGA
448     strncat(levels, bbs_id, PATHLEN);
449 #endif
450     append_slash(bones);
451     Strcat(bones, "bonesnn.*");
452     Strcpy(lock, levels);
453 #ifndef AMIGA
454     Strcat(lock, alllevels);
455 #endif
456     return;
457 }
458 #endif /* MFLOPPY */
459
460 /* Construct a file name for a level-type file, which is of the form
461  * something.level (with any old level stripped off).
462  * This assumes there is space on the end of 'file' to append
463  * a two digit number.  This is true for 'level'
464  * but be careful if you use it for other things -dgk
465  */
466 void
467 set_levelfile_name(file, lev)
468 char *file;
469 int lev;
470 {
471     char *tf;
472
473     tf = rindex(file, '.');
474     if (!tf)
475         tf = eos(file);
476     Sprintf(tf, ".%d", lev);
477 #ifdef VMS
478     Strcat(tf, ";1");
479 #endif
480     return;
481 }
482
483 int
484 create_levelfile(lev, errbuf)
485 int lev;
486 char errbuf[];
487 {
488     int fd;
489     const char *fq_lock;
490
491     if (errbuf)
492         *errbuf = '\0';
493     set_levelfile_name(lock, lev);
494     fq_lock = fqname(lock, LEVELPREFIX, 0);
495
496 #if defined(MICRO) || defined(WIN32)
497 /* Use O_TRUNC to force the file to be shortened if it already
498  * exists and is currently longer.
499  */
500 #ifdef HOLD_LOCKFILE_OPEN
501     if (lev == 0)
502         fd = open_levelfile_exclusively(
503             fq_lock, lev, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
504     else
505 #endif
506         fd = open(fq_lock, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
507 #else
508 #ifdef MAC
509     fd = maccreat(fq_lock, LEVL_TYPE);
510 #else
511     fd = creat(fq_lock, FCMASK);
512 #endif
513 #endif /* MICRO || WIN32 */
514
515     if (fd >= 0)
516         level_info[lev].flags |= LFILE_EXISTS;
517     else if (errbuf) /* failure explanation */
518         Sprintf(errbuf, "Cannot create file \"%s\" for level %d (errno %d).",
519                 lock, lev, errno);
520
521     return fd;
522 }
523
524 int
525 open_levelfile(lev, errbuf)
526 int lev;
527 char errbuf[];
528 {
529     int fd;
530     const char *fq_lock;
531
532     if (errbuf)
533         *errbuf = '\0';
534     set_levelfile_name(lock, lev);
535     fq_lock = fqname(lock, LEVELPREFIX, 0);
536 #ifdef MFLOPPY
537     /* If not currently accessible, swap it in. */
538     if (level_info[lev].where != ACTIVE)
539         swapin_file(lev);
540 #endif
541 #ifdef MAC
542     fd = macopen(fq_lock, O_RDONLY | O_BINARY, LEVL_TYPE);
543 #else
544 #ifdef HOLD_LOCKFILE_OPEN
545     if (lev == 0)
546         fd = open_levelfile_exclusively(fq_lock, lev, O_RDONLY | O_BINARY);
547     else
548 #endif
549         fd = open(fq_lock, O_RDONLY | O_BINARY, 0);
550 #endif
551
552     /* for failure, return an explanation that our caller can use;
553        settle for `lock' instead of `fq_lock' because the latter
554        might end up being too big for nethack's BUFSZ */
555     if (fd < 0 && errbuf)
556 #if 0 /*JP*/
557         Sprintf(errbuf, "Cannot open file \"%s\" for level %d (errno %d).",
558                 lock, lev, errno);
559 #else
560         Sprintf(errbuf, "\92n\89º%d\8aK\82Ì\83t\83@\83C\83\8b\"%s\"\82ð\8aJ\82¯\82È\82¢(errno %d)\81D",
561                 lev, lock, errno);
562 #endif
563
564     return fd;
565 }
566
567 void
568 delete_levelfile(lev)
569 int lev;
570 {
571     /*
572      * Level 0 might be created by port specific code that doesn't
573      * call create_levfile(), so always assume that it exists.
574      */
575     if (lev == 0 || (level_info[lev].flags & LFILE_EXISTS)) {
576         set_levelfile_name(lock, lev);
577 #ifdef HOLD_LOCKFILE_OPEN
578         if (lev == 0)
579             really_close();
580 #endif
581         (void) unlink(fqname(lock, LEVELPREFIX, 0));
582         level_info[lev].flags &= ~LFILE_EXISTS;
583     }
584 }
585
586 void
587 clearlocks()
588 {
589 #ifdef HANGUPHANDLING
590     if (program_state.preserve_locks)
591         return;
592 #endif
593 #if !defined(PC_LOCKING) && defined(MFLOPPY) && !defined(AMIGA)
594     eraseall(levels, alllevels);
595     if (ramdisk)
596         eraseall(permbones, alllevels);
597 #else
598     {
599         register int x;
600
601 #ifndef NO_SIGNAL
602         (void) signal(SIGINT, SIG_IGN);
603 #endif
604 #if defined(UNIX) || defined(VMS)
605         sethanguphandler((void FDECL((*), (int) )) SIG_IGN);
606 #endif
607         /* can't access maxledgerno() before dungeons are created -dlc */
608         for (x = (n_dgns ? maxledgerno() : 0); x >= 0; x--)
609             delete_levelfile(x); /* not all levels need be present */
610     }
611 #endif /* ?PC_LOCKING,&c */
612 }
613
614 #if defined(SELECTSAVED)
615 /* qsort comparison routine */
616 STATIC_OVL int CFDECLSPEC
617 strcmp_wrap(p, q)
618 const void *p;
619 const void *q;
620 {
621 #if defined(UNIX) && defined(QT_GRAPHICS)
622     return strncasecmp(*(char **) p, *(char **) q, 16);
623 #else
624     return strncmpi(*(char **) p, *(char **) q, 16);
625 #endif
626 }
627 #endif
628
629 #ifdef HOLD_LOCKFILE_OPEN
630 STATIC_OVL int
631 open_levelfile_exclusively(name, lev, oflag)
632 const char *name;
633 int lev, oflag;
634 {
635     int reslt, fd;
636     if (!lftrack.init) {
637         lftrack.init = 1;
638         lftrack.fd = -1;
639     }
640     if (lftrack.fd >= 0) {
641         /* check for compatible access */
642         if (lftrack.oflag == oflag) {
643             fd = lftrack.fd;
644             reslt = lseek(fd, 0L, SEEK_SET);
645             if (reslt == -1L)
646                 panic("open_levelfile_exclusively: lseek failed %d", errno);
647             lftrack.nethack_thinks_it_is_open = TRUE;
648         } else {
649             really_close();
650             fd = sopen(name, oflag, SH_DENYRW, FCMASK);
651             lftrack.fd = fd;
652             lftrack.oflag = oflag;
653             lftrack.nethack_thinks_it_is_open = TRUE;
654         }
655     } else {
656         fd = sopen(name, oflag, SH_DENYRW, FCMASK);
657         lftrack.fd = fd;
658         lftrack.oflag = oflag;
659         if (fd >= 0)
660             lftrack.nethack_thinks_it_is_open = TRUE;
661     }
662     return fd;
663 }
664
665 void
666 really_close()
667 {
668     int fd = lftrack.fd;
669
670     lftrack.nethack_thinks_it_is_open = FALSE;
671     lftrack.fd = -1;
672     lftrack.oflag = 0;
673     if (fd != -1)
674         (void) close(fd);
675     return;
676 }
677
678 int
679 nhclose(fd)
680 int fd;
681 {
682     if (lftrack.fd == fd) {
683         really_close(); /* close it, but reopen it to hold it */
684         fd = open_levelfile(0, (char *) 0);
685         lftrack.nethack_thinks_it_is_open = FALSE;
686         return 0;
687     }
688     return close(fd);
689 }
690 #else
691
692 int
693 nhclose(fd)
694 int fd;
695 {
696     return close(fd);
697 }
698 #endif
699
700 /* ----------  END LEVEL FILE HANDLING ----------- */
701
702 /* ----------  BEGIN BONES FILE HANDLING ----------- */
703
704 /* set up "file" to be file name for retrieving bones, and return a
705  * bonesid to be read/written in the bones file.
706  */
707 STATIC_OVL char *
708 set_bonesfile_name(file, lev)
709 char *file;
710 d_level *lev;
711 {
712     s_level *sptr;
713     char *dptr;
714
715     Sprintf(file, "bon%c%s", dungeons[lev->dnum].boneid,
716             In_quest(lev) ? urole.filecode : "0");
717     dptr = eos(file);
718     if ((sptr = Is_special(lev)) != 0)
719         Sprintf(dptr, ".%c", sptr->boneid);
720     else
721         Sprintf(dptr, ".%d", lev->dlevel);
722 #ifdef VMS
723     Strcat(dptr, ";1");
724 #endif
725     return (dptr - 2);
726 }
727
728 /* set up temporary file name for writing bones, to avoid another game's
729  * trying to read from an uncompleted bones file.  we want an uncontentious
730  * name, so use one in the namespace reserved for this game's level files.
731  * (we are not reading or writing level files while writing bones files, so
732  * the same array may be used instead of copying.)
733  */
734 STATIC_OVL char *
735 set_bonestemp_name()
736 {
737     char *tf;
738
739     tf = rindex(lock, '.');
740     if (!tf)
741         tf = eos(lock);
742     Sprintf(tf, ".bn");
743 #ifdef VMS
744     Strcat(tf, ";1");
745 #endif
746     return lock;
747 }
748
749 int
750 create_bonesfile(lev, bonesid, errbuf)
751 d_level *lev;
752 char **bonesid;
753 char errbuf[];
754 {
755     const char *file;
756     int fd;
757
758     if (errbuf)
759         *errbuf = '\0';
760     *bonesid = set_bonesfile_name(bones, lev);
761     file = set_bonestemp_name();
762     file = fqname(file, BONESPREFIX, 0);
763
764 #if defined(MICRO) || defined(WIN32)
765     /* Use O_TRUNC to force the file to be shortened if it already
766      * exists and is currently longer.
767      */
768     fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
769 #else
770 #ifdef MAC
771     fd = maccreat(file, BONE_TYPE);
772 #else
773     fd = creat(file, FCMASK);
774 #endif
775 #endif
776     if (fd < 0 && errbuf) /* failure explanation */
777         Sprintf(errbuf, "Cannot create bones \"%s\", id %s (errno %d).", lock,
778                 *bonesid, errno);
779
780 #if defined(VMS) && !defined(SECURE)
781     /*
782        Re-protect bones file with world:read+write+execute+delete access.
783        umask() doesn't seem very reliable; also, vaxcrtl won't let us set
784        delete access without write access, which is what's really wanted.
785        Can't simply create it with the desired protection because creat
786        ANDs the mask with the user's default protection, which usually
787        denies some or all access to world.
788      */
789     (void) chmod(file, FCMASK | 007); /* allow other users full access */
790 #endif /* VMS && !SECURE */
791
792     return fd;
793 }
794
795 #ifdef MFLOPPY
796 /* remove partial bonesfile in process of creation */
797 void
798 cancel_bonesfile()
799 {
800     const char *tempname;
801
802     tempname = set_bonestemp_name();
803     tempname = fqname(tempname, BONESPREFIX, 0);
804     (void) unlink(tempname);
805 }
806 #endif /* MFLOPPY */
807
808 /* move completed bones file to proper name */
809 void
810 commit_bonesfile(lev)
811 d_level *lev;
812 {
813     const char *fq_bones, *tempname;
814     int ret;
815
816     (void) set_bonesfile_name(bones, lev);
817     fq_bones = fqname(bones, BONESPREFIX, 0);
818     tempname = set_bonestemp_name();
819     tempname = fqname(tempname, BONESPREFIX, 1);
820
821 #if (defined(SYSV) && !defined(SVR4)) || defined(GENIX)
822     /* old SYSVs don't have rename.  Some SVR3's may, but since they
823      * also have link/unlink, it doesn't matter. :-)
824      */
825     (void) unlink(fq_bones);
826     ret = link(tempname, fq_bones);
827     ret += unlink(tempname);
828 #else
829     ret = rename(tempname, fq_bones);
830 #endif
831     if (wizard && ret != 0)
832         pline("couldn't rename %s to %s.", tempname, fq_bones);
833 }
834
835 int
836 open_bonesfile(lev, bonesid)
837 d_level *lev;
838 char **bonesid;
839 {
840     const char *fq_bones;
841     int fd;
842
843     *bonesid = set_bonesfile_name(bones, lev);
844     fq_bones = fqname(bones, BONESPREFIX, 0);
845     nh_uncompress(fq_bones); /* no effect if nonexistent */
846 #ifdef MAC
847     fd = macopen(fq_bones, O_RDONLY | O_BINARY, BONE_TYPE);
848 #else
849     fd = open(fq_bones, O_RDONLY | O_BINARY, 0);
850 #endif
851     return fd;
852 }
853
854 int
855 delete_bonesfile(lev)
856 d_level *lev;
857 {
858     (void) set_bonesfile_name(bones, lev);
859     return !(unlink(fqname(bones, BONESPREFIX, 0)) < 0);
860 }
861
862 /* assume we're compressing the recently read or created bonesfile, so the
863  * file name is already set properly */
864 void
865 compress_bonesfile()
866 {
867     nh_compress(fqname(bones, BONESPREFIX, 0));
868 }
869
870 /* ----------  END BONES FILE HANDLING ----------- */
871
872 /* ----------  BEGIN SAVE FILE HANDLING ----------- */
873
874 /* set savefile name in OS-dependent manner from pre-existing plname,
875  * avoiding troublesome characters */
876 void
877 set_savefile_name(regularize_it)
878 boolean regularize_it;
879 {
880 #ifdef VMS
881     Sprintf(SAVEF, "[.save]%d%s", getuid(), plname);
882     if (regularize_it)
883         regularize(SAVEF + 7);
884     Strcat(SAVEF, ";1");
885 #else
886 #if defined(MICRO)
887     Strcpy(SAVEF, SAVEP);
888 #ifdef AMIGA
889     strncat(SAVEF, bbs_id, PATHLEN);
890 #endif
891     {
892         int i = strlen(SAVEP);
893 #ifdef AMIGA
894         /* plname has to share space with SAVEP and ".sav" */
895         (void) strncat(SAVEF, plname, FILENAME - i - 4);
896 #else
897         (void) strncat(SAVEF, plname, 8);
898 #endif
899         if (regularize_it)
900             regularize(SAVEF + i);
901     }
902     Strcat(SAVEF, SAVE_EXTENSION);
903 #else
904 #if defined(WIN32)
905     {
906         static const char okchars[] =
907             "*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
908         const char *legal = okchars;
909         char fnamebuf[BUFSZ], encodedfnamebuf[BUFSZ];
910
911         /* Obtain the name of the logged on user and incorporate
912          * it into the name. */
913         Sprintf(fnamebuf, "%s-%s", get_username(0), plname);
914         if (regularize_it)
915             ++legal; /* skip '*' wildcard character */
916         (void) fname_encode(legal, '%', fnamebuf, encodedfnamebuf, BUFSZ);
917         Sprintf(SAVEF, "%s%s", encodedfnamebuf, SAVE_EXTENSION);
918     }
919 #else  /* not VMS or MICRO or WIN32 */
920     Sprintf(SAVEF, "save/%d%s", (int) getuid(), plname);
921     if (regularize_it)
922         regularize(SAVEF + 5); /* avoid . or / in name */
923 #endif /* WIN32 */
924 #endif /* MICRO */
925 #endif /* VMS   */
926 }
927
928 #ifdef INSURANCE
929 void
930 save_savefile_name(fd)
931 int fd;
932 {
933     (void) write(fd, (genericptr_t) SAVEF, sizeof(SAVEF));
934 }
935 #endif
936
937 #ifndef MICRO
938 /* change pre-existing savefile name to indicate an error savefile */
939 void
940 set_error_savefile()
941 {
942 #ifdef VMS
943     {
944         char *semi_colon = rindex(SAVEF, ';');
945
946         if (semi_colon)
947             *semi_colon = '\0';
948     }
949     Strcat(SAVEF, ".e;1");
950 #else
951 #ifdef MAC
952     Strcat(SAVEF, "-e");
953 #else
954     Strcat(SAVEF, ".e");
955 #endif
956 #endif
957 }
958 #endif
959
960 /* create save file, overwriting one if it already exists */
961 int
962 create_savefile()
963 {
964     const char *fq_save;
965     int fd;
966
967     fq_save = fqname(SAVEF, SAVEPREFIX, 0);
968 #if defined(MICRO) || defined(WIN32)
969     fd = open(fq_save, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, FCMASK);
970 #else
971 #ifdef MAC
972     fd = maccreat(fq_save, SAVE_TYPE);
973 #else
974     fd = creat(fq_save, FCMASK);
975 #endif
976 #if defined(VMS) && !defined(SECURE)
977     /*
978        Make sure the save file is owned by the current process.  That's
979        the default for non-privileged users, but for priv'd users the
980        file will be owned by the directory's owner instead of the user.
981     */
982 #undef getuid
983     (void) chown(fq_save, getuid(), getgid());
984 #define getuid() vms_getuid()
985 #endif /* VMS && !SECURE */
986 #endif /* MICRO */
987
988     return fd;
989 }
990
991 /* open savefile for reading */
992 int
993 open_savefile()
994 {
995     const char *fq_save;
996     int fd;
997
998     fq_save = fqname(SAVEF, SAVEPREFIX, 0);
999 #ifdef MAC
1000     fd = macopen(fq_save, O_RDONLY | O_BINARY, SAVE_TYPE);
1001 #else
1002     fd = open(fq_save, O_RDONLY | O_BINARY, 0);
1003 #endif
1004     return fd;
1005 }
1006
1007 /* delete savefile */
1008 int
1009 delete_savefile()
1010 {
1011     (void) unlink(fqname(SAVEF, SAVEPREFIX, 0));
1012     return 0; /* for restore_saved_game() (ex-xxxmain.c) test */
1013 }
1014
1015 /* try to open up a save file and prepare to restore it */
1016 int
1017 restore_saved_game()
1018 {
1019     const char *fq_save;
1020     int fd;
1021
1022     reset_restpref();
1023     set_savefile_name(TRUE);
1024 #ifdef MFLOPPY
1025     if (!saveDiskPrompt(1))
1026         return -1;
1027 #endif /* MFLOPPY */
1028     fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1029
1030     nh_uncompress(fq_save);
1031     if ((fd = open_savefile()) < 0)
1032         return fd;
1033
1034     if (validate(fd, fq_save) != 0) {
1035         (void) nhclose(fd), fd = -1;
1036         (void) delete_savefile();
1037     }
1038     return fd;
1039 }
1040
1041 #if defined(SELECTSAVED)
1042 char *
1043 plname_from_file(filename)
1044 const char *filename;
1045 {
1046     int fd;
1047     char *result = 0;
1048
1049     Strcpy(SAVEF, filename);
1050 #ifdef COMPRESS_EXTENSION
1051     SAVEF[strlen(SAVEF) - strlen(COMPRESS_EXTENSION)] = '\0';
1052 #endif
1053     nh_uncompress(SAVEF);
1054     if ((fd = open_savefile()) >= 0) {
1055         if (validate(fd, filename) == 0) {
1056             char tplname[PL_NSIZ];
1057             get_plname_from_file(fd, tplname);
1058             result = dupstr(tplname);
1059         }
1060         (void) nhclose(fd);
1061     }
1062     nh_compress(SAVEF);
1063
1064     return result;
1065 #if 0
1066 /* --------- obsolete - used to be ifndef STORE_PLNAME_IN_FILE ----*/
1067 #if defined(UNIX) && defined(QT_GRAPHICS)
1068     /* Name not stored in save file, so we have to extract it from
1069        the filename, which loses information
1070        (eg. "/", "_", and "." characters are lost. */
1071     int k;
1072     int uid;
1073     char name[64]; /* more than PL_NSIZ */
1074 #ifdef COMPRESS_EXTENSION
1075 #define EXTSTR COMPRESS_EXTENSION
1076 #else
1077 #define EXTSTR ""
1078 #endif
1079
1080     if ( sscanf( filename, "%*[^/]/%d%63[^.]" EXTSTR, &uid, name ) == 2 ) {
1081 #undef EXTSTR
1082         /* "_" most likely means " ", which certainly looks nicer */
1083         for (k=0; name[k]; k++)
1084             if ( name[k] == '_' )
1085                 name[k] = ' ';
1086         return dupstr(name);
1087     } else
1088 #endif /* UNIX && QT_GRAPHICS */
1089     {
1090         return 0;
1091     }
1092 /* --------- end of obsolete code ----*/
1093 #endif /* 0 - WAS STORE_PLNAME_IN_FILE*/
1094 }
1095 #endif /* defined(SELECTSAVED) */
1096
1097 char **
1098 get_saved_games()
1099 {
1100 #if defined(SELECTSAVED)
1101     int n, j = 0;
1102     char **result = 0;
1103 #ifdef WIN32
1104     {
1105         char *foundfile;
1106         const char *fq_save;
1107
1108         Strcpy(plname, "*");
1109         set_savefile_name(FALSE);
1110 #if defined(ZLIB_COMP)
1111         Strcat(SAVEF, COMPRESS_EXTENSION);
1112 #endif
1113         fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1114
1115         n = 0;
1116         foundfile = foundfile_buffer();
1117         if (findfirst((char *) fq_save)) {
1118             do {
1119                 ++n;
1120             } while (findnext());
1121         }
1122         if (n > 0) {
1123             result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1124             (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
1125             if (findfirst((char *) fq_save)) {
1126                 j = n = 0;
1127                 do {
1128                     char *r;
1129                     r = plname_from_file(foundfile);
1130                     if (r)
1131                         result[j++] = r;
1132                     ++n;
1133                 } while (findnext());
1134             }
1135         }
1136     }
1137 #endif
1138 #if defined(UNIX) && defined(QT_GRAPHICS)
1139     /* posixly correct version */
1140     int myuid = getuid();
1141     DIR *dir;
1142
1143     if ((dir = opendir(fqname("save", SAVEPREFIX, 0)))) {
1144         for (n = 0; readdir(dir); n++)
1145             ;
1146         closedir(dir);
1147         if (n > 0) {
1148             int i;
1149
1150             if (!(dir = opendir(fqname("save", SAVEPREFIX, 0))))
1151                 return 0;
1152             result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1153             (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
1154             for (i = 0, j = 0; i < n; i++) {
1155                 int uid;
1156                 char name[64]; /* more than PL_NSIZ */
1157                 struct dirent *entry = readdir(dir);
1158
1159                 if (!entry)
1160                     break;
1161                 if (sscanf(entry->d_name, "%d%63s", &uid, name) == 2) {
1162                     if (uid == myuid) {
1163                         char filename[BUFSZ];
1164                         char *r;
1165
1166                         Sprintf(filename, "save/%d%s", uid, name);
1167                         r = plname_from_file(filename);
1168                         if (r)
1169                             result[j++] = r;
1170                     }
1171                 }
1172             }
1173             closedir(dir);
1174         }
1175     }
1176 #endif
1177 #ifdef VMS
1178     Strcpy(plname, "*");
1179     set_savefile_name(FALSE);
1180     j = vms_get_saved_games(SAVEF, &result);
1181 #endif /* VMS */
1182
1183     if (j > 0) {
1184         if (j > 1)
1185             qsort(result, j, sizeof (char *), strcmp_wrap);
1186         result[j] = 0;
1187         return result;
1188     } else if (result) { /* could happen if save files are obsolete */
1189         free_saved_games(result);
1190     }
1191 #endif /* SELECTSAVED */
1192     return 0;
1193 }
1194
1195 void
1196 free_saved_games(saved)
1197 char **saved;
1198 {
1199     if (saved) {
1200         int i = 0;
1201
1202         while (saved[i])
1203             free((genericptr_t) saved[i++]);
1204         free((genericptr_t) saved);
1205     }
1206 }
1207
1208 /* ----------  END SAVE FILE HANDLING ----------- */
1209
1210 /* ----------  BEGIN FILE COMPRESSION HANDLING ----------- */
1211
1212 #ifdef COMPRESS
1213
1214 STATIC_OVL void
1215 redirect(filename, mode, stream, uncomp)
1216 const char *filename, *mode;
1217 FILE *stream;
1218 boolean uncomp;
1219 {
1220     if (freopen(filename, mode, stream) == (FILE *) 0) {
1221         (void) fprintf(stderr, "freopen of %s for %scompress failed\n",
1222                        filename, uncomp ? "un" : "");
1223         terminate(EXIT_FAILURE);
1224     }
1225 }
1226
1227 /*
1228  * using system() is simpler, but opens up security holes and causes
1229  * problems on at least Interactive UNIX 3.0.1 (SVR3.2), where any
1230  * setuid is renounced by /bin/sh, so the files cannot be accessed.
1231  *
1232  * cf. child() in unixunix.c.
1233  */
1234 STATIC_OVL void
1235 docompress_file(filename, uncomp)
1236 const char *filename;
1237 boolean uncomp;
1238 {
1239     char cfn[80];
1240     FILE *cf;
1241     const char *args[10];
1242 #ifdef COMPRESS_OPTIONS
1243     char opts[80];
1244 #endif
1245     int i = 0;
1246     int f;
1247 #ifdef TTY_GRAPHICS
1248     boolean istty = !strncmpi(windowprocs.name, "tty", 3);
1249 #endif
1250
1251     Strcpy(cfn, filename);
1252 #ifdef COMPRESS_EXTENSION
1253     Strcat(cfn, COMPRESS_EXTENSION);
1254 #endif
1255     /* when compressing, we know the file exists */
1256     if (uncomp) {
1257         if ((cf = fopen(cfn, RDBMODE)) == (FILE *) 0)
1258             return;
1259         (void) fclose(cf);
1260     }
1261
1262     args[0] = COMPRESS;
1263     if (uncomp)
1264         args[++i] = "-d"; /* uncompress */
1265 #ifdef COMPRESS_OPTIONS
1266     {
1267         /* we can't guarantee there's only one additional option, sigh */
1268         char *opt;
1269         boolean inword = FALSE;
1270
1271         Strcpy(opts, COMPRESS_OPTIONS);
1272         opt = opts;
1273         while (*opt) {
1274             if ((*opt == ' ') || (*opt == '\t')) {
1275                 if (inword) {
1276                     *opt = '\0';
1277                     inword = FALSE;
1278                 }
1279             } else if (!inword) {
1280                 args[++i] = opt;
1281                 inword = TRUE;
1282             }
1283             opt++;
1284         }
1285     }
1286 #endif
1287     args[++i] = (char *) 0;
1288
1289 #ifdef TTY_GRAPHICS
1290     /* If we don't do this and we are right after a y/n question *and*
1291      * there is an error message from the compression, the 'y' or 'n' can
1292      * end up being displayed after the error message.
1293      */
1294     if (istty)
1295         mark_synch();
1296 #endif
1297     f = fork();
1298     if (f == 0) { /* child */
1299 #ifdef TTY_GRAPHICS
1300         /* any error messages from the compression must come out after
1301          * the first line, because the more() to let the user read
1302          * them will have to clear the first line.  This should be
1303          * invisible if there are no error messages.
1304          */
1305         if (istty)
1306             raw_print("");
1307 #endif
1308         /* run compressor without privileges, in case other programs
1309          * have surprises along the line of gzip once taking filenames
1310          * in GZIP.
1311          */
1312         /* assume all compressors will compress stdin to stdout
1313          * without explicit filenames.  this is true of at least
1314          * compress and gzip, those mentioned in config.h.
1315          */
1316         if (uncomp) {
1317             redirect(cfn, RDBMODE, stdin, uncomp);
1318             redirect(filename, WRBMODE, stdout, uncomp);
1319         } else {
1320             redirect(filename, RDBMODE, stdin, uncomp);
1321             redirect(cfn, WRBMODE, stdout, uncomp);
1322         }
1323         (void) setgid(getgid());
1324         (void) setuid(getuid());
1325         (void) execv(args[0], (char *const *) args);
1326         perror((char *) 0);
1327         (void) fprintf(stderr, "Exec to %scompress %s failed.\n",
1328                        uncomp ? "un" : "", filename);
1329         terminate(EXIT_FAILURE);
1330     } else if (f == -1) {
1331         perror((char *) 0);
1332         pline("Fork to %scompress %s failed.", uncomp ? "un" : "", filename);
1333         return;
1334     }
1335 #ifndef NO_SIGNAL
1336     (void) signal(SIGINT, SIG_IGN);
1337     (void) signal(SIGQUIT, SIG_IGN);
1338     (void) wait((int *) &i);
1339     (void) signal(SIGINT, (SIG_RET_TYPE) done1);
1340     if (wizard)
1341         (void) signal(SIGQUIT, SIG_DFL);
1342 #else
1343     /* I don't think we can really cope with external compression
1344      * without signals, so we'll declare that compress failed and
1345      * go on.  (We could do a better job by forcing off external
1346      * compression if there are no signals, but we want this for
1347      * testing with FailSafeC
1348      */
1349     i = 1;
1350 #endif
1351     if (i == 0) {
1352         /* (un)compress succeeded: remove file left behind */
1353         if (uncomp)
1354             (void) unlink(cfn);
1355         else
1356             (void) unlink(filename);
1357     } else {
1358         /* (un)compress failed; remove the new, bad file */
1359         if (uncomp) {
1360             raw_printf("Unable to uncompress %s", filename);
1361             (void) unlink(filename);
1362         } else {
1363             /* no message needed for compress case; life will go on */
1364             (void) unlink(cfn);
1365         }
1366 #ifdef TTY_GRAPHICS
1367         /* Give them a chance to read any error messages from the
1368          * compression--these would go to stdout or stderr and would get
1369          * overwritten only in tty mode.  It's still ugly, since the
1370          * messages are being written on top of the screen, but at least
1371          * the user can read them.
1372          */
1373         if (istty && iflags.window_inited) {
1374             clear_nhwindow(WIN_MESSAGE);
1375             more();
1376             /* No way to know if this is feasible */
1377             /* doredraw(); */
1378         }
1379 #endif
1380     }
1381 }
1382 #endif /* COMPRESS */
1383
1384 #if defined(COMPRESS) || defined(ZLIB_COMP)
1385 #define UNUSED_if_not_COMPRESS /*empty*/
1386 #else
1387 #define UNUSED_if_not_COMPRESS UNUSED
1388 #endif
1389
1390 /* compress file */
1391 void
1392 nh_compress(filename)
1393 const char *filename UNUSED_if_not_COMPRESS;
1394 {
1395 #if !defined(COMPRESS) && !defined(ZLIB_COMP)
1396 #ifdef PRAGMA_UNUSED
1397 #pragma unused(filename)
1398 #endif
1399 #else
1400     docompress_file(filename, FALSE);
1401 #endif
1402 }
1403
1404 /* uncompress file if it exists */
1405 void
1406 nh_uncompress(filename)
1407 const char *filename UNUSED_if_not_COMPRESS;
1408 {
1409 #if !defined(COMPRESS) && !defined(ZLIB_COMP)
1410 #ifdef PRAGMA_UNUSED
1411 #pragma unused(filename)
1412 #endif
1413 #else
1414     docompress_file(filename, TRUE);
1415 #endif
1416 }
1417
1418 #ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
1419 STATIC_OVL boolean
1420 make_compressed_name(filename, cfn)
1421 const char *filename;
1422 char *cfn;
1423 {
1424 #ifndef SHORT_FILENAMES
1425     /* Assume free-form filename with no 8.3 restrictions */
1426     strcpy(cfn, filename);
1427     strcat(cfn, COMPRESS_EXTENSION);
1428     return TRUE;
1429 #else
1430 #ifdef SAVE_EXTENSION
1431     char *bp = (char *) 0;
1432
1433     strcpy(cfn, filename);
1434     if ((bp = strstri(cfn, SAVE_EXTENSION))) {
1435         strsubst(bp, SAVE_EXTENSION, ".saz");
1436         return TRUE;
1437     } else {
1438         /* find last occurrence of bon */
1439         bp = eos(cfn);
1440         while (bp-- > cfn) {
1441             if (strstri(bp, "bon")) {
1442                 strsubst(bp, "bon", "boz");
1443                 return TRUE;
1444             }
1445         }
1446     }
1447 #endif /* SAVE_EXTENSION */
1448     return FALSE;
1449 #endif /* SHORT_FILENAMES */
1450 }
1451
1452 STATIC_OVL void
1453 docompress_file(filename, uncomp)
1454 const char *filename;
1455 boolean uncomp;
1456 {
1457     gzFile compressedfile;
1458     FILE *uncompressedfile;
1459     char cfn[256];
1460     char buf[1024];
1461     unsigned len, len2;
1462
1463     if (!make_compressed_name(filename, cfn))
1464         return;
1465
1466     if (!uncomp) {
1467         /* Open the input and output files */
1468         /* Note that gzopen takes "wb" as its mode, even on systems where
1469            fopen takes "r" and "w" */
1470
1471         uncompressedfile = fopen(filename, RDBMODE);
1472         if (!uncompressedfile) {
1473             pline("Error in zlib docompress_file %s", filename);
1474             return;
1475         }
1476         compressedfile = gzopen(cfn, "wb");
1477         if (compressedfile == NULL) {
1478             if (errno == 0) {
1479                 pline("zlib failed to allocate memory");
1480             } else {
1481                 panic("Error in docompress_file %d", errno);
1482             }
1483             fclose(uncompressedfile);
1484             return;
1485         }
1486
1487         /* Copy from the uncompressed to the compressed file */
1488
1489         while (1) {
1490             len = fread(buf, 1, sizeof(buf), uncompressedfile);
1491             if (ferror(uncompressedfile)) {
1492                 pline("Failure reading uncompressed file");
1493                 pline("Can't compress %s.", filename);
1494                 fclose(uncompressedfile);
1495                 gzclose(compressedfile);
1496                 (void) unlink(cfn);
1497                 return;
1498             }
1499             if (len == 0)
1500                 break; /* End of file */
1501
1502             len2 = gzwrite(compressedfile, buf, len);
1503             if (len2 == 0) {
1504                 pline("Failure writing compressed file");
1505                 pline("Can't compress %s.", filename);
1506                 fclose(uncompressedfile);
1507                 gzclose(compressedfile);
1508                 (void) unlink(cfn);
1509                 return;
1510             }
1511         }
1512
1513         fclose(uncompressedfile);
1514         gzclose(compressedfile);
1515
1516         /* Delete the file left behind */
1517
1518         (void) unlink(filename);
1519
1520     } else { /* uncomp */
1521
1522         /* Open the input and output files */
1523         /* Note that gzopen takes "rb" as its mode, even on systems where
1524            fopen takes "r" and "w" */
1525
1526         compressedfile = gzopen(cfn, "rb");
1527         if (compressedfile == NULL) {
1528             if (errno == 0) {
1529                 pline("zlib failed to allocate memory");
1530             } else if (errno != ENOENT) {
1531                 panic("Error in zlib docompress_file %s, %d", filename,
1532                       errno);
1533             }
1534             return;
1535         }
1536         uncompressedfile = fopen(filename, WRBMODE);
1537         if (!uncompressedfile) {
1538             pline("Error in zlib docompress file uncompress %s", filename);
1539             gzclose(compressedfile);
1540             return;
1541         }
1542
1543         /* Copy from the compressed to the uncompressed file */
1544
1545         while (1) {
1546             len = gzread(compressedfile, buf, sizeof(buf));
1547             if (len == (unsigned) -1) {
1548                 pline("Failure reading compressed file");
1549                 pline("Can't uncompress %s.", filename);
1550                 fclose(uncompressedfile);
1551                 gzclose(compressedfile);
1552                 (void) unlink(filename);
1553                 return;
1554             }
1555             if (len == 0)
1556                 break; /* End of file */
1557
1558             fwrite(buf, 1, len, uncompressedfile);
1559             if (ferror(uncompressedfile)) {
1560                 pline("Failure writing uncompressed file");
1561                 pline("Can't uncompress %s.", filename);
1562                 fclose(uncompressedfile);
1563                 gzclose(compressedfile);
1564                 (void) unlink(filename);
1565                 return;
1566             }
1567         }
1568
1569         fclose(uncompressedfile);
1570         gzclose(compressedfile);
1571
1572         /* Delete the file left behind */
1573         (void) unlink(cfn);
1574     }
1575 }
1576 #endif /* RLC 09 Mar 1999: End ZLIB patch */
1577
1578 /* ----------  END FILE COMPRESSION HANDLING ----------- */
1579
1580 /* ----------  BEGIN FILE LOCKING HANDLING ----------- */
1581
1582 static int nesting = 0;
1583
1584 #if defined(NO_FILE_LINKS) || defined(USE_FCNTL) /* implies UNIX */
1585 static int lockfd; /* for lock_file() to pass to unlock_file() */
1586 #endif
1587 #ifdef USE_FCNTL
1588 struct flock sflock; /* for unlocking, same as above */
1589 #endif
1590
1591 #define HUP if (!program_state.done_hup)
1592
1593 #ifndef USE_FCNTL
1594 STATIC_OVL char *
1595 make_lockname(filename, lockname)
1596 const char *filename;
1597 char *lockname;
1598 {
1599 #if defined(UNIX) || defined(VMS) || defined(AMIGA) || defined(WIN32) \
1600     || defined(MSDOS)
1601 #ifdef NO_FILE_LINKS
1602     Strcpy(lockname, LOCKDIR);
1603     Strcat(lockname, "/");
1604     Strcat(lockname, filename);
1605 #else
1606     Strcpy(lockname, filename);
1607 #endif
1608 #ifdef VMS
1609     {
1610         char *semi_colon = rindex(lockname, ';');
1611         if (semi_colon)
1612             *semi_colon = '\0';
1613     }
1614     Strcat(lockname, ".lock;1");
1615 #else
1616     Strcat(lockname, "_lock");
1617 #endif
1618     return lockname;
1619 #else /* !(UNIX || VMS || AMIGA || WIN32 || MSDOS) */
1620 #ifdef PRAGMA_UNUSED
1621 #pragma unused(filename)
1622 #endif
1623     lockname[0] = '\0';
1624     return (char *) 0;
1625 #endif
1626 }
1627 #endif /* !USE_FCNTL */
1628
1629 /* lock a file */
1630 boolean
1631 lock_file(filename, whichprefix, retryct)
1632 const char *filename;
1633 int whichprefix;
1634 int retryct;
1635 {
1636 #if defined(PRAGMA_UNUSED) && !(defined(UNIX) || defined(VMS)) \
1637     && !(defined(AMIGA) || defined(WIN32) || defined(MSDOS))
1638 #pragma unused(retryct)
1639 #endif
1640 #ifndef USE_FCNTL
1641     char locknambuf[BUFSZ];
1642     const char *lockname;
1643 #endif
1644
1645     nesting++;
1646     if (nesting > 1) {
1647         impossible("TRIED TO NEST LOCKS");
1648         return TRUE;
1649     }
1650
1651 #ifndef USE_FCNTL
1652     lockname = make_lockname(filename, locknambuf);
1653 #ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
1654     lockname = fqname(lockname, LOCKPREFIX, 2);
1655 #endif
1656 #endif
1657     filename = fqname(filename, whichprefix, 0);
1658 #ifdef USE_FCNTL
1659     lockfd = open(filename, O_RDWR);
1660     if (lockfd == -1) {
1661         HUP raw_printf("Cannot open file %s. This is a program bug.",
1662                        filename);
1663     }
1664     sflock.l_type = F_WRLCK;
1665     sflock.l_whence = SEEK_SET;
1666     sflock.l_start = 0;
1667     sflock.l_len = 0;
1668 #endif
1669
1670 #if defined(UNIX) || defined(VMS)
1671 #ifdef USE_FCNTL
1672     while (fcntl(lockfd, F_SETLK, &sflock) == -1) {
1673 #else
1674 #ifdef NO_FILE_LINKS
1675     while ((lockfd = open(lockname, O_RDWR | O_CREAT | O_EXCL, 0666)) == -1) {
1676 #else
1677     while (link(filename, lockname) == -1) {
1678 #endif
1679 #endif
1680
1681 #ifdef USE_FCNTL
1682         if (retryct--) {
1683             HUP raw_printf(
1684                 "Waiting for release of fcntl lock on %s. (%d retries left).",
1685                 filename, retryct);
1686             sleep(1);
1687         } else {
1688             HUP(void) raw_print("I give up.  Sorry.");
1689             HUP raw_printf("Some other process has an unnatural grip on %s.",
1690                            filename);
1691             nesting--;
1692             return FALSE;
1693         }
1694 #else
1695         register int errnosv = errno;
1696
1697         switch (errnosv) { /* George Barbanis */
1698         case EEXIST:
1699             if (retryct--) {
1700                 HUP raw_printf(
1701                     "Waiting for access to %s.  (%d retries left).", filename,
1702                     retryct);
1703 #if defined(SYSV) || defined(ULTRIX) || defined(VMS)
1704                 (void)
1705 #endif
1706                     sleep(1);
1707             } else {
1708                 HUP(void) raw_print("I give up.  Sorry.");
1709                 HUP raw_printf("Perhaps there is an old %s around?",
1710                                lockname);
1711                 nesting--;
1712                 return FALSE;
1713             }
1714
1715             break;
1716         case ENOENT:
1717             HUP raw_printf("Can't find file %s to lock!", filename);
1718             nesting--;
1719             return FALSE;
1720         case EACCES:
1721             HUP raw_printf("No write permission to lock %s!", filename);
1722             nesting--;
1723             return FALSE;
1724 #ifdef VMS /* c__translate(vmsfiles.c) */
1725         case EPERM:
1726             /* could be misleading, but usually right */
1727             HUP raw_printf("Can't lock %s due to directory protection.",
1728                            filename);
1729             nesting--;
1730             return FALSE;
1731 #endif
1732         case EROFS:
1733             /* take a wild guess at the underlying cause */
1734             HUP perror(lockname);
1735             HUP raw_printf("Cannot lock %s.", filename);
1736             HUP raw_printf(
1737   "(Perhaps you are running NetHack from inside the distribution package?).");
1738             nesting--;
1739             return FALSE;
1740         default:
1741             HUP perror(lockname);
1742             HUP raw_printf("Cannot lock %s for unknown reason (%d).",
1743                            filename, errnosv);
1744             nesting--;
1745             return FALSE;
1746         }
1747 #endif /* USE_FCNTL */
1748     }
1749 #endif /* UNIX || VMS */
1750
1751 #if (defined(AMIGA) || defined(WIN32) || defined(MSDOS)) \
1752     && !defined(USE_FCNTL)
1753 #ifdef AMIGA
1754 #define OPENFAILURE(fd) (!fd)
1755     lockptr = 0;
1756 #else
1757 #define OPENFAILURE(fd) (fd < 0)
1758     lockptr = -1;
1759 #endif
1760     while (--retryct && OPENFAILURE(lockptr)) {
1761 #if defined(WIN32) && !defined(WIN_CE)
1762         lockptr = sopen(lockname, O_RDWR | O_CREAT, SH_DENYRW, S_IWRITE);
1763 #else
1764         (void) DeleteFile(lockname); /* in case dead process was here first */
1765 #ifdef AMIGA
1766         lockptr = Open(lockname, MODE_NEWFILE);
1767 #else
1768         lockptr = open(lockname, O_RDWR | O_CREAT | O_EXCL, S_IWRITE);
1769 #endif
1770 #endif
1771         if (OPENFAILURE(lockptr)) {
1772             raw_printf("Waiting for access to %s.  (%d retries left).",
1773                        filename, retryct);
1774             Delay(50);
1775         }
1776     }
1777     if (!retryct) {
1778         raw_printf("I give up.  Sorry.");
1779         nesting--;
1780         return FALSE;
1781     }
1782 #endif /* AMIGA || WIN32 || MSDOS */
1783     return TRUE;
1784 }
1785
1786 #ifdef VMS /* for unlock_file, use the unlink() routine in vmsunix.c */
1787 #ifdef unlink
1788 #undef unlink
1789 #endif
1790 #define unlink(foo) vms_unlink(foo)
1791 #endif
1792
1793 /* unlock file, which must be currently locked by lock_file */
1794 void
1795 unlock_file(filename)
1796 const char *filename;
1797 {
1798 #ifndef USE_FCNTL
1799     char locknambuf[BUFSZ];
1800     const char *lockname;
1801 #endif
1802
1803     if (nesting == 1) {
1804 #ifdef USE_FCNTL
1805         sflock.l_type = F_UNLCK;
1806         if (fcntl(lockfd, F_SETLK, &sflock) == -1) {
1807             HUP raw_printf("Can't remove fcntl lock on %s.", filename);
1808             (void) close(lockfd);
1809         }
1810 #else
1811         lockname = make_lockname(filename, locknambuf);
1812 #ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
1813         lockname = fqname(lockname, LOCKPREFIX, 2);
1814 #endif
1815
1816 #if defined(UNIX) || defined(VMS)
1817         if (unlink(lockname) < 0)
1818             HUP raw_printf("Can't unlink %s.", lockname);
1819 #ifdef NO_FILE_LINKS
1820         (void) nhclose(lockfd);
1821 #endif
1822
1823 #endif /* UNIX || VMS */
1824
1825 #if defined(AMIGA) || defined(WIN32) || defined(MSDOS)
1826         if (lockptr)
1827             Close(lockptr);
1828         DeleteFile(lockname);
1829         lockptr = 0;
1830 #endif /* AMIGA || WIN32 || MSDOS */
1831 #endif /* USE_FCNTL */
1832     }
1833
1834     nesting--;
1835 }
1836
1837 /* ----------  END FILE LOCKING HANDLING ----------- */
1838
1839 /* ----------  BEGIN CONFIG FILE HANDLING ----------- */
1840
1841 const char *configfile =
1842 #ifdef UNIX
1843     ".nethackrc";
1844 #else
1845 #if defined(MAC) || defined(__BEOS__)
1846     "NetHack Defaults";
1847 #else
1848 #if defined(MSDOS) || defined(WIN32)
1849     "defaults.nh";
1850 #else
1851     "NetHack.cnf";
1852 #endif
1853 #endif
1854 #endif
1855
1856 /* used for messaging */
1857 char lastconfigfile[BUFSZ];
1858
1859 #ifdef MSDOS
1860 /* conflict with speed-dial under windows
1861  * for XXX.cnf file so support of NetHack.cnf
1862  * is for backward compatibility only.
1863  * Preferred name (and first tried) is now defaults.nh but
1864  * the game will try the old name if there
1865  * is no defaults.nh.
1866  */
1867 const char *backward_compat_configfile = "nethack.cnf";
1868 #endif
1869
1870 #ifndef MFLOPPY
1871 #define fopenp fopen
1872 #endif
1873
1874 STATIC_OVL FILE *
1875 fopen_config_file(filename, src)
1876 const char *filename;
1877 int src;
1878 {
1879     FILE *fp;
1880 #if defined(UNIX) || defined(VMS)
1881     char tmp_config[BUFSZ];
1882     char *envp;
1883 #endif
1884
1885     /* If src != SET_IN_SYS, "filename" is an environment variable, so it
1886      * should hang around. If set, it is expected to be a full path name
1887      * (if relevant) */
1888     if (filename) {
1889 #ifdef UNIX
1890         if ((src != SET_IN_SYS) && access(filename, 4) == -1) {
1891             /* 4 is R_OK on newer systems */
1892             /* nasty sneaky attempt to read file through
1893              * NetHack's setuid permissions -- this is the only
1894              * place a file name may be wholly under the player's
1895              * control (but SYSCF_FILE is not under the player's
1896              * control so it's OK).
1897              */
1898             raw_printf("Access to %s denied (%d).", filename, errno);
1899             wait_synch();
1900             /* fall through to standard names */
1901         } else
1902 #endif
1903 #ifdef PREFIXES_IN_USE
1904             if (src == SET_IN_SYS) {
1905             (void) strncpy(lastconfigfile, fqname(filename, SYSCONFPREFIX, 0),
1906                            BUFSZ - 1);
1907         } else
1908 #endif
1909             /* always honor sysconf first before anything else */
1910             (void) strncpy(lastconfigfile, filename, BUFSZ - 1);
1911         lastconfigfile[BUFSZ - 1] = '\0';
1912         if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
1913             return  fp;
1914         if ((fp = fopenp(filename, "r")) != (FILE *) 0) {
1915             return  fp;
1916 #if defined(UNIX) || defined(VMS)
1917         } else {
1918             /* access() above probably caught most problems for UNIX */
1919             raw_printf("Couldn't open requested config file %s (%d).",
1920                        filename, errno);
1921             wait_synch();
1922 /* fall through to standard names */
1923 #endif
1924         }
1925     }
1926
1927 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
1928     if ((fp = fopenp(fqname(configfile, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
1929         return fp;
1930     if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
1931         return fp;
1932 #ifdef MSDOS
1933     if ((fp = fopenp(fqname(backward_compat_configfile, CONFIGPREFIX, 0),
1934                      "r")) != (FILE *) 0)
1935         return fp;
1936     if ((fp = fopenp(backward_compat_configfile, "r")) != (FILE *) 0)
1937         return fp;
1938 #endif
1939 #else
1940 /* constructed full path names don't need fqname() */
1941 #ifdef VMS
1942     (void) strncpy(lastconfigfile, fqname("nethackini", CONFIGPREFIX, 0),
1943                    BUFSZ - 1);
1944     lastconfigfile[BUFSZ - 1] = '\0';
1945     if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0) {
1946         return fp;
1947     }
1948     (void) strncpy(lastconfigfile, "sys$login:nethack.ini", BUFSZ - 1);
1949     lastconfigfile[BUFSZ - 1] = '\0';
1950     if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0) {
1951         return fp;
1952     }
1953
1954     envp = nh_getenv("HOME");
1955     if (!envp)
1956         Strcpy(tmp_config, "NetHack.cnf");
1957     else
1958         Sprintf(tmp_config, "%s%s", envp, "NetHack.cnf");
1959
1960     (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
1961     lastconfigfile[BUFSZ - 1] = '\0';
1962     if ((fp = fopenp(tmp_config, "r")) != (FILE *) 0)
1963         return fp;
1964 #else /* should be only UNIX left */
1965     envp = nh_getenv("HOME");
1966 #if 1 /*JP*//*".jnethackrc"\82ð\97D\90æ\82µ\82Ä\93Ç\82Ý\8d\9e\82Ý*/
1967     if (!envp)
1968         Strcpy(tmp_config, ".jnethackrc");
1969     else
1970         Sprintf(tmp_config, "%s/%s", envp, ".jnethackrc");
1971
1972     (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
1973     if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
1974         return fp;
1975 #endif
1976     if (!envp)
1977         Strcpy(tmp_config, ".nethackrc");
1978     else
1979         Sprintf(tmp_config, "%s/%s", envp, ".nethackrc");
1980
1981     (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
1982     lastconfigfile[BUFSZ - 1] = '\0';
1983     if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
1984         return fp;
1985 #if defined(__APPLE__)
1986     /* try an alternative */
1987     if (envp) {
1988         Sprintf(tmp_config, "%s/%s", envp,
1989                 "Library/Preferences/NetHack Defaults");
1990         (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
1991         lastconfigfile[BUFSZ - 1] = '\0';
1992         if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
1993             return fp;
1994         Sprintf(tmp_config, "%s/%s", envp,
1995                 "Library/Preferences/NetHack Defaults.txt");
1996         (void) strncpy(lastconfigfile, tmp_config, BUFSZ - 1);
1997         lastconfigfile[BUFSZ - 1] = '\0';
1998         if ((fp = fopenp(lastconfigfile, "r")) != (FILE *) 0)
1999             return fp;
2000     }
2001 #endif
2002     if (errno != ENOENT) {
2003         const char *details;
2004
2005 /* e.g., problems when setuid NetHack can't search home
2006  * directory restricted to user */
2007
2008 #if defined(NHSTDC) && !defined(NOTSTDC)
2009         if ((details = strerror(errno)) == 0)
2010 #endif
2011             details = "";
2012         raw_printf("Couldn't open default config file %s %s(%d).",
2013                    lastconfigfile, details, errno);
2014         wait_synch();
2015     }
2016 #endif /* Unix */
2017 #endif
2018     return (FILE *) 0;
2019 }
2020
2021 /*
2022  * Retrieve a list of integers from a file into a uchar array.
2023  *
2024  * NOTE: zeros are inserted unless modlist is TRUE, in which case the list
2025  *  location is unchanged.  Callers must handle zeros if modlist is FALSE.
2026  */
2027 STATIC_OVL int
2028 get_uchars(fp, buf, bufp, list, modlist, size, name)
2029 FILE *fp;         /* input file pointer */
2030 char *buf;        /* read buffer, must be of size BUFSZ */
2031 char *bufp;       /* current pointer */
2032 uchar *list;      /* return list */
2033 boolean modlist;  /* TRUE: list is being modified in place */
2034 int size;         /* return list size */
2035 const char *name; /* name of option for error message */
2036 {
2037     unsigned int num = 0;
2038     int count = 0;
2039     boolean havenum = FALSE;
2040
2041     while (1) {
2042         switch (*bufp) {
2043         case ' ':
2044         case '\0':
2045         case '\t':
2046         case '\n':
2047             if (havenum) {
2048                 /* if modifying in place, don't insert zeros */
2049                 if (num || !modlist)
2050                     list[count] = num;
2051                 count++;
2052                 num = 0;
2053                 havenum = FALSE;
2054             }
2055             if (count == size || !*bufp)
2056                 return count;
2057             bufp++;
2058             break;
2059
2060         case '0':
2061         case '1':
2062         case '2':
2063         case '3':
2064         case '4':
2065         case '5':
2066         case '6':
2067         case '7':
2068         case '8':
2069         case '9':
2070             havenum = TRUE;
2071             num = num * 10 + (*bufp - '0');
2072             bufp++;
2073             break;
2074
2075         case '\\':
2076             if (fp == (FILE *) 0)
2077                 goto gi_error;
2078             do {
2079                 if (!fgets(buf, BUFSZ, fp))
2080                     goto gi_error;
2081             } while (buf[0] == '#');
2082             bufp = buf;
2083             break;
2084
2085         default:
2086         gi_error:
2087             raw_printf("Syntax error in %s", name);
2088             wait_synch();
2089             return count;
2090         }
2091     }
2092     /*NOTREACHED*/
2093 }
2094
2095 #ifdef NOCWD_ASSUMPTIONS
2096 STATIC_OVL void
2097 adjust_prefix(bufp, prefixid)
2098 char *bufp;
2099 int prefixid;
2100 {
2101     char *ptr;
2102
2103     if (!bufp)
2104         return;
2105     /* Backward compatibility, ignore trailing ;n */
2106     if ((ptr = index(bufp, ';')) != 0)
2107         *ptr = '\0';
2108     if (strlen(bufp) > 0) {
2109         fqn_prefix[prefixid] = (char *) alloc(strlen(bufp) + 2);
2110         Strcpy(fqn_prefix[prefixid], bufp);
2111         append_slash(fqn_prefix[prefixid]);
2112     }
2113 }
2114 #endif
2115
2116 #define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE)
2117
2118 int
2119 parse_config_line(fp, origbuf, src)
2120 FILE *fp;
2121 char *origbuf;
2122 int src;
2123 {
2124 #if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
2125     static boolean ramdisk_specified = FALSE;
2126 #endif
2127 #ifdef SYSCF
2128     int n;
2129 #endif
2130     char *bufp, *altp, buf[BUFSZ];
2131     uchar translate[MAXPCHARS];
2132     int len;
2133
2134     /* convert any tab to space, condense consecutive spaces into one,
2135        remove leading and trailing spaces (exception: if there is nothing
2136        but spaces, one of them will be kept even though it leads/trails) */
2137     mungspaces(strcpy(buf, origbuf));
2138     /* lines beginning with '#' are comments; accept empty lines too */
2139     if (!*buf || *buf == '#' || !strcmp(buf, " "))
2140         return 1;
2141
2142     /* find the '=' or ':' */
2143     bufp = index(buf, '=');
2144     altp = index(buf, ':');
2145     if (!bufp || (altp && altp < bufp))
2146         bufp = altp;
2147     if (!bufp)
2148         return 0;
2149     /* skip past '=', then space between it and value, if any */
2150     ++bufp;
2151     if (*bufp == ' ')
2152         ++bufp;
2153
2154     /* Go through possible variables */
2155     /* some of these (at least LEVELS and SAVE) should now set the
2156      * appropriate fqn_prefix[] rather than specialized variables
2157      */
2158     if (match_varname(buf, "OPTIONS", 4)) {
2159         /* hack: un-mungspaces to allow consecutive spaces in
2160            general options until we verify that this is unnecessary;
2161            '=' or ':' is guaranteed to be present */
2162         bufp = index(origbuf, '=');
2163         altp = index(origbuf, ':');
2164         if (!bufp || (altp && altp < bufp))
2165             bufp = altp;
2166         ++bufp; /* skip '='; parseoptions() handles spaces */
2167
2168         parseoptions(bufp, TRUE, TRUE);
2169         if (plname[0])      /* If a name was given */
2170             plnamesuffix(); /* set the character class */
2171     } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) {
2172         add_autopickup_exception(bufp);
2173     } else if (match_varname(buf, "MSGTYPE", 7)) {
2174         (void) msgtype_parse_add(bufp);
2175 #ifdef NOCWD_ASSUMPTIONS
2176     } else if (match_varname(buf, "HACKDIR", 4)) {
2177         adjust_prefix(bufp, HACKPREFIX);
2178     } else if (match_varname(buf, "LEVELDIR", 4)
2179                || match_varname(buf, "LEVELS", 4)) {
2180         adjust_prefix(bufp, LEVELPREFIX);
2181     } else if (match_varname(buf, "SAVEDIR", 4)) {
2182         adjust_prefix(bufp, SAVEPREFIX);
2183     } else if (match_varname(buf, "BONESDIR", 5)) {
2184         adjust_prefix(bufp, BONESPREFIX);
2185     } else if (match_varname(buf, "DATADIR", 4)) {
2186         adjust_prefix(bufp, DATAPREFIX);
2187     } else if (match_varname(buf, "SCOREDIR", 4)) {
2188         adjust_prefix(bufp, SCOREPREFIX);
2189     } else if (match_varname(buf, "LOCKDIR", 4)) {
2190         adjust_prefix(bufp, LOCKPREFIX);
2191     } else if (match_varname(buf, "CONFIGDIR", 4)) {
2192         adjust_prefix(bufp, CONFIGPREFIX);
2193     } else if (match_varname(buf, "TROUBLEDIR", 4)) {
2194         adjust_prefix(bufp, TROUBLEPREFIX);
2195 #else /*NOCWD_ASSUMPTIONS*/
2196 #ifdef MICRO
2197     } else if (match_varname(buf, "HACKDIR", 4)) {
2198         (void) strncpy(hackdir, bufp, PATHLEN - 1);
2199 #ifdef MFLOPPY
2200     } else if (match_varname(buf, "RAMDISK", 3)) {
2201 /* The following ifdef is NOT in the wrong
2202  * place.  For now, we accept and silently
2203  * ignore RAMDISK */
2204 #ifndef AMIGA
2205         if (strlen(bufp) >= PATHLEN)
2206             bufp[PATHLEN - 1] = '\0';
2207         Strcpy(levels, bufp);
2208         ramdisk = (strcmp(permbones, levels) != 0);
2209         ramdisk_specified = TRUE;
2210 #endif
2211 #endif
2212     } else if (match_varname(buf, "LEVELS", 4)) {
2213         if (strlen(bufp) >= PATHLEN)
2214             bufp[PATHLEN - 1] = '\0';
2215         Strcpy(permbones, bufp);
2216         if (!ramdisk_specified || !*levels)
2217             Strcpy(levels, bufp);
2218         ramdisk = (strcmp(permbones, levels) != 0);
2219     } else if (match_varname(buf, "SAVE", 4)) {
2220 #ifdef MFLOPPY
2221         extern int saveprompt;
2222 #endif
2223         char *ptr;
2224
2225         if ((ptr = index(bufp, ';')) != 0) {
2226             *ptr = '\0';
2227 #ifdef MFLOPPY
2228             if (*(ptr + 1) == 'n' || *(ptr + 1) == 'N') {
2229                 saveprompt = FALSE;
2230             }
2231 #endif
2232         }
2233 #if defined(SYSFLAGS) && defined(MFLOPPY)
2234         else
2235             saveprompt = sysflags.asksavedisk;
2236 #endif
2237
2238         (void) strncpy(SAVEP, bufp, SAVESIZE - 1);
2239         append_slash(SAVEP);
2240 #endif /* MICRO */
2241 #endif /*NOCWD_ASSUMPTIONS*/
2242
2243     } else if (match_varname(buf, "NAME", 4)) {
2244         (void) strncpy(plname, bufp, PL_NSIZ - 1);
2245         plnamesuffix();
2246     } else if (match_varname(buf, "ROLE", 4)
2247                || match_varname(buf, "CHARACTER", 4)) {
2248         if ((len = str2role(bufp)) >= 0)
2249             flags.initrole = len;
2250     } else if (match_varname(buf, "DOGNAME", 3)) {
2251         (void) strncpy(dogname, bufp, PL_PSIZ - 1);
2252     } else if (match_varname(buf, "CATNAME", 3)) {
2253         (void) strncpy(catname, bufp, PL_PSIZ - 1);
2254
2255 #ifdef SYSCF
2256     } else if (src == SET_IN_SYS && match_varname(buf, "WIZARDS", 7)) {
2257         if (sysopt.wizards)
2258             free((genericptr_t) sysopt.wizards);
2259         sysopt.wizards = dupstr(bufp);
2260         if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) {
2261             /* pre-format WIZARDS list now; it's displayed during a panic
2262                and since that panic might be due to running out of memory,
2263                we don't want to risk attempting to allocate any memory then */
2264             if (sysopt.fmtd_wizard_list)
2265                 free((genericptr_t) sysopt.fmtd_wizard_list);
2266             sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards);
2267         }
2268     } else if (src == SET_IN_SYS && match_varname(buf, "SHELLERS", 8)) {
2269         if (sysopt.shellers)
2270             free((genericptr_t) sysopt.shellers);
2271         sysopt.shellers = dupstr(bufp);
2272     } else if (src == SET_IN_SYS && match_varname(buf, "EXPLORERS", 7)) {
2273         if (sysopt.explorers)
2274             free((genericptr_t) sysopt.explorers);
2275         sysopt.explorers = dupstr(bufp);
2276     } else if (src == SET_IN_SYS && match_varname(buf, "DEBUGFILES", 5)) {
2277         /* if showdebug() has already been called (perhaps we've added
2278            some debugpline() calls to option processing) and has found
2279            a value for getenv("DEBUGFILES"), don't override that */
2280         if (sysopt.env_dbgfl <= 0) {
2281             if (sysopt.debugfiles)
2282                 free((genericptr_t) sysopt.debugfiles);
2283             sysopt.debugfiles = dupstr(bufp);
2284         }
2285     } else if (src == SET_IN_SYS && match_varname(buf, "SUPPORT", 7)) {
2286         if (sysopt.support)
2287             free((genericptr_t) sysopt.support);
2288         sysopt.support = dupstr(bufp);
2289     } else if (src == SET_IN_SYS && match_varname(buf, "RECOVER", 7)) {
2290         if (sysopt.recover)
2291             free((genericptr_t) sysopt.recover);
2292         sysopt.recover = dupstr(bufp);
2293     } else if (src == SET_IN_SYS
2294                && match_varname(buf, "CHECK_SAVE_UID", 14)) {
2295         n = atoi(bufp);
2296         sysopt.check_save_uid = n;
2297     } else if (match_varname(buf, "SEDUCE", 6)) {
2298         n = !!atoi(bufp); /* XXX this could be tighter */
2299         /* allow anyone to turn it off, but only sysconf to turn it on*/
2300         if (src != SET_IN_SYS && n != 0) {
2301             raw_printf("Illegal value in SEDUCE");
2302             return 0;
2303         }
2304         sysopt.seduce = n;
2305         sysopt_seduce_set(sysopt.seduce);
2306     } else if (src == SET_IN_SYS && match_varname(buf, "MAXPLAYERS", 10)) {
2307         n = atoi(bufp);
2308         /* XXX to get more than 25, need to rewrite all lock code */
2309         if (n < 1 || n > 25) {
2310             raw_printf("Illegal value in MAXPLAYERS (maximum is 25).");
2311             return 0;
2312         }
2313         sysopt.maxplayers = n;
2314     } else if (src == SET_IN_SYS && match_varname(buf, "PERSMAX", 7)) {
2315         n = atoi(bufp);
2316         if (n < 1) {
2317             raw_printf("Illegal value in PERSMAX (minimum is 1).");
2318             return 0;
2319         }
2320         sysopt.persmax = n;
2321     } else if (src == SET_IN_SYS && match_varname(buf, "PERS_IS_UID", 11)) {
2322         n = atoi(bufp);
2323         if (n != 0 && n != 1) {
2324             raw_printf("Illegal value in PERS_IS_UID (must be 0 or 1).");
2325             return 0;
2326         }
2327         sysopt.pers_is_uid = n;
2328     } else if (src == SET_IN_SYS && match_varname(buf, "ENTRYMAX", 8)) {
2329         n = atoi(bufp);
2330         if (n < 10) {
2331             raw_printf("Illegal value in ENTRYMAX (minimum is 10).");
2332             return 0;
2333         }
2334         sysopt.entrymax = n;
2335     } else if ((src == SET_IN_SYS) && match_varname(buf, "POINTSMIN", 9)) {
2336         n = atoi(bufp);
2337         if (n < 1) {
2338             raw_printf("Illegal value in POINTSMIN (minimum is 1).");
2339             return 0;
2340         }
2341         sysopt.pointsmin = n;
2342     } else if (src == SET_IN_SYS
2343                && match_varname(buf, "MAX_STATUENAME_RANK", 10)) {
2344         n = atoi(bufp);
2345         if (n < 1) {
2346             raw_printf(
2347                 "Illegal value in MAX_STATUENAME_RANK (minimum is 1).");
2348             return 0;
2349         }
2350         sysopt.tt_oname_maxrank = n;
2351
2352     /* SYSCF PANICTRACE options */
2353     } else if (src == SET_IN_SYS
2354                && match_varname(buf, "PANICTRACE_LIBC", 15)) {
2355         n = atoi(bufp);
2356 #if defined(PANICTRACE) && defined(PANICTRACE_LIBC)
2357         if (n < 0 || n > 2) {
2358             raw_printf("Illegal value in PANICTRACE_LIBC (not 0,1,2).");
2359             return 0;
2360         }
2361 #endif
2362         sysopt.panictrace_libc = n;
2363     } else if (src == SET_IN_SYS
2364                && match_varname(buf, "PANICTRACE_GDB", 14)) {
2365         n = atoi(bufp);
2366 #if defined(PANICTRACE)
2367         if (n < 0 || n > 2) {
2368             raw_printf("Illegal value in PANICTRACE_GDB (not 0,1,2).");
2369             return 0;
2370         }
2371 #endif
2372         sysopt.panictrace_gdb = n;
2373     } else if (src == SET_IN_SYS && match_varname(buf, "GDBPATH", 7)) {
2374 #if defined(PANICTRACE) && !defined(VMS)
2375         if (!file_exists(bufp)) {
2376             raw_printf("File specified in GDBPATH does not exist.");
2377             return 0;
2378         }
2379 #endif
2380         if (sysopt.gdbpath)
2381             free((genericptr_t) sysopt.gdbpath);
2382         sysopt.gdbpath = dupstr(bufp);
2383     } else if (src == SET_IN_SYS && match_varname(buf, "GREPPATH", 7)) {
2384 #if defined(PANICTRACE) && !defined(VMS)
2385         if (!file_exists(bufp)) {
2386             raw_printf("File specified in GREPPATH does not exist.");
2387             return 0;
2388         }
2389 #endif
2390         if (sysopt.greppath)
2391             free((genericptr_t) sysopt.greppath);
2392         sysopt.greppath = dupstr(bufp);
2393 #endif /* SYSCF */
2394
2395     } else if (match_varname(buf, "BOULDER", 3)) {
2396         (void) get_uchars(fp, buf, bufp, &iflags.bouldersym, TRUE, 1,
2397                           "BOULDER");
2398     } else if (match_varname(buf, "MENUCOLOR", 9)) {
2399         (void) add_menu_coloring(bufp);
2400     } else if (match_varname(buf, "WARNINGS", 5)) {
2401         (void) get_uchars(fp, buf, bufp, translate, FALSE, WARNCOUNT,
2402                           "WARNINGS");
2403         assign_warnings(translate);
2404     } else if (match_varname(buf, "SYMBOLS", 4)) {
2405         char *op, symbuf[BUFSZ];
2406         boolean morelines;
2407
2408         do {
2409             /* check for line continuation (trailing '\') */
2410             op = eos(bufp);
2411             morelines = (--op >= bufp && *op == '\\');
2412             if (morelines) {
2413                 *op = '\0';
2414                 /* strip trailing space now that '\' is gone */
2415                 if (--op >= bufp && *op == ' ')
2416                     *op = '\0';
2417             }
2418             /* parse here */
2419             parsesymbols(bufp);
2420             if (morelines) {
2421                 do {
2422                     *symbuf = '\0';
2423                     if (!fgets(symbuf, BUFSZ, fp)) {
2424                         morelines = FALSE;
2425                         break;
2426                     }
2427                     mungspaces(symbuf);
2428                     bufp = symbuf;
2429                 } while (*bufp == '#');
2430             }
2431         } while (morelines);
2432         switch_symbols(TRUE);
2433     } else if (match_varname(buf, "WIZKIT", 6)) {
2434         (void) strncpy(wizkit, bufp, WIZKIT_MAX - 1);
2435 #ifdef AMIGA
2436     } else if (match_varname(buf, "FONT", 4)) {
2437         char *t;
2438
2439         if (t = strchr(buf + 5, ':')) {
2440             *t = 0;
2441             amii_set_text_font(buf + 5, atoi(t + 1));
2442             *t = ':';
2443         }
2444     } else if (match_varname(buf, "PATH", 4)) {
2445         (void) strncpy(PATH, bufp, PATHLEN - 1);
2446     } else if (match_varname(buf, "DEPTH", 5)) {
2447         extern int amii_numcolors;
2448         int val = atoi(bufp);
2449
2450         amii_numcolors = 1L << min(DEPTH, val);
2451 #ifdef SYSFLAGS
2452     } else if (match_varname(buf, "DRIPENS", 7)) {
2453         int i, val;
2454         char *t;
2455
2456         for (i = 0, t = strtok(bufp, ",/"); t != (char *) 0;
2457              i < 20 && (t = strtok((char *) 0, ",/")), ++i) {
2458             sscanf(t, "%d", &val);
2459             sysflags.amii_dripens[i] = val;
2460         }
2461 #endif
2462     } else if (match_varname(buf, "SCREENMODE", 10)) {
2463         extern long amii_scrnmode;
2464
2465         if (!stricmp(bufp, "req"))
2466             amii_scrnmode = 0xffffffff; /* Requester */
2467         else if (sscanf(bufp, "%x", &amii_scrnmode) != 1)
2468             amii_scrnmode = 0;
2469     } else if (match_varname(buf, "MSGPENS", 7)) {
2470         extern int amii_msgAPen, amii_msgBPen;
2471         char *t = strtok(bufp, ",/");
2472
2473         if (t) {
2474             sscanf(t, "%d", &amii_msgAPen);
2475             if (t = strtok((char *) 0, ",/"))
2476                 sscanf(t, "%d", &amii_msgBPen);
2477         }
2478     } else if (match_varname(buf, "TEXTPENS", 8)) {
2479         extern int amii_textAPen, amii_textBPen;
2480         char *t = strtok(bufp, ",/");
2481
2482         if (t) {
2483             sscanf(t, "%d", &amii_textAPen);
2484             if (t = strtok((char *) 0, ",/"))
2485                 sscanf(t, "%d", &amii_textBPen);
2486         }
2487     } else if (match_varname(buf, "MENUPENS", 8)) {
2488         extern int amii_menuAPen, amii_menuBPen;
2489         char *t = strtok(bufp, ",/");
2490
2491         if (t) {
2492             sscanf(t, "%d", &amii_menuAPen);
2493             if (t = strtok((char *) 0, ",/"))
2494                 sscanf(t, "%d", &amii_menuBPen);
2495         }
2496     } else if (match_varname(buf, "STATUSPENS", 10)) {
2497         extern int amii_statAPen, amii_statBPen;
2498         char *t = strtok(bufp, ",/");
2499
2500         if (t) {
2501             sscanf(t, "%d", &amii_statAPen);
2502             if (t = strtok((char *) 0, ",/"))
2503                 sscanf(t, "%d", &amii_statBPen);
2504         }
2505     } else if (match_varname(buf, "OTHERPENS", 9)) {
2506         extern int amii_otherAPen, amii_otherBPen;
2507         char *t = strtok(bufp, ",/");
2508
2509         if (t) {
2510             sscanf(t, "%d", &amii_otherAPen);
2511             if (t = strtok((char *) 0, ",/"))
2512                 sscanf(t, "%d", &amii_otherBPen);
2513         }
2514     } else if (match_varname(buf, "PENS", 4)) {
2515         extern unsigned short amii_init_map[AMII_MAXCOLORS];
2516         int i;
2517         char *t;
2518
2519         for (i = 0, t = strtok(bufp, ",/");
2520              i < AMII_MAXCOLORS && t != (char *) 0;
2521              t = strtok((char *) 0, ",/"), ++i) {
2522             sscanf(t, "%hx", &amii_init_map[i]);
2523         }
2524         amii_setpens(amii_numcolors = i);
2525     } else if (match_varname(buf, "FGPENS", 6)) {
2526         extern int foreg[AMII_MAXCOLORS];
2527         int i;
2528         char *t;
2529
2530         for (i = 0, t = strtok(bufp, ",/");
2531              i < AMII_MAXCOLORS && t != (char *) 0;
2532              t = strtok((char *) 0, ",/"), ++i) {
2533             sscanf(t, "%d", &foreg[i]);
2534         }
2535     } else if (match_varname(buf, "BGPENS", 6)) {
2536         extern int backg[AMII_MAXCOLORS];
2537         int i;
2538         char *t;
2539
2540         for (i = 0, t = strtok(bufp, ",/");
2541              i < AMII_MAXCOLORS && t != (char *) 0;
2542              t = strtok((char *) 0, ",/"), ++i) {
2543             sscanf(t, "%d", &backg[i]);
2544         }
2545 #endif /*AMIGA*/
2546 #ifdef USER_SOUNDS
2547     } else if (match_varname(buf, "SOUNDDIR", 8)) {
2548         sounddir = dupstr(bufp);
2549     } else if (match_varname(buf, "SOUND", 5)) {
2550         add_sound_mapping(bufp);
2551 #endif
2552 #ifdef QT_GRAPHICS
2553         /* These should move to wc_ options */
2554     } else if (match_varname(buf, "QT_TILEWIDTH", 12)) {
2555         extern char *qt_tilewidth;
2556
2557         if (qt_tilewidth == NULL)
2558             qt_tilewidth = dupstr(bufp);
2559     } else if (match_varname(buf, "QT_TILEHEIGHT", 13)) {
2560         extern char *qt_tileheight;
2561
2562         if (qt_tileheight == NULL)
2563             qt_tileheight = dupstr(bufp);
2564     } else if (match_varname(buf, "QT_FONTSIZE", 11)) {
2565         extern char *qt_fontsize;
2566
2567         if (qt_fontsize == NULL)
2568             qt_fontsize = dupstr(bufp);
2569     } else if (match_varname(buf, "QT_COMPACT", 10)) {
2570         extern int qt_compact_mode;
2571
2572         qt_compact_mode = atoi(bufp);
2573 #endif
2574     } else
2575         return 0;
2576     return 1;
2577 }
2578
2579 #ifdef USER_SOUNDS
2580 boolean
2581 can_read_file(filename)
2582 const char *filename;
2583 {
2584     return (boolean) (access(filename, 4) == 0);
2585 }
2586 #endif /* USER_SOUNDS */
2587
2588 boolean
2589 read_config_file(filename, src)
2590 const char *filename;
2591 int src;
2592 {
2593     char buf[4 * BUFSZ], *p;
2594     FILE *fp;
2595     boolean rv = TRUE; /* assume successful parse */
2596
2597     if (!(fp = fopen_config_file(filename, src)))
2598         return FALSE;
2599
2600     /* begin detection of duplicate configfile options */
2601     set_duplicate_opt_detection(1);
2602
2603     while (fgets(buf, sizeof buf, fp)) {
2604 #ifdef notyet
2605 /*
2606 XXX Don't call read() in parse_config_line, read as callback or reassemble
2607 line at this level.
2608 OR: Forbid multiline stuff for alternate config sources.
2609 */
2610 #endif
2611         if ((p = index(buf, '\n')) != 0)
2612             *p = '\0';
2613         if (!parse_config_line(fp, buf, src)) {
2614             static const char badoptionline[] = "Bad option line: \"%s\"";
2615
2616             /* truncate buffer if it's long; this is actually conservative */
2617             if (strlen(buf) > BUFSZ - sizeof badoptionline)
2618                 buf[BUFSZ - sizeof badoptionline] = '\0';
2619
2620             raw_printf(badoptionline, buf);
2621             wait_synch();
2622             rv = FALSE;
2623         }
2624     }
2625     (void) fclose(fp);
2626
2627     /* turn off detection of duplicate configfile options */
2628     set_duplicate_opt_detection(0);
2629     return rv;
2630 }
2631
2632 STATIC_OVL FILE *
2633 fopen_wizkit_file()
2634 {
2635     FILE *fp;
2636 #if defined(VMS) || defined(UNIX)
2637     char tmp_wizkit[BUFSZ];
2638 #endif
2639     char *envp;
2640
2641     envp = nh_getenv("WIZKIT");
2642     if (envp && *envp)
2643         (void) strncpy(wizkit, envp, WIZKIT_MAX - 1);
2644     if (!wizkit[0])
2645         return (FILE *) 0;
2646
2647 #ifdef UNIX
2648     if (access(wizkit, 4) == -1) {
2649         /* 4 is R_OK on newer systems */
2650         /* nasty sneaky attempt to read file through
2651          * NetHack's setuid permissions -- this is a
2652          * place a file name may be wholly under the player's
2653          * control
2654          */
2655         raw_printf("Access to %s denied (%d).", wizkit, errno);
2656         wait_synch();
2657         /* fall through to standard names */
2658     } else
2659 #endif
2660         if ((fp = fopenp(wizkit, "r")) != (FILE *) 0) {
2661         return fp;
2662 #if defined(UNIX) || defined(VMS)
2663     } else {
2664         /* access() above probably caught most problems for UNIX */
2665         raw_printf("Couldn't open requested config file %s (%d).", wizkit,
2666                    errno);
2667         wait_synch();
2668 #endif
2669     }
2670
2671 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
2672     if ((fp = fopenp(fqname(wizkit, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
2673         return fp;
2674 #else
2675 #ifdef VMS
2676     envp = nh_getenv("HOME");
2677     if (envp)
2678         Sprintf(tmp_wizkit, "%s%s", envp, wizkit);
2679     else
2680         Sprintf(tmp_wizkit, "%s%s", "sys$login:", wizkit);
2681     if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2682         return fp;
2683 #else /* should be only UNIX left */
2684     envp = nh_getenv("HOME");
2685     if (envp)
2686         Sprintf(tmp_wizkit, "%s/%s", envp, wizkit);
2687     else
2688         Strcpy(tmp_wizkit, wizkit);
2689     if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2690         return fp;
2691     else if (errno != ENOENT) {
2692         /* e.g., problems when setuid NetHack can't search home
2693          * directory restricted to user */
2694         raw_printf("Couldn't open default wizkit file %s (%d).", tmp_wizkit,
2695                    errno);
2696         wait_synch();
2697     }
2698 #endif
2699 #endif
2700     return (FILE *) 0;
2701 }
2702
2703 /* add to hero's inventory if there's room, otherwise put item on floor */
2704 STATIC_DCL void
2705 wizkit_addinv(obj)
2706 struct obj *obj;
2707 {
2708     if (!obj || obj == &zeroobj)
2709         return;
2710
2711     /* subset of starting inventory pre-ID */
2712     obj->dknown = 1;
2713     if (Role_if(PM_PRIEST))
2714         obj->bknown = 1;
2715     /* same criteria as lift_object()'s check for available inventory slot */
2716     if (obj->oclass != COIN_CLASS && inv_cnt(FALSE) >= 52
2717         && !merge_choice(invent, obj)) {
2718         /* inventory overflow; can't just place & stack object since
2719            hero isn't in position yet, so schedule for arrival later */
2720         add_to_migration(obj);
2721         obj->ox = 0; /* index of main dungeon */
2722         obj->oy = 1; /* starting level number */
2723         obj->owornmask =
2724             (long) (MIGR_WITH_HERO | MIGR_NOBREAK | MIGR_NOSCATTER);
2725     } else {
2726         (void) addinv(obj);
2727     }
2728 }
2729
2730 void
2731 read_wizkit()
2732 {
2733     FILE *fp;
2734     char *ep, buf[BUFSZ];
2735     struct obj *otmp;
2736     boolean bad_items = FALSE, skip = FALSE;
2737
2738     if (!wizard || !(fp = fopen_wizkit_file()))
2739         return;
2740
2741     program_state.wizkit_wishing = 1;
2742     while (fgets(buf, (int) (sizeof buf), fp)) {
2743         ep = index(buf, '\n');
2744         if (skip) { /* in case previous line was too long */
2745             if (ep)
2746                 skip = FALSE; /* found newline; next line is normal */
2747         } else {
2748             if (!ep)
2749                 skip = TRUE; /* newline missing; discard next fgets */
2750             else
2751                 *ep = '\0'; /* remove newline */
2752
2753             if (buf[0]) {
2754                 otmp = readobjnam(buf, (struct obj *) 0);
2755                 if (otmp) {
2756                     if (otmp != &zeroobj)
2757                         wizkit_addinv(otmp);
2758                 } else {
2759                     /* .60 limits output line width to 79 chars */
2760                     raw_printf("Bad wizkit item: \"%.60s\"", buf);
2761                     bad_items = TRUE;
2762                 }
2763             }
2764         }
2765     }
2766     program_state.wizkit_wishing = 0;
2767     if (bad_items)
2768         wait_synch();
2769     (void) fclose(fp);
2770     return;
2771 }
2772
2773 extern struct symsetentry *symset_list;  /* options.c */
2774 extern struct symparse loadsyms[];       /* drawing.c */
2775 extern const char *known_handling[];     /* drawing.c */
2776 extern const char *known_restrictions[]; /* drawing.c */
2777 static int symset_count = 0;             /* for pick-list building only */
2778 static boolean chosen_symset_start = FALSE, chosen_symset_end = FALSE;
2779
2780 STATIC_OVL
2781 FILE *
2782 fopen_sym_file()
2783 {
2784     FILE *fp;
2785
2786     fp = fopen_datafile(SYMBOLS, "r", HACKPREFIX);
2787     return fp;
2788 }
2789
2790 /*
2791  * Returns 1 if the chose symset was found and loaded.
2792  *         0 if it wasn't found in the sym file or other problem.
2793  */
2794 int
2795 read_sym_file(which_set)
2796 int which_set;
2797 {
2798     char buf[4 * BUFSZ];
2799     FILE *fp;
2800
2801     if (!(fp = fopen_sym_file()))
2802         return 0;
2803
2804     symset_count = 0;
2805     chosen_symset_start = chosen_symset_end = FALSE;
2806     while (fgets(buf, 4 * BUFSZ, fp)) {
2807         if (!parse_sym_line(buf, which_set)) {
2808             raw_printf("Bad symbol line:  \"%.50s\"", buf);
2809             wait_synch();
2810         }
2811     }
2812     (void) fclose(fp);
2813     if (!chosen_symset_end && !chosen_symset_start)
2814         return (symset[which_set].name == 0) ? 1 : 0;
2815     if (!chosen_symset_end) {
2816         raw_printf("Missing finish for symset \"%s\"",
2817                    symset[which_set].name ? symset[which_set].name
2818                                           : "unknown");
2819         wait_synch();
2820     }
2821     return 1;
2822 }
2823
2824 /* returns 0 on error */
2825 int
2826 parse_sym_line(buf, which_set)
2827 char *buf;
2828 int which_set;
2829 {
2830     int val, i;
2831     struct symparse *symp = (struct symparse *) 0;
2832     char *bufp, *commentp, *altp;
2833
2834     /* convert each instance of whitespace (tabs, consecutive spaces)
2835        into a single space; leading and trailing spaces are stripped */
2836     mungspaces(buf);
2837     if (!*buf || *buf == '#' || !strcmp(buf, " "))
2838         return 1;
2839     /* remove trailing comment, if any */
2840     if ((commentp = rindex(buf, '#')) != 0) {
2841         *commentp = '\0';
2842         /* remove space preceding the stripped comment, if any;
2843            we know 'commentp > buf' because *buf=='#' was caught above */
2844         if (commentp[-1] == ' ')
2845             *--commentp = '\0';
2846     }
2847
2848     /* find the '=' or ':' */
2849     bufp = index(buf, '=');
2850     altp = index(buf, ':');
2851     if (!bufp || (altp && altp < bufp))
2852         bufp = altp;
2853     if (!bufp) {
2854         if (strncmpi(buf, "finish", 6) == 0) {
2855             /* end current graphics set */
2856             if (chosen_symset_start)
2857                 chosen_symset_end = TRUE;
2858             chosen_symset_start = FALSE;
2859             return 1;
2860         }
2861         return 0;
2862     }
2863     /* skip '=' and space which follows, if any */
2864     ++bufp;
2865     if (*bufp == ' ')
2866         ++bufp;
2867
2868     symp = match_sym(buf);
2869     if (!symp)
2870         return 0;
2871
2872     if (!symset[which_set].name) {
2873         /* A null symset name indicates that we're just
2874            building a pick-list of possible symset
2875            values from the file, so only do that */
2876         if (symp->range == SYM_CONTROL) {
2877             struct symsetentry *tmpsp;
2878
2879             switch (symp->idx) {
2880             case 0:
2881                 tmpsp =
2882                     (struct symsetentry *) alloc(sizeof (struct symsetentry));
2883                 tmpsp->next = (struct symsetentry *) 0;
2884                 if (!symset_list) {
2885                     symset_list = tmpsp;
2886                     symset_count = 0;
2887                 } else {
2888                     symset_count++;
2889                     tmpsp->next = symset_list;
2890                     symset_list = tmpsp;
2891                 }
2892                 tmpsp->idx = symset_count;
2893                 tmpsp->name = dupstr(bufp);
2894                 tmpsp->desc = (char *) 0;
2895                 tmpsp->nocolor = 0;
2896                 /* initialize restriction bits */
2897                 tmpsp->primary = 0;
2898                 tmpsp->rogue = 0;
2899                 break;
2900             case 2:
2901                 /* handler type identified */
2902                 tmpsp = symset_list; /* most recent symset */
2903                 tmpsp->handling = H_UNK;
2904                 i = 0;
2905                 while (known_handling[i]) {
2906                     if (!strcmpi(known_handling[i], bufp)) {
2907                         tmpsp->handling = i;
2908                         break; /* while loop */
2909                     }
2910                     i++;
2911                 }
2912                 break;
2913             case 3:                  /* description:something */
2914                 tmpsp = symset_list; /* most recent symset */
2915                 if (tmpsp && !tmpsp->desc)
2916                     tmpsp->desc = dupstr(bufp);
2917                 break;
2918             case 5:
2919                 /* restrictions: xxxx*/
2920                 tmpsp = symset_list; /* most recent symset */
2921                 for (i = 0; known_restrictions[i]; ++i) {
2922                     if (!strcmpi(known_restrictions[i], bufp)) {
2923                         switch (i) {
2924                         case 0:
2925                             tmpsp->primary = 1;
2926                             break;
2927                         case 1:
2928                             tmpsp->rogue = 1;
2929                             break;
2930                         }
2931                         break; /* while loop */
2932                     }
2933                 }
2934                 break;
2935             }
2936         }
2937         return 1;
2938     }
2939     if (symp->range) {
2940         if (symp->range == SYM_CONTROL) {
2941             switch (symp->idx) {
2942             case 0:
2943                 /* start of symset */
2944                 if (!strcmpi(bufp, symset[which_set].name)) {
2945                     /* matches desired one */
2946                     chosen_symset_start = TRUE;
2947                     /* these init_*() functions clear symset fields too */
2948                     if (which_set == ROGUESET)
2949                         init_r_symbols();
2950                     else if (which_set == PRIMARY)
2951                         init_l_symbols();
2952                 }
2953                 break;
2954             case 1:
2955                 /* finish symset */
2956                 if (chosen_symset_start)
2957                     chosen_symset_end = TRUE;
2958                 chosen_symset_start = FALSE;
2959                 break;
2960             case 2:
2961                 /* handler type identified */
2962                 if (chosen_symset_start)
2963                     set_symhandling(bufp, which_set);
2964                 break;
2965             /* case 3: (description) is ignored here */
2966             case 4: /* color:off */
2967                 if (chosen_symset_start) {
2968                     if (bufp) {
2969                         if (!strcmpi(bufp, "true") || !strcmpi(bufp, "yes")
2970                             || !strcmpi(bufp, "on"))
2971                             symset[which_set].nocolor = 0;
2972                         else if (!strcmpi(bufp, "false")
2973                                  || !strcmpi(bufp, "no")
2974                                  || !strcmpi(bufp, "off"))
2975                             symset[which_set].nocolor = 1;
2976                     }
2977                 }
2978                 break;
2979             case 5: /* restrictions: xxxx*/
2980                 if (chosen_symset_start) {
2981                     int n = 0;
2982
2983                     while (known_restrictions[n]) {
2984                         if (!strcmpi(known_restrictions[n], bufp)) {
2985                             switch (n) {
2986                             case 0:
2987                                 symset[which_set].primary = 1;
2988                                 break;
2989                             case 1:
2990                                 symset[which_set].rogue = 1;
2991                                 break;
2992                             }
2993                             break; /* while loop */
2994                         }
2995                         n++;
2996                     }
2997                 }
2998                 break;
2999             }
3000         } else { /* !SYM_CONTROL */
3001             val = sym_val(bufp);
3002             if (chosen_symset_start) {
3003                 if (which_set == PRIMARY) {
3004                     update_l_symset(symp, val);
3005                 } else if (which_set == ROGUESET) {
3006                     update_r_symset(symp, val);
3007                 }
3008             }
3009         }
3010     }
3011     return 1;
3012 }
3013
3014 STATIC_OVL void
3015 set_symhandling(handling, which_set)
3016 char *handling;
3017 int which_set;
3018 {
3019     int i = 0;
3020
3021     symset[which_set].handling = H_UNK;
3022     while (known_handling[i]) {
3023         if (!strcmpi(known_handling[i], handling)) {
3024             symset[which_set].handling = i;
3025             return;
3026         }
3027         i++;
3028     }
3029 }
3030
3031 /* ----------  END CONFIG FILE HANDLING ----------- */
3032
3033 /* ----------  BEGIN SCOREBOARD CREATION ----------- */
3034
3035 #ifdef OS2_CODEVIEW
3036 #define UNUSED_if_not_OS2_CODEVIEW /*empty*/
3037 #else
3038 #define UNUSED_if_not_OS2_CODEVIEW UNUSED
3039 #endif
3040
3041 /* verify that we can write to scoreboard file; if not, try to create one */
3042 /*ARGUSED*/
3043 void
3044 check_recordfile(dir)
3045 const char *dir UNUSED_if_not_OS2_CODEVIEW;
3046 {
3047 #if defined(PRAGMA_UNUSED) && !defined(OS2_CODEVIEW)
3048 #pragma unused(dir)
3049 #endif
3050     const char *fq_record;
3051     int fd;
3052
3053 #if defined(UNIX) || defined(VMS)
3054     fq_record = fqname(RECORD, SCOREPREFIX, 0);
3055     fd = open(fq_record, O_RDWR, 0);
3056     if (fd >= 0) {
3057 #ifdef VMS /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */
3058         if (!file_is_stmlf(fd)) {
3059             raw_printf(
3060                 "Warning: scoreboard file %s is not in stream_lf format",
3061                 fq_record);
3062             wait_synch();
3063         }
3064 #endif
3065         (void) nhclose(fd); /* RECORD is accessible */
3066     } else if ((fd = open(fq_record, O_CREAT | O_RDWR, FCMASK)) >= 0) {
3067         (void) nhclose(fd); /* RECORD newly created */
3068 #if defined(VMS) && !defined(SECURE)
3069         /* Re-protect RECORD with world:read+write+execute+delete access. */
3070         (void) chmod(fq_record, FCMASK | 007);
3071 #endif /* VMS && !SECURE */
3072     } else {
3073         raw_printf("Warning: cannot write scoreboard file %s", fq_record);
3074         wait_synch();
3075     }
3076 #endif /* !UNIX && !VMS */
3077 #if defined(MICRO) || defined(WIN32)
3078     char tmp[PATHLEN];
3079
3080 #ifdef OS2_CODEVIEW /* explicit path on opening for OS/2 */
3081     /* how does this work when there isn't an explicit path or fopenp
3082      * for later access to the file via fopen_datafile? ? */
3083     (void) strncpy(tmp, dir, PATHLEN - 1);
3084     tmp[PATHLEN - 1] = '\0';
3085     if ((strlen(tmp) + 1 + strlen(RECORD)) < (PATHLEN - 1)) {
3086         append_slash(tmp);
3087         Strcat(tmp, RECORD);
3088     }
3089     fq_record = tmp;
3090 #else
3091     Strcpy(tmp, RECORD);
3092     fq_record = fqname(RECORD, SCOREPREFIX, 0);
3093 #endif
3094
3095     if ((fd = open(fq_record, O_RDWR)) < 0) {
3096 /* try to create empty record */
3097 #if defined(AZTEC_C) || defined(_DCC) \
3098     || (defined(__GNUC__) && defined(__AMIGA__))
3099         /* Aztec doesn't use the third argument */
3100         /* DICE doesn't like it */
3101         if ((fd = open(fq_record, O_CREAT | O_RDWR)) < 0) {
3102 #else
3103         if ((fd = open(fq_record, O_CREAT | O_RDWR, S_IREAD | S_IWRITE))
3104             < 0) {
3105 #endif
3106             raw_printf("Warning: cannot write record %s", tmp);
3107             wait_synch();
3108         } else
3109             (void) nhclose(fd);
3110     } else /* open succeeded */
3111         (void) nhclose(fd);
3112 #else /* MICRO || WIN32*/
3113
3114 #ifdef MAC
3115     /* Create the "record" file, if necessary */
3116     fq_record = fqname(RECORD, SCOREPREFIX, 0);
3117     fd = macopen(fq_record, O_RDWR | O_CREAT, TEXT_TYPE);
3118     if (fd != -1)
3119         macclose(fd);
3120 #endif /* MAC */
3121
3122 #endif /* MICRO || WIN32*/
3123 }
3124
3125 /* ----------  END SCOREBOARD CREATION ----------- */
3126
3127 /* ----------  BEGIN PANIC/IMPOSSIBLE LOG ----------- */
3128
3129 /*ARGSUSED*/
3130 void
3131 paniclog(type, reason)
3132 const char *type;   /* panic, impossible, trickery */
3133 const char *reason; /* explanation */
3134 {
3135 #ifdef PANICLOG
3136     FILE *lfile;
3137     char buf[BUFSZ];
3138
3139     if (!program_state.in_paniclog) {
3140         program_state.in_paniclog = 1;
3141         lfile = fopen_datafile(PANICLOG, "a", TROUBLEPREFIX);
3142         if (lfile) {
3143             time_t now = getnow();
3144             int uid = getuid();
3145             char playmode = wizard ? 'D' : discover ? 'X' : '-';
3146
3147             (void) fprintf(lfile, "%s %08ld %06ld %d %c: %s %s\n",
3148                            version_string(buf), yyyymmdd(now), hhmmss(now),
3149                            uid, playmode, type, reason);
3150             (void) fclose(lfile);
3151         }
3152         program_state.in_paniclog = 0;
3153     }
3154 #endif /* PANICLOG */
3155     return;
3156 }
3157
3158 /* ----------  END PANIC/IMPOSSIBLE LOG ----------- */
3159
3160 #ifdef SELF_RECOVER
3161
3162 /* ----------  BEGIN INTERNAL RECOVER ----------- */
3163 boolean
3164 recover_savefile()
3165 {
3166     int gfd, lfd, sfd;
3167     int lev, savelev, hpid, pltmpsiz;
3168     xchar levc;
3169     struct version_info version_data;
3170     int processed[256];
3171     char savename[SAVESIZE], errbuf[BUFSZ];
3172     struct savefile_info sfi;
3173     char tmpplbuf[PL_NSIZ];
3174
3175     for (lev = 0; lev < 256; lev++)
3176         processed[lev] = 0;
3177
3178     /* level 0 file contains:
3179      *  pid of creating process (ignored here)
3180      *  level number for current level of save file
3181      *  name of save file nethack would have created
3182      *  savefile info
3183      *  player name
3184      *  and game state
3185      */
3186     gfd = open_levelfile(0, errbuf);
3187     if (gfd < 0) {
3188         raw_printf("%s\n", errbuf);
3189         return FALSE;
3190     }
3191     if (read(gfd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) {
3192         raw_printf("\n%s\n%s\n",
3193             "Checkpoint data incompletely written or subsequently clobbered.",
3194                    "Recovery impossible.");
3195         (void) nhclose(gfd);
3196         return FALSE;
3197     }
3198     if (read(gfd, (genericptr_t) &savelev, sizeof(savelev))
3199         != sizeof(savelev)) {
3200         raw_printf(
3201          "\nCheckpointing was not in effect for %s -- recovery impossible.\n",
3202                    lock);
3203         (void) nhclose(gfd);
3204         return FALSE;
3205     }
3206     if ((read(gfd, (genericptr_t) savename, sizeof savename)
3207          != sizeof savename)
3208         || (read(gfd, (genericptr_t) &version_data, sizeof version_data)
3209             != sizeof version_data)
3210         || (read(gfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi)
3211         || (read(gfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3212             != sizeof pltmpsiz) || (pltmpsiz > PL_NSIZ)
3213         || (read(gfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz)) {
3214         raw_printf("\nError reading %s -- can't recover.\n", lock);
3215         (void) nhclose(gfd);
3216         return FALSE;
3217     }
3218
3219     /* save file should contain:
3220      *  version info
3221      *  savefile info
3222      *  player name
3223      *  current level (including pets)
3224      *  (non-level-based) game state
3225      *  other levels
3226      */
3227     set_savefile_name(TRUE);
3228     sfd = create_savefile();
3229     if (sfd < 0) {
3230         raw_printf("\nCannot recover savefile %s.\n", SAVEF);
3231         (void) nhclose(gfd);
3232         return FALSE;
3233     }
3234
3235     lfd = open_levelfile(savelev, errbuf);
3236     if (lfd < 0) {
3237         raw_printf("\n%s\n", errbuf);
3238         (void) nhclose(gfd);
3239         (void) nhclose(sfd);
3240         delete_savefile();
3241         return FALSE;
3242     }
3243
3244     if (write(sfd, (genericptr_t) &version_data, sizeof version_data)
3245         != sizeof version_data) {
3246         raw_printf("\nError writing %s; recovery failed.", SAVEF);
3247         (void) nhclose(gfd);
3248         (void) nhclose(sfd);
3249         delete_savefile();
3250         return FALSE;
3251     }
3252
3253     if (write(sfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi) {
3254         raw_printf("\nError writing %s; recovery failed (savefile_info).\n",
3255                    SAVEF);
3256         (void) nhclose(gfd);
3257         (void) nhclose(sfd);
3258         delete_savefile();
3259         return FALSE;
3260     }
3261
3262     if (write(sfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3263         != sizeof pltmpsiz) {
3264         raw_printf("Error writing %s; recovery failed (player name size).\n",
3265                    SAVEF);
3266         (void) nhclose(gfd);
3267         (void) nhclose(sfd);
3268         delete_savefile();
3269         return FALSE;
3270     }
3271
3272     if (write(sfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz) {
3273         raw_printf("Error writing %s; recovery failed (player name).\n",
3274                    SAVEF);
3275         (void) nhclose(gfd);
3276         (void) nhclose(sfd);
3277         delete_savefile();
3278         return FALSE;
3279     }
3280
3281     if (!copy_bytes(lfd, sfd)) {
3282         (void) nhclose(lfd);
3283         (void) nhclose(sfd);
3284         delete_savefile();
3285         return FALSE;
3286     }
3287     (void) nhclose(lfd);
3288     processed[savelev] = 1;
3289
3290     if (!copy_bytes(gfd, sfd)) {
3291         (void) nhclose(lfd);
3292         (void) nhclose(sfd);
3293         delete_savefile();
3294         return FALSE;
3295     }
3296     (void) nhclose(gfd);
3297     processed[0] = 1;
3298
3299     for (lev = 1; lev < 256; lev++) {
3300         /* level numbers are kept in xchars in save.c, so the
3301          * maximum level number (for the endlevel) must be < 256
3302          */
3303         if (lev != savelev) {
3304             lfd = open_levelfile(lev, (char *) 0);
3305             if (lfd >= 0) {
3306                 /* any or all of these may not exist */
3307                 levc = (xchar) lev;
3308                 write(sfd, (genericptr_t) &levc, sizeof(levc));
3309                 if (!copy_bytes(lfd, sfd)) {
3310                     (void) nhclose(lfd);
3311                     (void) nhclose(sfd);
3312                     delete_savefile();
3313                     return FALSE;
3314                 }
3315                 (void) nhclose(lfd);
3316                 processed[lev] = 1;
3317             }
3318         }
3319     }
3320     (void) nhclose(sfd);
3321
3322 #ifdef HOLD_LOCKFILE_OPEN
3323     really_close();
3324 #endif
3325     /*
3326      * We have a successful savefile!
3327      * Only now do we erase the level files.
3328      */
3329     for (lev = 0; lev < 256; lev++) {
3330         if (processed[lev]) {
3331             const char *fq_lock;
3332             set_levelfile_name(lock, lev);
3333             fq_lock = fqname(lock, LEVELPREFIX, 3);
3334             (void) unlink(fq_lock);
3335         }
3336     }
3337     return TRUE;
3338 }
3339
3340 boolean
3341 copy_bytes(ifd, ofd)
3342 int ifd, ofd;
3343 {
3344     char buf[BUFSIZ];
3345     int nfrom, nto;
3346
3347     do {
3348         nfrom = read(ifd, buf, BUFSIZ);
3349         nto = write(ofd, buf, nfrom);
3350         if (nto != nfrom)
3351             return FALSE;
3352     } while (nfrom == BUFSIZ);
3353     return TRUE;
3354 }
3355
3356 /* ----------  END INTERNAL RECOVER ----------- */
3357 #endif /*SELF_RECOVER*/
3358
3359 /* ----------  OTHER ----------- */
3360
3361 #ifdef SYSCF
3362 #ifdef SYSCF_FILE
3363 void
3364 assure_syscf_file()
3365 {
3366     int fd;
3367
3368     /*
3369      * All we really care about is the end result - can we read the file?
3370      * So just check that directly.
3371      *
3372      * Not tested on most of the old platforms (which don't attempt
3373      * to implement SYSCF).
3374      * Some ports don't like open()'s optional third argument;
3375      * VMS overrides open() usage with a macro which requires it.
3376      */
3377 #ifndef VMS
3378     fd = open(SYSCF_FILE, O_RDONLY);
3379 #else
3380     fd = open(SYSCF_FILE, O_RDONLY, 0);
3381 #endif
3382     if (fd >= 0) {
3383         /* readable */
3384         close(fd);
3385         return;
3386     }
3387     raw_printf("Unable to open SYSCF_FILE.\n");
3388     exit(EXIT_FAILURE);
3389 }
3390
3391 #endif /* SYSCF_FILE */
3392 #endif /* SYSCF */
3393
3394 #ifdef DEBUG
3395 /* used by debugpline() to decide whether to issue a message
3396  * from a particular source file; caller passes __FILE__ and we check
3397  * whether it is in the source file list supplied by SYSCF's DEBUGFILES
3398  *
3399  * pass FALSE to override wildcard matching; useful for files
3400  * like dungeon.c and questpgr.c, which generate a ridiculous amount of
3401  * output if DEBUG is defined and effectively block the use of a wildcard */
3402 boolean
3403 debugcore(filename, wildcards)
3404 const char *filename;
3405 boolean wildcards;
3406 {
3407     const char *debugfiles, *p;
3408
3409     if (!filename || !*filename)
3410         return FALSE; /* sanity precaution */
3411
3412     if (sysopt.env_dbgfl == 0) {
3413         /* check once for DEBUGFILES in the environment;
3414            if found, it supersedes the sysconf value
3415            [note: getenv() rather than nh_getenv() since a long value
3416            is valid and doesn't pose any sort of overflow risk here] */
3417         if ((p = getenv("DEBUGFILES")) != 0) {
3418             if (sysopt.debugfiles)
3419                 free((genericptr_t) sysopt.debugfiles);
3420             sysopt.debugfiles = dupstr(p);
3421             sysopt.env_dbgfl = 1;
3422         } else
3423             sysopt.env_dbgfl = -1;
3424     }
3425
3426     debugfiles = sysopt.debugfiles;
3427     /* usual case: sysopt.debugfiles will be empty */
3428     if (!debugfiles || !*debugfiles)
3429         return FALSE;
3430
3431 /* strip filename's path if present */
3432 #ifdef UNIX
3433     if ((p = rindex(filename, '/')) != 0)
3434         filename = p + 1;
3435 #endif
3436 #ifdef VMS
3437     filename = vms_basename(filename);
3438     /* vms_basename strips off 'type' suffix as well as path and version;
3439        we want to put suffix back (".c" assumed); since it always returns
3440        a pointer to a static buffer, we can safely modify its result */
3441     Strcat((char *) filename, ".c");
3442 #endif
3443
3444     /*
3445      * Wildcard match will only work if there's a single pattern (which
3446      * might be a single file name without any wildcarding) rather than
3447      * a space-separated list.
3448      * [to NOT do: We could step through the space-separated list and
3449      * attempt a wildcard match against each element, but that would be
3450      * overkill for the intended usage.]
3451      */
3452     if (wildcards && pmatch(debugfiles, filename))
3453         return TRUE;
3454
3455     /* check whether filename is an element of the list */
3456     if ((p = strstr(debugfiles, filename)) != 0) {
3457         int l = (int) strlen(filename);
3458
3459         if ((p == debugfiles || p[-1] == ' ' || p[-1] == '/')
3460             && (p[l] == ' ' || p[l] == '\0'))
3461             return TRUE;
3462     }
3463     return FALSE;
3464 }
3465
3466 #endif /*DEBUG*/
3467
3468 /* ----------  BEGIN TRIBUTE ----------- */
3469
3470 /* 3.6 tribute code
3471  */
3472
3473 #define SECTIONSCOPE 1
3474 #define TITLESCOPE 2
3475 #define PASSAGESCOPE 3
3476
3477 #define MAXPASSAGES SIZE(context.novel.pasg) /* 30 */
3478
3479 static int FDECL(choose_passage, (int, unsigned));
3480
3481 /* choose a random passage that hasn't been chosen yet; once all have
3482    been chosen, reset the tracking to make all passages available again */
3483 static int
3484 choose_passage(passagecnt, oid)
3485 int passagecnt; /* total of available passages, 1..MAXPASSAGES */
3486 unsigned oid; /* book.o_id, used to determine whether re-reading same book */
3487 {
3488     int idx, res;
3489
3490     if (passagecnt < 1)
3491         return 0;
3492     if (passagecnt > MAXPASSAGES)
3493         passagecnt = MAXPASSAGES;
3494
3495     /* if a different book or we've used up all the passages already,
3496        reset in order to have all 'passagecnt' passages available */
3497     if (oid != context.novel.id || context.novel.count == 0) {
3498         context.novel.id = oid;
3499         context.novel.count = passagecnt;
3500         for (idx = 0; idx < MAXPASSAGES; idx++)
3501             context.novel.pasg[idx] = (xchar) ((idx < passagecnt) ? idx + 1
3502                                                                   : 0);
3503     }
3504
3505     idx = rn2(context.novel.count);
3506     res = (int) context.novel.pasg[idx];
3507     /* move the last slot's passage index into the slot just used
3508        and reduce the number of passages available */
3509     context.novel.pasg[idx] = context.novel.pasg[--context.novel.count];
3510     return res;
3511 }
3512
3513 /* Returns True if you were able to read something. */
3514 boolean
3515 read_tribute(tribsection, tribtitle, tribpassage, nowin_buf, bufsz, oid)
3516 const char *tribsection, *tribtitle;
3517 int tribpassage, bufsz;
3518 char *nowin_buf;
3519 unsigned oid; /* book identifier */
3520 {
3521     dlb *fp;
3522     char *endp;
3523     char line[BUFSZ], lastline[BUFSZ];
3524
3525     int scope = 0;
3526     int linect = 0, passagecnt = 0, targetpassage = 0;
3527     const char *badtranslation = "an incomprehensible foreign translation";
3528     boolean matchedsection = FALSE, matchedtitle = FALSE;
3529     winid tribwin = WIN_ERR;
3530     boolean grasped = FALSE;
3531     boolean foundpassage = FALSE;
3532
3533     /* check for mandatories */
3534     if (!tribsection || !tribtitle) {
3535         if (!nowin_buf)
3536             pline("It's %s of \"%s\"!", badtranslation, tribtitle);
3537         return grasped;
3538     }
3539
3540     debugpline3("read_tribute %s, %s, %d.", tribsection, tribtitle,
3541                 tribpassage);
3542
3543     fp = dlb_fopen(TRIBUTEFILE, "r");
3544     if (!fp) {
3545         /* this is actually an error - cannot open tribute file! */
3546         if (!nowin_buf)
3547             pline("You feel too overwhelmed to continue!");
3548         return grasped;
3549     }
3550
3551     /*
3552      * Syntax (not case-sensitive):
3553      *  %section books
3554      *
3555      * In the books section:
3556      *    %title booktitle (n)
3557      *          where booktitle=book title without quotes
3558      *          (n)= total number of passages present for this title
3559      *    %passage k
3560      *          where k=sequential passage number
3561      *
3562      * %e ends the passage/book/section
3563      *    If in a passage, it marks the end of that passage.
3564      *    If in a book, it marks the end of that book.
3565      *    If in a section, it marks the end of that section.
3566      *
3567      *  %section death
3568      */
3569
3570     *line = *lastline = '\0';
3571     while (dlb_fgets(line, sizeof line, fp) != 0) {
3572         linect++;
3573         if ((endp = index(line, '\n')) != 0)
3574             *endp = 0;
3575         switch (line[0]) {
3576         case '%':
3577             if (!strncmpi(&line[1], "section ", sizeof("section ") - 1)) {
3578                 char *st = &line[9]; /* 9 from "%section " */
3579
3580                 scope = SECTIONSCOPE;
3581                 if (!strcmpi(st, tribsection))
3582                     matchedsection = TRUE;
3583                 else
3584                     matchedsection = FALSE;
3585             } else if (!strncmpi(&line[1], "title ", sizeof("title ") - 1)) {
3586                 char *st = &line[7]; /* 7 from "%title " */
3587                 char *p1, *p2;
3588
3589                 if ((p1 = index(st, '(')) != 0) {
3590                     *p1++ = '\0';
3591                     (void) mungspaces(st);
3592                     if ((p2 = index(p1, ')')) != 0) {
3593                         *p2 = '\0';
3594                         passagecnt = atoi(p1);
3595                         if (passagecnt > MAXPASSAGES)
3596                             passagecnt = MAXPASSAGES;
3597                         scope = TITLESCOPE;
3598                         if (matchedsection && !strcmpi(st, tribtitle)) {
3599                             matchedtitle = TRUE;
3600                             targetpassage = !tribpassage
3601                                              ? choose_passage(passagecnt, oid)
3602                                              : (tribpassage <= passagecnt)
3603                                                 ? tribpassage : 0;
3604                         } else {
3605                             matchedtitle = FALSE;
3606                         }
3607                     }
3608                 }
3609             } else if (!strncmpi(&line[1], "passage ",
3610                                  sizeof("passage ") - 1)) {
3611                 int passagenum = 0;
3612                 char *st = &line[9]; /* 9 from "%passage " */
3613
3614                 while (*st == ' ' || *st == '\t')
3615                     st++;
3616                 if (*st && digit(*st) && (strlen(st) < 3))
3617                     passagenum = atoi(st);
3618                 if (passagenum && (passagenum <= passagecnt)) {
3619                     scope = PASSAGESCOPE;
3620                     if (matchedtitle && (passagenum == targetpassage)) {
3621                         if (!nowin_buf)
3622                             tribwin = create_nhwindow(NHW_MENU);
3623                         else
3624                             foundpassage = TRUE;
3625                     }
3626                 }
3627             } else if (!strncmpi(&line[1], "e ", sizeof("e ") - 1)) {
3628                 if (matchedtitle && scope == PASSAGESCOPE
3629                     && ((!nowin_buf && tribwin != WIN_ERR)
3630                         || (nowin_buf && foundpassage)))
3631                     goto cleanup;
3632                 if (scope == TITLESCOPE)
3633                     matchedtitle = FALSE;
3634                 if (scope == SECTIONSCOPE)
3635                     matchedsection = FALSE;
3636                 if (scope)
3637                     --scope;
3638             } else {
3639                 debugpline1("tribute file error: bad %% command, line %d.",
3640                             linect);
3641             }
3642             break;
3643         case '#':
3644             /* comment only, next! */
3645             break;
3646         default:
3647             if (matchedtitle && scope == PASSAGESCOPE) {
3648                 if (!nowin_buf && tribwin != WIN_ERR) {
3649                     putstr(tribwin, 0, line);
3650                     Strcpy(lastline, line);
3651                 } else if (nowin_buf) {
3652                     if ((int) strlen(line) < bufsz - 1)
3653                         Strcpy(nowin_buf, line);
3654                 }
3655             }
3656         }
3657     }
3658
3659 cleanup:
3660     (void) dlb_fclose(fp);
3661     if (!nowin_buf && tribwin != WIN_ERR) {
3662         if (matchedtitle && scope == PASSAGESCOPE) {
3663             display_nhwindow(tribwin, FALSE);
3664             /* put the final attribution line into message history,
3665                analogous to the summary line from long quest messages */
3666             if (index(lastline, '['))
3667                 mungspaces(lastline); /* to remove leading spaces */
3668             else /* construct one if necessary */
3669                 Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle);
3670             putmsghistory(lastline, FALSE);
3671         }
3672         destroy_nhwindow(tribwin);
3673         tribwin = WIN_ERR;
3674         grasped = TRUE;
3675     } else {
3676         if (!nowin_buf)
3677             pline("It seems to be %s of \"%s\"!", badtranslation, tribtitle);
3678         else
3679             if (foundpassage)
3680                 grasped = TRUE;
3681     }
3682     return grasped;
3683 }
3684
3685 boolean
3686 Death_quote(buf, bufsz)
3687 char *buf;
3688 int bufsz;
3689 {
3690     unsigned death_oid = 1; /* chance of oid #1 being a novel is negligible */
3691
3692     return read_tribute("Death", "Death Quotes", 0, buf, bufsz, death_oid);
3693 }
3694
3695 /* ----------  END TRIBUTE ----------- */
3696
3697 /*files.c*/