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