OSDN Git Service

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