OSDN Git Service

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