Also pass raw printed texts to the msghandler
[aNetHack.git] / src / files.c
blob96d602fc12567b542c164526cbee167125ce23bf
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, "MSGTYPE", 7)) {
2156 (void) msgtype_parse_add(bufp);
2157 #ifdef NOCWD_ASSUMPTIONS
2158 } else if (match_varname(buf, "HACKDIR", 4)) {
2159 adjust_prefix(bufp, HACKPREFIX);
2160 } else if (match_varname(buf, "LEVELDIR", 4)
2161 || match_varname(buf, "LEVELS", 4)) {
2162 adjust_prefix(bufp, LEVELPREFIX);
2163 } else if (match_varname(buf, "SAVEDIR", 4)) {
2164 adjust_prefix(bufp, SAVEPREFIX);
2165 } else if (match_varname(buf, "BONESDIR", 5)) {
2166 adjust_prefix(bufp, BONESPREFIX);
2167 } else if (match_varname(buf, "DATADIR", 4)) {
2168 adjust_prefix(bufp, DATAPREFIX);
2169 } else if (match_varname(buf, "SCOREDIR", 4)) {
2170 adjust_prefix(bufp, SCOREPREFIX);
2171 } else if (match_varname(buf, "LOCKDIR", 4)) {
2172 adjust_prefix(bufp, LOCKPREFIX);
2173 } else if (match_varname(buf, "CONFIGDIR", 4)) {
2174 adjust_prefix(bufp, CONFIGPREFIX);
2175 } else if (match_varname(buf, "TROUBLEDIR", 4)) {
2176 adjust_prefix(bufp, TROUBLEPREFIX);
2177 #else /*NOCWD_ASSUMPTIONS*/
2178 #ifdef MICRO
2179 } else if (match_varname(buf, "HACKDIR", 4)) {
2180 (void) strncpy(hackdir, bufp, PATHLEN - 1);
2181 #ifdef MFLOPPY
2182 } else if (match_varname(buf, "RAMDISK", 3)) {
2183 /* The following ifdef is NOT in the wrong
2184 * place. For now, we accept and silently
2185 * ignore RAMDISK */
2186 #ifndef AMIGA
2187 if (strlen(bufp) >= PATHLEN)
2188 bufp[PATHLEN - 1] = '\0';
2189 Strcpy(levels, bufp);
2190 ramdisk = (strcmp(permbones, levels) != 0);
2191 ramdisk_specified = TRUE;
2192 #endif
2193 #endif
2194 } else if (match_varname(buf, "LEVELS", 4)) {
2195 if (strlen(bufp) >= PATHLEN)
2196 bufp[PATHLEN - 1] = '\0';
2197 Strcpy(permbones, bufp);
2198 if (!ramdisk_specified || !*levels)
2199 Strcpy(levels, bufp);
2200 ramdisk = (strcmp(permbones, levels) != 0);
2201 } else if (match_varname(buf, "SAVE", 4)) {
2202 #ifdef MFLOPPY
2203 extern int saveprompt;
2204 #endif
2205 char *ptr;
2207 if ((ptr = index(bufp, ';')) != 0) {
2208 *ptr = '\0';
2209 #ifdef MFLOPPY
2210 if (*(ptr + 1) == 'n' || *(ptr + 1) == 'N') {
2211 saveprompt = FALSE;
2213 #endif
2215 #if defined(SYSFLAGS) && defined(MFLOPPY)
2216 else
2217 saveprompt = sysflags.asksavedisk;
2218 #endif
2220 (void) strncpy(SAVEP, bufp, SAVESIZE - 1);
2221 append_slash(SAVEP);
2222 #endif /* MICRO */
2223 #endif /*NOCWD_ASSUMPTIONS*/
2225 } else if (match_varname(buf, "NAME", 4)) {
2226 (void) strncpy(plname, bufp, PL_NSIZ - 1);
2227 } else if (match_varname(buf, "ROLE", 4)
2228 || match_varname(buf, "CHARACTER", 4)) {
2229 if ((len = str2role(bufp)) >= 0)
2230 flags.initrole = len;
2231 } else if (match_varname(buf, "DOGNAME", 3)) {
2232 (void) strncpy(dogname, bufp, PL_PSIZ - 1);
2233 } else if (match_varname(buf, "CATNAME", 3)) {
2234 (void) strncpy(catname, bufp, PL_PSIZ - 1);
2236 #ifdef SYSCF
2237 } else if (src == SET_IN_SYS && match_varname(buf, "WIZARDS", 7)) {
2238 if (sysopt.wizards)
2239 free((genericptr_t) sysopt.wizards);
2240 sysopt.wizards = dupstr(bufp);
2241 if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) {
2242 /* pre-format WIZARDS list now; it's displayed during a panic
2243 and since that panic might be due to running out of memory,
2244 we don't want to risk attempting to allocate any memory then */
2245 if (sysopt.fmtd_wizard_list)
2246 free((genericptr_t) sysopt.fmtd_wizard_list);
2247 sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards);
2249 } else if (src == SET_IN_SYS && match_varname(buf, "SHELLERS", 8)) {
2250 if (sysopt.shellers)
2251 free((genericptr_t) sysopt.shellers);
2252 sysopt.shellers = dupstr(bufp);
2253 } else if (src == SET_IN_SYS && match_varname(buf, "EXPLORERS", 7)) {
2254 if (sysopt.explorers)
2255 free((genericptr_t) sysopt.explorers);
2256 sysopt.explorers = dupstr(bufp);
2257 } else if (src == SET_IN_SYS && match_varname(buf, "DEBUGFILES", 5)) {
2258 /* if showdebug() has already been called (perhaps we've added
2259 some debugpline() calls to option processing) and has found
2260 a value for getenv("DEBUGFILES"), don't override that */
2261 if (sysopt.env_dbgfl <= 0) {
2262 if (sysopt.debugfiles)
2263 free((genericptr_t) sysopt.debugfiles);
2264 sysopt.debugfiles = dupstr(bufp);
2266 } else if (src == SET_IN_SYS && match_varname(buf, "GENERICUSERS", 12)) {
2267 if (sysopt.genericusers) free(sysopt.genericusers);
2268 sysopt.genericusers = dupstr(bufp);
2269 } else if (src == SET_IN_SYS && match_varname(buf, "SUPPORT", 7)) {
2270 if (sysopt.support)
2271 free((genericptr_t) sysopt.support);
2272 sysopt.support = dupstr(bufp);
2273 } else if (src == SET_IN_SYS && match_varname(buf, "RECOVER", 7)) {
2274 if (sysopt.recover)
2275 free((genericptr_t) sysopt.recover);
2276 sysopt.recover = dupstr(bufp);
2277 } else if (src == SET_IN_SYS
2278 && match_varname(buf, "CHECK_SAVE_UID", 14)) {
2279 n = atoi(bufp);
2280 sysopt.check_save_uid = n;
2281 } else if (src == SET_IN_SYS
2282 && match_varname(buf, "CHECK_PLNAME", 12)) {
2283 n = atoi(bufp);
2284 sysopt.check_plname = n;
2285 } else if (match_varname(buf, "SEDUCE", 6)) {
2286 n = !!atoi(bufp); /* XXX this could be tighter */
2287 /* allow anyone to turn it off, but only sysconf to turn it on*/
2288 if (src != SET_IN_SYS && n != 0) {
2289 raw_printf("Illegal value in SEDUCE");
2290 return 0;
2292 sysopt.seduce = n;
2293 sysopt_seduce_set(sysopt.seduce);
2294 } else if (src == SET_IN_SYS && match_varname(buf, "MAXPLAYERS", 10)) {
2295 n = atoi(bufp);
2296 /* XXX to get more than 25, need to rewrite all lock code */
2297 if (n < 0 || n > 25) {
2298 raw_printf("Illegal value in MAXPLAYERS (maximum is 25).");
2299 return 0;
2301 sysopt.maxplayers = n;
2302 } else if (src == SET_IN_SYS && match_varname(buf, "PERSMAX", 7)) {
2303 n = atoi(bufp);
2304 if (n < 1) {
2305 raw_printf("Illegal value in PERSMAX (minimum is 1).");
2306 return 0;
2308 sysopt.persmax = n;
2309 } else if (src == SET_IN_SYS && match_varname(buf, "PERS_IS_UID", 11)) {
2310 n = atoi(bufp);
2311 if (n != 0 && n != 1) {
2312 raw_printf("Illegal value in PERS_IS_UID (must be 0 or 1).");
2313 return 0;
2315 sysopt.pers_is_uid = n;
2316 } else if (src == SET_IN_SYS && match_varname(buf, "ENTRYMAX", 8)) {
2317 n = atoi(bufp);
2318 if (n < 10) {
2319 raw_printf("Illegal value in ENTRYMAX (minimum is 10).");
2320 return 0;
2322 sysopt.entrymax = n;
2323 } else if ((src == SET_IN_SYS) && match_varname(buf, "POINTSMIN", 9)) {
2324 n = atoi(bufp);
2325 if (n < 1) {
2326 raw_printf("Illegal value in POINTSMIN (minimum is 1).");
2327 return 0;
2329 sysopt.pointsmin = n;
2330 } else if (src == SET_IN_SYS
2331 && match_varname(buf, "MAX_STATUENAME_RANK", 10)) {
2332 n = atoi(bufp);
2333 if (n < 1) {
2334 raw_printf(
2335 "Illegal value in MAX_STATUENAME_RANK (minimum is 1).");
2336 return 0;
2338 sysopt.tt_oname_maxrank = n;
2340 /* SYSCF PANICTRACE options */
2341 } else if (src == SET_IN_SYS
2342 && match_varname(buf, "PANICTRACE_LIBC", 15)) {
2343 n = atoi(bufp);
2344 #if defined(PANICTRACE) && defined(PANICTRACE_LIBC)
2345 if (n < 0 || n > 2) {
2346 raw_printf("Illegal value in PANICTRACE_LIBC (not 0,1,2).");
2347 return 0;
2349 #endif
2350 sysopt.panictrace_libc = n;
2351 } else if (src == SET_IN_SYS
2352 && match_varname(buf, "PANICTRACE_GDB", 14)) {
2353 n = atoi(bufp);
2354 #if defined(PANICTRACE)
2355 if (n < 0 || n > 2) {
2356 raw_printf("Illegal value in PANICTRACE_GDB (not 0,1,2).");
2357 return 0;
2359 #endif
2360 sysopt.panictrace_gdb = n;
2361 } else if (src == SET_IN_SYS && match_varname(buf, "GDBPATH", 7)) {
2362 #if defined(PANICTRACE) && !defined(VMS)
2363 if (!file_exists(bufp)) {
2364 raw_printf("File specified in GDBPATH does not exist.");
2365 return 0;
2367 #endif
2368 if (sysopt.gdbpath)
2369 free((genericptr_t) sysopt.gdbpath);
2370 sysopt.gdbpath = dupstr(bufp);
2371 } else if (src == SET_IN_SYS && match_varname(buf, "GREPPATH", 7)) {
2372 #if defined(PANICTRACE) && !defined(VMS)
2373 if (!file_exists(bufp)) {
2374 raw_printf("File specified in GREPPATH does not exist.");
2375 return 0;
2377 #endif
2378 if (sysopt.greppath)
2379 free((genericptr_t) sysopt.greppath);
2380 sysopt.greppath = dupstr(bufp);
2381 #endif /* SYSCF */
2383 } else if (match_varname(buf, "BOULDER", 3)) {
2384 (void) get_uchars(fp, buf, bufp, &iflags.bouldersym, TRUE, 1,
2385 "BOULDER");
2386 } else if (match_varname(buf, "MENUCOLOR", 9)) {
2387 (void) add_menu_coloring(bufp);
2388 } else if (match_varname(buf, "WARNINGS", 5)) {
2389 (void) get_uchars(fp, buf, bufp, translate, FALSE, WARNCOUNT,
2390 "WARNINGS");
2391 assign_warnings(translate);
2392 } else if (match_varname(buf, "SYMBOLS", 4)) {
2393 char *op, symbuf[BUFSZ];
2394 boolean morelines;
2396 do {
2397 /* check for line continuation (trailing '\') */
2398 op = eos(bufp);
2399 morelines = (--op >= bufp && *op == '\\');
2400 if (morelines) {
2401 *op = '\0';
2402 /* strip trailing space now that '\' is gone */
2403 if (--op >= bufp && *op == ' ')
2404 *op = '\0';
2406 /* parse here */
2407 if (!parsesymbols(bufp)) {
2408 raw_printf("Error in SYMBOLS definition '%s'.\n", bufp);
2409 wait_synch();
2411 if (morelines) {
2412 do {
2413 *symbuf = '\0';
2414 if (!fgets(symbuf, BUFSZ, fp)) {
2415 morelines = FALSE;
2416 break;
2418 mungspaces(symbuf);
2419 bufp = symbuf;
2420 } while (*bufp == '#');
2422 } while (morelines);
2423 switch_symbols(TRUE);
2424 } else if (match_varname(buf, "WIZKIT", 6)) {
2425 (void) strncpy(wizkit, bufp, WIZKIT_MAX - 1);
2426 #ifdef AMIGA
2427 } else if (match_varname(buf, "FONT", 4)) {
2428 char *t;
2430 if (t = strchr(buf + 5, ':')) {
2431 *t = 0;
2432 amii_set_text_font(buf + 5, atoi(t + 1));
2433 *t = ':';
2435 } else if (match_varname(buf, "PATH", 4)) {
2436 (void) strncpy(PATH, bufp, PATHLEN - 1);
2437 } else if (match_varname(buf, "DEPTH", 5)) {
2438 extern int amii_numcolors;
2439 int val = atoi(bufp);
2441 amii_numcolors = 1L << min(DEPTH, val);
2442 #ifdef SYSFLAGS
2443 } else if (match_varname(buf, "DRIPENS", 7)) {
2444 int i, val;
2445 char *t;
2447 for (i = 0, t = strtok(bufp, ",/"); t != (char *) 0;
2448 i < 20 && (t = strtok((char *) 0, ",/")), ++i) {
2449 sscanf(t, "%d", &val);
2450 sysflags.amii_dripens[i] = val;
2452 #endif
2453 } else if (match_varname(buf, "SCREENMODE", 10)) {
2454 extern long amii_scrnmode;
2456 if (!stricmp(bufp, "req"))
2457 amii_scrnmode = 0xffffffff; /* Requester */
2458 else if (sscanf(bufp, "%x", &amii_scrnmode) != 1)
2459 amii_scrnmode = 0;
2460 } else if (match_varname(buf, "MSGPENS", 7)) {
2461 extern int amii_msgAPen, amii_msgBPen;
2462 char *t = strtok(bufp, ",/");
2464 if (t) {
2465 sscanf(t, "%d", &amii_msgAPen);
2466 if (t = strtok((char *) 0, ",/"))
2467 sscanf(t, "%d", &amii_msgBPen);
2469 } else if (match_varname(buf, "TEXTPENS", 8)) {
2470 extern int amii_textAPen, amii_textBPen;
2471 char *t = strtok(bufp, ",/");
2473 if (t) {
2474 sscanf(t, "%d", &amii_textAPen);
2475 if (t = strtok((char *) 0, ",/"))
2476 sscanf(t, "%d", &amii_textBPen);
2478 } else if (match_varname(buf, "MENUPENS", 8)) {
2479 extern int amii_menuAPen, amii_menuBPen;
2480 char *t = strtok(bufp, ",/");
2482 if (t) {
2483 sscanf(t, "%d", &amii_menuAPen);
2484 if (t = strtok((char *) 0, ",/"))
2485 sscanf(t, "%d", &amii_menuBPen);
2487 } else if (match_varname(buf, "STATUSPENS", 10)) {
2488 extern int amii_statAPen, amii_statBPen;
2489 char *t = strtok(bufp, ",/");
2491 if (t) {
2492 sscanf(t, "%d", &amii_statAPen);
2493 if (t = strtok((char *) 0, ",/"))
2494 sscanf(t, "%d", &amii_statBPen);
2496 } else if (match_varname(buf, "OTHERPENS", 9)) {
2497 extern int amii_otherAPen, amii_otherBPen;
2498 char *t = strtok(bufp, ",/");
2500 if (t) {
2501 sscanf(t, "%d", &amii_otherAPen);
2502 if (t = strtok((char *) 0, ",/"))
2503 sscanf(t, "%d", &amii_otherBPen);
2505 } else if (match_varname(buf, "PENS", 4)) {
2506 extern unsigned short amii_init_map[AMII_MAXCOLORS];
2507 int i;
2508 char *t;
2510 for (i = 0, t = strtok(bufp, ",/");
2511 i < AMII_MAXCOLORS && t != (char *) 0;
2512 t = strtok((char *) 0, ",/"), ++i) {
2513 sscanf(t, "%hx", &amii_init_map[i]);
2515 amii_setpens(amii_numcolors = i);
2516 } else if (match_varname(buf, "FGPENS", 6)) {
2517 extern int foreg[AMII_MAXCOLORS];
2518 int i;
2519 char *t;
2521 for (i = 0, t = strtok(bufp, ",/");
2522 i < AMII_MAXCOLORS && t != (char *) 0;
2523 t = strtok((char *) 0, ",/"), ++i) {
2524 sscanf(t, "%d", &foreg[i]);
2526 } else if (match_varname(buf, "BGPENS", 6)) {
2527 extern int backg[AMII_MAXCOLORS];
2528 int i;
2529 char *t;
2531 for (i = 0, t = strtok(bufp, ",/");
2532 i < AMII_MAXCOLORS && t != (char *) 0;
2533 t = strtok((char *) 0, ",/"), ++i) {
2534 sscanf(t, "%d", &backg[i]);
2536 #endif /*AMIGA*/
2537 #ifdef USER_SOUNDS
2538 } else if (match_varname(buf, "SOUNDDIR", 8)) {
2539 sounddir = dupstr(bufp);
2540 } else if (match_varname(buf, "SOUND", 5)) {
2541 add_sound_mapping(bufp);
2542 #endif
2543 #ifdef QT_GRAPHICS
2544 /* These should move to wc_ options */
2545 } else if (match_varname(buf, "QT_TILEWIDTH", 12)) {
2546 extern char *qt_tilewidth;
2548 if (qt_tilewidth == NULL)
2549 qt_tilewidth = dupstr(bufp);
2550 } else if (match_varname(buf, "QT_TILEHEIGHT", 13)) {
2551 extern char *qt_tileheight;
2553 if (qt_tileheight == NULL)
2554 qt_tileheight = dupstr(bufp);
2555 } else if (match_varname(buf, "QT_FONTSIZE", 11)) {
2556 extern char *qt_fontsize;
2558 if (qt_fontsize == NULL)
2559 qt_fontsize = dupstr(bufp);
2560 } else if (match_varname(buf, "QT_COMPACT", 10)) {
2561 extern int qt_compact_mode;
2563 qt_compact_mode = atoi(bufp);
2564 #endif
2565 } else
2566 return 0;
2567 return 1;
2570 #ifdef USER_SOUNDS
2571 boolean
2572 can_read_file(filename)
2573 const char *filename;
2575 return (boolean) (access(filename, 4) == 0);
2577 #endif /* USER_SOUNDS */
2579 boolean
2580 read_config_file(filename, src)
2581 const char *filename;
2582 int src;
2584 char buf[4 * BUFSZ];
2585 FILE *fp;
2586 boolean rv = TRUE; /* assume successful parse */
2588 if (!(fp = fopen_config_file(filename, src)))
2589 return FALSE;
2591 /* begin detection of duplicate configfile options */
2592 set_duplicate_opt_detection(1);
2594 while (fgets(buf, sizeof buf, fp)) {
2595 #ifdef notyet
2597 XXX Don't call read() in parse_config_line, read as callback or reassemble
2598 line at this level.
2599 OR: Forbid multiline stuff for alternate config sources.
2601 #endif
2602 if (!parse_config_line(fp, strip_newline(buf), src)) {
2603 static const char badoptionline[] = "Bad option line: \"%s\"";
2605 /* truncate buffer if it's long; this is actually conservative */
2606 if (strlen(buf) > BUFSZ - sizeof badoptionline)
2607 buf[BUFSZ - sizeof badoptionline] = '\0';
2609 raw_printf(badoptionline, buf);
2610 wait_synch();
2611 rv = FALSE;
2614 (void) fclose(fp);
2616 /* turn off detection of duplicate configfile options */
2617 set_duplicate_opt_detection(0);
2618 return rv;
2621 STATIC_OVL FILE *
2622 fopen_wizkit_file()
2624 FILE *fp;
2625 #if defined(VMS) || defined(UNIX)
2626 char tmp_wizkit[BUFSZ];
2627 #endif
2628 char *envp;
2630 envp = nh_getenv("WIZKIT");
2631 if (envp && *envp)
2632 (void) strncpy(wizkit, envp, WIZKIT_MAX - 1);
2633 if (!wizkit[0])
2634 return (FILE *) 0;
2636 #ifdef UNIX
2637 if (access(wizkit, 4) == -1) {
2638 /* 4 is R_OK on newer systems */
2639 /* nasty sneaky attempt to read file through
2640 * NetHack's setuid permissions -- this is a
2641 * place a file name may be wholly under the player's
2642 * control
2644 raw_printf("Access to %s denied (%d).", wizkit, errno);
2645 wait_synch();
2646 /* fall through to standard names */
2647 } else
2648 #endif
2649 if ((fp = fopenp(wizkit, "r")) != (FILE *) 0) {
2650 return fp;
2651 #if defined(UNIX) || defined(VMS)
2652 } else {
2653 /* access() above probably caught most problems for UNIX */
2654 raw_printf("Couldn't open requested config file %s (%d).", wizkit,
2655 errno);
2656 wait_synch();
2657 #endif
2660 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
2661 if ((fp = fopenp(fqname(wizkit, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
2662 return fp;
2663 #else
2664 #ifdef VMS
2665 envp = nh_getenv("HOME");
2666 if (envp)
2667 Sprintf(tmp_wizkit, "%s%s", envp, wizkit);
2668 else
2669 Sprintf(tmp_wizkit, "%s%s", "sys$login:", wizkit);
2670 if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2671 return fp;
2672 #else /* should be only UNIX left */
2673 envp = nh_getenv("HOME");
2674 if (envp)
2675 Sprintf(tmp_wizkit, "%s/%s", envp, wizkit);
2676 else
2677 Strcpy(tmp_wizkit, wizkit);
2678 if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2679 return fp;
2680 else if (errno != ENOENT) {
2681 /* e.g., problems when setuid NetHack can't search home
2682 * directory restricted to user */
2683 raw_printf("Couldn't open default wizkit file %s (%d).", tmp_wizkit,
2684 errno);
2685 wait_synch();
2687 #endif
2688 #endif
2689 return (FILE *) 0;
2692 /* add to hero's inventory if there's room, otherwise put item on floor */
2693 STATIC_DCL void
2694 wizkit_addinv(obj)
2695 struct obj *obj;
2697 if (!obj || obj == &zeroobj)
2698 return;
2700 /* subset of starting inventory pre-ID */
2701 obj->dknown = 1;
2702 if (Role_if(PM_PRIEST))
2703 obj->bknown = 1;
2704 /* same criteria as lift_object()'s check for available inventory slot */
2705 if (obj->oclass != COIN_CLASS && inv_cnt(FALSE) >= 52
2706 && !merge_choice(invent, obj)) {
2707 /* inventory overflow; can't just place & stack object since
2708 hero isn't in position yet, so schedule for arrival later */
2709 add_to_migration(obj);
2710 obj->ox = 0; /* index of main dungeon */
2711 obj->oy = 1; /* starting level number */
2712 obj->owornmask =
2713 (long) (MIGR_WITH_HERO | MIGR_NOBREAK | MIGR_NOSCATTER);
2714 } else {
2715 (void) addinv(obj);
2719 void
2720 read_wizkit()
2722 FILE *fp;
2723 char *ep, buf[BUFSZ];
2724 struct obj *otmp;
2725 boolean bad_items = FALSE, skip = FALSE;
2727 if (!wizard || !(fp = fopen_wizkit_file()))
2728 return;
2730 program_state.wizkit_wishing = 1;
2731 while (fgets(buf, (int) (sizeof buf), fp)) {
2732 ep = index(buf, '\n');
2733 if (skip) { /* in case previous line was too long */
2734 if (ep)
2735 skip = FALSE; /* found newline; next line is normal */
2736 } else {
2737 if (!ep)
2738 skip = TRUE; /* newline missing; discard next fgets */
2739 else
2740 *ep = '\0'; /* remove newline */
2742 if (buf[0]) {
2743 otmp = readobjnam(buf, (struct obj *) 0);
2744 if (otmp) {
2745 if (otmp != &zeroobj)
2746 wizkit_addinv(otmp);
2747 } else {
2748 /* .60 limits output line width to 79 chars */
2749 raw_printf("Bad wizkit item: \"%.60s\"", buf);
2750 bad_items = TRUE;
2755 program_state.wizkit_wishing = 0;
2756 if (bad_items)
2757 wait_synch();
2758 (void) fclose(fp);
2759 return;
2762 extern struct symsetentry *symset_list; /* options.c */
2763 extern struct symparse loadsyms[]; /* drawing.c */
2764 extern const char *known_handling[]; /* drawing.c */
2765 extern const char *known_restrictions[]; /* drawing.c */
2766 static int symset_count = 0; /* for pick-list building only */
2767 static boolean chosen_symset_start = FALSE, chosen_symset_end = FALSE;
2769 STATIC_OVL
2770 FILE *
2771 fopen_sym_file()
2773 FILE *fp;
2775 fp = fopen_datafile(SYMBOLS, "r", HACKPREFIX);
2776 return fp;
2780 * Returns 1 if the chose symset was found and loaded.
2781 * 0 if it wasn't found in the sym file or other problem.
2784 read_sym_file(which_set)
2785 int which_set;
2787 char buf[4 * BUFSZ];
2788 FILE *fp;
2790 if (!(fp = fopen_sym_file()))
2791 return 0;
2793 symset_count = 0;
2794 chosen_symset_start = chosen_symset_end = FALSE;
2795 while (fgets(buf, 4 * BUFSZ, fp)) {
2796 if (!parse_sym_line(buf, which_set)) {
2797 raw_printf("Bad symbol line: \"%.50s\"", buf);
2798 wait_synch();
2801 (void) fclose(fp);
2802 if (!chosen_symset_start && !chosen_symset_end) {
2803 /* name caller put in symset[which_set].name was not found;
2804 if it looks like "Default symbols", null it out and return
2805 success to use the default; otherwise, return failure */
2806 if (symset[which_set].name
2807 && (fuzzymatch(symset[which_set].name, "Default symbols",
2808 " -_", TRUE)
2809 || !strcmpi(symset[which_set].name, "default")))
2810 clear_symsetentry(which_set, TRUE);
2811 return (symset[which_set].name == 0) ? 1 : 0;
2813 if (!chosen_symset_end) {
2814 raw_printf("Missing finish for symset \"%s\"",
2815 symset[which_set].name ? symset[which_set].name
2816 : "unknown");
2817 wait_synch();
2819 return 1;
2822 /* returns 0 on error */
2824 parse_sym_line(buf, which_set)
2825 char *buf;
2826 int which_set;
2828 int val, i;
2829 struct symparse *symp = (struct symparse *) 0;
2830 char *bufp, *commentp, *altp;
2832 /* convert each instance of whitespace (tabs, consecutive spaces)
2833 into a single space; leading and trailing spaces are stripped */
2834 mungspaces(buf);
2835 if (!*buf || *buf == '#' || !strcmp(buf, " "))
2836 return 1;
2837 /* remove trailing comment, if any (this isn't strictly needed for
2838 individual symbols, and it won't matter if "X#comment" without
2839 separating space slips through; for handling or set description,
2840 symbol set creator is responsible for preceding '#' with a space
2841 and that comment itself doesn't contain " #") */
2842 if ((commentp = rindex(buf, '#')) != 0 && commentp[-1] == ' ')
2843 commentp[-1] = '\0';
2845 /* find the '=' or ':' */
2846 bufp = index(buf, '=');
2847 altp = index(buf, ':');
2848 if (!bufp || (altp && altp < bufp))
2849 bufp = altp;
2850 if (!bufp) {
2851 if (strncmpi(buf, "finish", 6) == 0) {
2852 /* end current graphics set */
2853 if (chosen_symset_start)
2854 chosen_symset_end = TRUE;
2855 chosen_symset_start = FALSE;
2856 return 1;
2858 return 0;
2860 /* skip '=' and space which follows, if any */
2861 ++bufp;
2862 if (*bufp == ' ')
2863 ++bufp;
2865 symp = match_sym(buf);
2866 if (!symp)
2867 return 0;
2869 if (!symset[which_set].name) {
2870 /* A null symset name indicates that we're just
2871 building a pick-list of possible symset
2872 values from the file, so only do that */
2873 if (symp->range == SYM_CONTROL) {
2874 struct symsetentry *tmpsp;
2876 switch (symp->idx) {
2877 case 0:
2878 tmpsp =
2879 (struct symsetentry *) alloc(sizeof (struct symsetentry));
2880 tmpsp->next = (struct symsetentry *) 0;
2881 if (!symset_list) {
2882 symset_list = tmpsp;
2883 symset_count = 0;
2884 } else {
2885 symset_count++;
2886 tmpsp->next = symset_list;
2887 symset_list = tmpsp;
2889 tmpsp->idx = symset_count;
2890 tmpsp->name = dupstr(bufp);
2891 tmpsp->desc = (char *) 0;
2892 tmpsp->nocolor = 0;
2893 /* initialize restriction bits */
2894 tmpsp->primary = 0;
2895 tmpsp->rogue = 0;
2896 break;
2897 case 2:
2898 /* handler type identified */
2899 tmpsp = symset_list; /* most recent symset */
2900 tmpsp->handling = H_UNK;
2901 i = 0;
2902 while (known_handling[i]) {
2903 if (!strcmpi(known_handling[i], bufp)) {
2904 tmpsp->handling = i;
2905 break; /* while loop */
2907 i++;
2909 break;
2910 case 3: /* description:something */
2911 tmpsp = symset_list; /* most recent symset */
2912 if (tmpsp && !tmpsp->desc)
2913 tmpsp->desc = dupstr(bufp);
2914 break;
2915 case 5:
2916 /* restrictions: xxxx*/
2917 tmpsp = symset_list; /* most recent symset */
2918 for (i = 0; known_restrictions[i]; ++i) {
2919 if (!strcmpi(known_restrictions[i], bufp)) {
2920 switch (i) {
2921 case 0:
2922 tmpsp->primary = 1;
2923 break;
2924 case 1:
2925 tmpsp->rogue = 1;
2926 break;
2928 break; /* while loop */
2931 break;
2934 return 1;
2936 if (symp->range) {
2937 if (symp->range == SYM_CONTROL) {
2938 switch (symp->idx) {
2939 case 0:
2940 /* start of symset */
2941 if (!strcmpi(bufp, symset[which_set].name)) {
2942 /* matches desired one */
2943 chosen_symset_start = TRUE;
2944 /* these init_*() functions clear symset fields too */
2945 if (which_set == ROGUESET)
2946 init_r_symbols();
2947 else if (which_set == PRIMARY)
2948 init_l_symbols();
2950 break;
2951 case 1:
2952 /* finish symset */
2953 if (chosen_symset_start)
2954 chosen_symset_end = TRUE;
2955 chosen_symset_start = FALSE;
2956 break;
2957 case 2:
2958 /* handler type identified */
2959 if (chosen_symset_start)
2960 set_symhandling(bufp, which_set);
2961 break;
2962 /* case 3: (description) is ignored here */
2963 case 4: /* color:off */
2964 if (chosen_symset_start) {
2965 if (bufp) {
2966 if (!strcmpi(bufp, "true") || !strcmpi(bufp, "yes")
2967 || !strcmpi(bufp, "on"))
2968 symset[which_set].nocolor = 0;
2969 else if (!strcmpi(bufp, "false")
2970 || !strcmpi(bufp, "no")
2971 || !strcmpi(bufp, "off"))
2972 symset[which_set].nocolor = 1;
2975 break;
2976 case 5: /* restrictions: xxxx*/
2977 if (chosen_symset_start) {
2978 int n = 0;
2980 while (known_restrictions[n]) {
2981 if (!strcmpi(known_restrictions[n], bufp)) {
2982 switch (n) {
2983 case 0:
2984 symset[which_set].primary = 1;
2985 break;
2986 case 1:
2987 symset[which_set].rogue = 1;
2988 break;
2990 break; /* while loop */
2992 n++;
2995 break;
2997 } else { /* !SYM_CONTROL */
2998 val = sym_val(bufp);
2999 if (chosen_symset_start) {
3000 if (which_set == PRIMARY) {
3001 update_l_symset(symp, val);
3002 } else if (which_set == ROGUESET) {
3003 update_r_symset(symp, val);
3008 return 1;
3011 STATIC_OVL void
3012 set_symhandling(handling, which_set)
3013 char *handling;
3014 int which_set;
3016 int i = 0;
3018 symset[which_set].handling = H_UNK;
3019 while (known_handling[i]) {
3020 if (!strcmpi(known_handling[i], handling)) {
3021 symset[which_set].handling = i;
3022 return;
3024 i++;
3028 /* ---------- END CONFIG FILE HANDLING ----------- */
3030 /* ---------- BEGIN SCOREBOARD CREATION ----------- */
3032 #ifdef OS2_CODEVIEW
3033 #define UNUSED_if_not_OS2_CODEVIEW /*empty*/
3034 #else
3035 #define UNUSED_if_not_OS2_CODEVIEW UNUSED
3036 #endif
3038 /* verify that we can write to scoreboard file; if not, try to create one */
3039 /*ARGUSED*/
3040 void
3041 check_recordfile(dir)
3042 const char *dir UNUSED_if_not_OS2_CODEVIEW;
3044 #if defined(PRAGMA_UNUSED) && !defined(OS2_CODEVIEW)
3045 #pragma unused(dir)
3046 #endif
3047 const char *fq_record;
3048 int fd;
3050 #if defined(UNIX) || defined(VMS)
3051 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3052 fd = open(fq_record, O_RDWR, 0);
3053 if (fd >= 0) {
3054 #ifdef VMS /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */
3055 if (!file_is_stmlf(fd)) {
3056 raw_printf(
3057 "Warning: scoreboard file %s is not in stream_lf format",
3058 fq_record);
3059 wait_synch();
3061 #endif
3062 (void) nhclose(fd); /* RECORD is accessible */
3063 } else if ((fd = open(fq_record, O_CREAT | O_RDWR, FCMASK)) >= 0) {
3064 (void) nhclose(fd); /* RECORD newly created */
3065 #if defined(VMS) && !defined(SECURE)
3066 /* Re-protect RECORD with world:read+write+execute+delete access. */
3067 (void) chmod(fq_record, FCMASK | 007);
3068 #endif /* VMS && !SECURE */
3069 } else {
3070 raw_printf("Warning: cannot write scoreboard file %s", fq_record);
3071 wait_synch();
3073 #endif /* !UNIX && !VMS */
3074 #if defined(MICRO) || defined(WIN32)
3075 char tmp[PATHLEN];
3077 #ifdef OS2_CODEVIEW /* explicit path on opening for OS/2 */
3078 /* how does this work when there isn't an explicit path or fopenp
3079 * for later access to the file via fopen_datafile? ? */
3080 (void) strncpy(tmp, dir, PATHLEN - 1);
3081 tmp[PATHLEN - 1] = '\0';
3082 if ((strlen(tmp) + 1 + strlen(RECORD)) < (PATHLEN - 1)) {
3083 append_slash(tmp);
3084 Strcat(tmp, RECORD);
3086 fq_record = tmp;
3087 #else
3088 Strcpy(tmp, RECORD);
3089 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3090 #endif
3092 if ((fd = open(fq_record, O_RDWR)) < 0) {
3093 /* try to create empty record */
3094 #if defined(AZTEC_C) || defined(_DCC) \
3095 || (defined(__GNUC__) && defined(__AMIGA__))
3096 /* Aztec doesn't use the third argument */
3097 /* DICE doesn't like it */
3098 if ((fd = open(fq_record, O_CREAT | O_RDWR)) < 0) {
3099 #else
3100 if ((fd = open(fq_record, O_CREAT | O_RDWR, S_IREAD | S_IWRITE))
3101 < 0) {
3102 #endif
3103 raw_printf("Warning: cannot write record %s", tmp);
3104 wait_synch();
3105 } else
3106 (void) nhclose(fd);
3107 } else /* open succeeded */
3108 (void) nhclose(fd);
3109 #else /* MICRO || WIN32*/
3111 #ifdef MAC
3112 /* Create the "record" file, if necessary */
3113 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3114 fd = macopen(fq_record, O_RDWR | O_CREAT, TEXT_TYPE);
3115 if (fd != -1)
3116 macclose(fd);
3117 #endif /* MAC */
3119 #endif /* MICRO || WIN32*/
3122 /* ---------- END SCOREBOARD CREATION ----------- */
3124 /* ---------- BEGIN PANIC/IMPOSSIBLE LOG ----------- */
3126 /*ARGSUSED*/
3127 void
3128 paniclog(type, reason)
3129 const char *type; /* panic, impossible, trickery */
3130 const char *reason; /* explanation */
3132 #ifdef PANICLOG
3133 FILE *lfile;
3134 char buf[BUFSZ];
3136 if (!program_state.in_paniclog) {
3137 program_state.in_paniclog = 1;
3138 lfile = fopen_datafile(PANICLOG, "a", TROUBLEPREFIX);
3139 if (lfile) {
3140 #ifdef PANICLOG_FMT2
3141 (void) fprintf(lfile, "%ld %s: %s %s\n",
3142 ubirthday, (plname ? plname : "(none)"),
3143 type, reason);
3144 #else
3145 time_t now = getnow();
3146 int uid = getuid();
3147 char playmode = wizard ? 'D' : discover ? 'X' : '-';
3149 (void) fprintf(lfile, "%s %08ld %06ld %d %c: %s %s\n",
3150 version_string(buf), yyyymmdd(now), hhmmss(now),
3151 uid, playmode, type, reason);
3152 #endif /* !PANICLOG_FMT2 */
3153 (void) fclose(lfile);
3155 program_state.in_paniclog = 0;
3157 #endif /* PANICLOG */
3158 return;
3161 /* ---------- END PANIC/IMPOSSIBLE LOG ----------- */
3163 #ifdef SELF_RECOVER
3165 /* ---------- BEGIN INTERNAL RECOVER ----------- */
3166 boolean
3167 recover_savefile()
3169 int gfd, lfd, sfd;
3170 int lev, savelev, hpid, pltmpsiz;
3171 xchar levc;
3172 struct version_info version_data;
3173 int processed[256];
3174 char savename[SAVESIZE], errbuf[BUFSZ];
3175 struct savefile_info sfi;
3176 char tmpplbuf[PL_NSIZ];
3178 for (lev = 0; lev < 256; lev++)
3179 processed[lev] = 0;
3181 /* level 0 file contains:
3182 * pid of creating process (ignored here)
3183 * level number for current level of save file
3184 * name of save file nethack would have created
3185 * savefile info
3186 * player name
3187 * and game state
3189 gfd = open_levelfile(0, errbuf);
3190 if (gfd < 0) {
3191 raw_printf("%s\n", errbuf);
3192 return FALSE;
3194 if (read(gfd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) {
3195 raw_printf("\n%s\n%s\n",
3196 "Checkpoint data incompletely written or subsequently clobbered.",
3197 "Recovery impossible.");
3198 (void) nhclose(gfd);
3199 return FALSE;
3201 if (read(gfd, (genericptr_t) &savelev, sizeof(savelev))
3202 != sizeof(savelev)) {
3203 raw_printf(
3204 "\nCheckpointing was not in effect for %s -- recovery impossible.\n",
3205 lock);
3206 (void) nhclose(gfd);
3207 return FALSE;
3209 if ((read(gfd, (genericptr_t) savename, sizeof savename)
3210 != sizeof savename)
3211 || (read(gfd, (genericptr_t) &version_data, sizeof version_data)
3212 != sizeof version_data)
3213 || (read(gfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi)
3214 || (read(gfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3215 != sizeof pltmpsiz) || (pltmpsiz > PL_NSIZ)
3216 || (read(gfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz)) {
3217 raw_printf("\nError reading %s -- can't recover.\n", lock);
3218 (void) nhclose(gfd);
3219 return FALSE;
3222 /* save file should contain:
3223 * version info
3224 * savefile info
3225 * player name
3226 * current level (including pets)
3227 * (non-level-based) game state
3228 * other levels
3230 set_savefile_name(TRUE);
3231 sfd = create_savefile();
3232 if (sfd < 0) {
3233 raw_printf("\nCannot recover savefile %s.\n", SAVEF);
3234 (void) nhclose(gfd);
3235 return FALSE;
3238 lfd = open_levelfile(savelev, errbuf);
3239 if (lfd < 0) {
3240 raw_printf("\n%s\n", errbuf);
3241 (void) nhclose(gfd);
3242 (void) nhclose(sfd);
3243 delete_savefile();
3244 return FALSE;
3247 if (write(sfd, (genericptr_t) &version_data, sizeof version_data)
3248 != sizeof version_data) {
3249 raw_printf("\nError writing %s; recovery failed.", SAVEF);
3250 (void) nhclose(gfd);
3251 (void) nhclose(sfd);
3252 delete_savefile();
3253 return FALSE;
3256 if (write(sfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi) {
3257 raw_printf("\nError writing %s; recovery failed (savefile_info).\n",
3258 SAVEF);
3259 (void) nhclose(gfd);
3260 (void) nhclose(sfd);
3261 delete_savefile();
3262 return FALSE;
3265 if (write(sfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3266 != sizeof pltmpsiz) {
3267 raw_printf("Error writing %s; recovery failed (player name size).\n",
3268 SAVEF);
3269 (void) nhclose(gfd);
3270 (void) nhclose(sfd);
3271 delete_savefile();
3272 return FALSE;
3275 if (write(sfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz) {
3276 raw_printf("Error writing %s; recovery failed (player name).\n",
3277 SAVEF);
3278 (void) nhclose(gfd);
3279 (void) nhclose(sfd);
3280 delete_savefile();
3281 return FALSE;
3284 if (!copy_bytes(lfd, sfd)) {
3285 (void) nhclose(lfd);
3286 (void) nhclose(sfd);
3287 delete_savefile();
3288 return FALSE;
3290 (void) nhclose(lfd);
3291 processed[savelev] = 1;
3293 if (!copy_bytes(gfd, sfd)) {
3294 (void) nhclose(lfd);
3295 (void) nhclose(sfd);
3296 delete_savefile();
3297 return FALSE;
3299 (void) nhclose(gfd);
3300 processed[0] = 1;
3302 for (lev = 1; lev < 256; lev++) {
3303 /* level numbers are kept in xchars in save.c, so the
3304 * maximum level number (for the endlevel) must be < 256
3306 if (lev != savelev) {
3307 lfd = open_levelfile(lev, (char *) 0);
3308 if (lfd >= 0) {
3309 /* any or all of these may not exist */
3310 levc = (xchar) lev;
3311 write(sfd, (genericptr_t) &levc, sizeof(levc));
3312 if (!copy_bytes(lfd, sfd)) {
3313 (void) nhclose(lfd);
3314 (void) nhclose(sfd);
3315 delete_savefile();
3316 return FALSE;
3318 (void) nhclose(lfd);
3319 processed[lev] = 1;
3323 (void) nhclose(sfd);
3325 #ifdef HOLD_LOCKFILE_OPEN
3326 really_close();
3327 #endif
3329 * We have a successful savefile!
3330 * Only now do we erase the level files.
3332 for (lev = 0; lev < 256; lev++) {
3333 if (processed[lev]) {
3334 const char *fq_lock;
3335 set_levelfile_name(lock, lev);
3336 fq_lock = fqname(lock, LEVELPREFIX, 3);
3337 (void) unlink(fq_lock);
3340 return TRUE;
3343 boolean
3344 copy_bytes(ifd, ofd)
3345 int ifd, ofd;
3347 char buf[BUFSIZ];
3348 int nfrom, nto;
3350 do {
3351 nfrom = read(ifd, buf, BUFSIZ);
3352 nto = write(ofd, buf, nfrom);
3353 if (nto != nfrom)
3354 return FALSE;
3355 } while (nfrom == BUFSIZ);
3356 return TRUE;
3359 /* ---------- END INTERNAL RECOVER ----------- */
3360 #endif /*SELF_RECOVER*/
3362 /* ---------- OTHER ----------- */
3364 #ifdef SYSCF
3365 #ifdef SYSCF_FILE
3366 void
3367 assure_syscf_file()
3369 int fd;
3372 * All we really care about is the end result - can we read the file?
3373 * So just check that directly.
3375 * Not tested on most of the old platforms (which don't attempt
3376 * to implement SYSCF).
3377 * Some ports don't like open()'s optional third argument;
3378 * VMS overrides open() usage with a macro which requires it.
3380 #ifndef VMS
3381 # if defined(NOCWD_ASSUMPTIONS) && defined(WIN32)
3382 fd = open(fqname(SYSCF_FILE, SYSCONFPREFIX, 0), O_RDONLY);
3383 # else
3384 fd = open(SYSCF_FILE, O_RDONLY);
3385 # endif
3386 #else
3387 fd = open(SYSCF_FILE, O_RDONLY, 0);
3388 #endif
3389 if (fd >= 0) {
3390 /* readable */
3391 close(fd);
3392 return;
3394 raw_printf("Unable to open SYSCF_FILE.\n");
3395 exit(EXIT_FAILURE);
3398 #endif /* SYSCF_FILE */
3399 #endif /* SYSCF */
3401 #ifdef DEBUG
3402 /* used by debugpline() to decide whether to issue a message
3403 * from a particular source file; caller passes __FILE__ and we check
3404 * whether it is in the source file list supplied by SYSCF's DEBUGFILES
3406 * pass FALSE to override wildcard matching; useful for files
3407 * like dungeon.c and questpgr.c, which generate a ridiculous amount of
3408 * output if DEBUG is defined and effectively block the use of a wildcard */
3409 boolean
3410 debugcore(filename, wildcards)
3411 const char *filename;
3412 boolean wildcards;
3414 const char *debugfiles, *p;
3416 if (!filename || !*filename)
3417 return FALSE; /* sanity precaution */
3419 if (sysopt.env_dbgfl == 0) {
3420 /* check once for DEBUGFILES in the environment;
3421 if found, it supersedes the sysconf value
3422 [note: getenv() rather than nh_getenv() since a long value
3423 is valid and doesn't pose any sort of overflow risk here] */
3424 if ((p = getenv("DEBUGFILES")) != 0) {
3425 if (sysopt.debugfiles)
3426 free((genericptr_t) sysopt.debugfiles);
3427 sysopt.debugfiles = dupstr(p);
3428 sysopt.env_dbgfl = 1;
3429 } else
3430 sysopt.env_dbgfl = -1;
3433 debugfiles = sysopt.debugfiles;
3434 /* usual case: sysopt.debugfiles will be empty */
3435 if (!debugfiles || !*debugfiles)
3436 return FALSE;
3438 /* strip filename's path if present */
3439 #ifdef UNIX
3440 if ((p = rindex(filename, '/')) != 0)
3441 filename = p + 1;
3442 #endif
3443 #ifdef VMS
3444 filename = vms_basename(filename);
3445 /* vms_basename strips off 'type' suffix as well as path and version;
3446 we want to put suffix back (".c" assumed); since it always returns
3447 a pointer to a static buffer, we can safely modify its result */
3448 Strcat((char *) filename, ".c");
3449 #endif
3452 * Wildcard match will only work if there's a single pattern (which
3453 * might be a single file name without any wildcarding) rather than
3454 * a space-separated list.
3455 * [to NOT do: We could step through the space-separated list and
3456 * attempt a wildcard match against each element, but that would be
3457 * overkill for the intended usage.]
3459 if (wildcards && pmatch(debugfiles, filename))
3460 return TRUE;
3462 /* check whether filename is an element of the list */
3463 if ((p = strstr(debugfiles, filename)) != 0) {
3464 int l = (int) strlen(filename);
3466 if ((p == debugfiles || p[-1] == ' ' || p[-1] == '/')
3467 && (p[l] == ' ' || p[l] == '\0'))
3468 return TRUE;
3470 return FALSE;
3473 #endif /*DEBUG*/
3475 /* ---------- BEGIN TRIBUTE ----------- */
3477 /* 3.6 tribute code
3480 #define SECTIONSCOPE 1
3481 #define TITLESCOPE 2
3482 #define PASSAGESCOPE 3
3484 #define MAXPASSAGES SIZE(context.novel.pasg) /* 20 */
3486 static int FDECL(choose_passage, (int, unsigned));
3488 /* choose a random passage that hasn't been chosen yet; once all have
3489 been chosen, reset the tracking to make all passages available again */
3490 static int
3491 choose_passage(passagecnt, oid)
3492 int passagecnt; /* total of available passages */
3493 unsigned oid; /* book.o_id, used to determine whether re-reading same book */
3495 int idx, res;
3497 if (passagecnt < 1)
3498 return 0;
3500 /* if a different book or we've used up all the passages already,
3501 reset in order to have all 'passagecnt' passages available */
3502 if (oid != context.novel.id || context.novel.count == 0) {
3503 int i, range = passagecnt, limit = MAXPASSAGES;
3505 context.novel.id = oid;
3506 if (range <= limit) {
3507 /* collect all of the N indices */
3508 context.novel.count = passagecnt;
3509 for (idx = 0; idx < MAXPASSAGES; idx++)
3510 context.novel.pasg[idx] = (xchar) ((idx < passagecnt)
3511 ? idx + 1 : 0);
3512 } else {
3513 /* collect MAXPASSAGES of the N indices */
3514 context.novel.count = MAXPASSAGES;
3515 for (idx = i = 0; i < passagecnt; ++i, --range)
3516 if (range > 0 && rn2(range) < limit) {
3517 context.novel.pasg[idx++] = (xchar) (i + 1);
3518 --limit;
3523 idx = rn2(context.novel.count);
3524 res = (int) context.novel.pasg[idx];
3525 /* move the last slot's passage index into the slot just used
3526 and reduce the number of passages available */
3527 context.novel.pasg[idx] = context.novel.pasg[--context.novel.count];
3528 return res;
3531 /* Returns True if you were able to read something. */
3532 boolean
3533 read_tribute(tribsection, tribtitle, tribpassage, nowin_buf, bufsz, oid)
3534 const char *tribsection, *tribtitle;
3535 int tribpassage, bufsz;
3536 char *nowin_buf;
3537 unsigned oid; /* book identifier */
3539 dlb *fp;
3540 char line[BUFSZ], lastline[BUFSZ];
3542 int scope = 0;
3543 int linect = 0, passagecnt = 0, targetpassage = 0;
3544 const char *badtranslation = "an incomprehensible foreign translation";
3545 boolean matchedsection = FALSE, matchedtitle = FALSE;
3546 winid tribwin = WIN_ERR;
3547 boolean grasped = FALSE;
3548 boolean foundpassage = FALSE;
3550 if (nowin_buf)
3551 *nowin_buf = '\0';
3553 /* check for mandatories */
3554 if (!tribsection || !tribtitle) {
3555 if (!nowin_buf)
3556 pline("It's %s of \"%s\"!", badtranslation, tribtitle);
3557 return grasped;
3560 debugpline3("read_tribute %s, %s, %d.", tribsection, tribtitle,
3561 tribpassage);
3563 fp = dlb_fopen(TRIBUTEFILE, "r");
3564 if (!fp) {
3565 /* this is actually an error - cannot open tribute file! */
3566 if (!nowin_buf)
3567 pline("You feel too overwhelmed to continue!");
3568 return grasped;
3572 * Syntax (not case-sensitive):
3573 * %section books
3575 * In the books section:
3576 * %title booktitle (n)
3577 * where booktitle=book title without quotes
3578 * (n)= total number of passages present for this title
3579 * %passage k
3580 * where k=sequential passage number
3582 * %e ends the passage/book/section
3583 * If in a passage, it marks the end of that passage.
3584 * If in a book, it marks the end of that book.
3585 * If in a section, it marks the end of that section.
3587 * %section death
3590 *line = *lastline = '\0';
3591 while (dlb_fgets(line, sizeof line, fp) != 0) {
3592 linect++;
3593 (void) strip_newline(line);
3594 switch (line[0]) {
3595 case '%':
3596 if (!strncmpi(&line[1], "section ", sizeof "section " - 1)) {
3597 char *st = &line[9]; /* 9 from "%section " */
3599 scope = SECTIONSCOPE;
3600 matchedsection = !strcmpi(st, tribsection) ? TRUE : FALSE;
3601 } else if (!strncmpi(&line[1], "title ", sizeof "title " - 1)) {
3602 char *st = &line[7]; /* 7 from "%title " */
3603 char *p1, *p2;
3605 if ((p1 = index(st, '(')) != 0) {
3606 *p1++ = '\0';
3607 (void) mungspaces(st);
3608 if ((p2 = index(p1, ')')) != 0) {
3609 *p2 = '\0';
3610 passagecnt = atoi(p1);
3611 scope = TITLESCOPE;
3612 if (matchedsection && !strcmpi(st, tribtitle)) {
3613 matchedtitle = TRUE;
3614 targetpassage = !tribpassage
3615 ? choose_passage(passagecnt, oid)
3616 : (tribpassage <= passagecnt)
3617 ? tribpassage : 0;
3618 } else {
3619 matchedtitle = FALSE;
3623 } else if (!strncmpi(&line[1], "passage ",
3624 sizeof "passage " - 1)) {
3625 int passagenum = 0;
3626 char *st = &line[9]; /* 9 from "%passage " */
3628 mungspaces(st);
3629 passagenum = atoi(st);
3630 if (passagenum > 0 && passagenum <= passagecnt) {
3631 scope = PASSAGESCOPE;
3632 if (matchedtitle && passagenum == targetpassage) {
3633 foundpassage = TRUE;
3634 if (!nowin_buf) {
3635 tribwin = create_nhwindow(NHW_MENU);
3636 if (tribwin == WIN_ERR)
3637 goto cleanup;
3641 } else if (!strncmpi(&line[1], "e ", sizeof "e " - 1)) {
3642 if (foundpassage)
3643 goto cleanup;
3644 if (scope == TITLESCOPE)
3645 matchedtitle = FALSE;
3646 if (scope == SECTIONSCOPE)
3647 matchedsection = FALSE;
3648 if (scope)
3649 --scope;
3650 } else {
3651 debugpline1("tribute file error: bad %% command, line %d.",
3652 linect);
3654 break;
3655 case '#':
3656 /* comment only, next! */
3657 break;
3658 default:
3659 if (foundpassage) {
3660 if (!nowin_buf) {
3661 /* outputting multi-line passage to text window */
3662 putstr(tribwin, 0, line);
3663 if (*line)
3664 Strcpy(lastline, line);
3665 } else {
3666 /* fetching one-line passage into buffer */
3667 copynchars(nowin_buf, line, bufsz - 1);
3668 goto cleanup; /* don't wait for "%e passage" */
3674 cleanup:
3675 (void) dlb_fclose(fp);
3676 if (nowin_buf) {
3677 /* one-line buffer */
3678 grasped = *nowin_buf ? TRUE : FALSE;
3679 } else {
3680 if (tribwin != WIN_ERR) { /* implies 'foundpassage' */
3681 /* multi-line window, normal case;
3682 if lastline is empty, there were no non-empty lines between
3683 "%passage n" and "%e passage" so we leave 'grasped' False */
3684 if (*lastline) {
3685 display_nhwindow(tribwin, FALSE);
3686 /* put the final attribution line into message history,
3687 analogous to the summary line from long quest messages */
3688 if (index(lastline, '['))
3689 mungspaces(lastline); /* to remove leading spaces */
3690 else /* construct one if necessary */
3691 Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle);
3692 putmsghistory(lastline, FALSE);
3693 grasped = TRUE;
3695 destroy_nhwindow(tribwin);
3697 if (!grasped)
3698 /* multi-line window, problem */
3699 pline("It seems to be %s of \"%s\"!", badtranslation, tribtitle);
3701 return grasped;
3704 boolean
3705 Death_quote(buf, bufsz)
3706 char *buf;
3707 int bufsz;
3709 unsigned death_oid = 1; /* chance of oid #1 being a novel is negligible */
3711 return read_tribute("Death", "Death Quotes", 0, buf, bufsz, death_oid);
3714 /* ---------- END TRIBUTE ----------- */
3716 /*files.c*/