OSDN Git Service

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