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