NHDT->ANH, nethack->anethack, nhdat->anhdat
[aNetHack.git] / src / files.c
blob599a2d71b71fa65b6d5e437225e6059c22fb451f
1 /* aNetHack 0.0.1 files.c $ANH-Date: 1459987580 2016/04/07 00:06:20 $ $ANH-Branch: master $:$ANH-Revision: 1.205 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* aNetHack 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.aNetHack-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 ".aNetHack-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 anethack_thinks_it_is_open; /* Does aNetHack 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 anethack'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.anethack_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.anethack_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.anethack_thinks_it_is_open = TRUE;
646 return fd;
649 void
650 really_close()
652 int fd = lftrack.fd;
654 lftrack.anethack_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.anethack_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 aNetHack 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 ".anethackrc";
1828 #else
1829 #if defined(MAC) || defined(__BEOS__)
1830 "aNetHack Defaults";
1831 #else
1832 #if defined(MSDOS) || defined(WIN32)
1833 "defaults.nh";
1834 #else
1835 "aNetHack.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 aNetHack.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 = "anethack.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 * aNetHack'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("anethackini", CONFIGPREFIX, 0));
1944 if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
1945 return fp;
1946 set_configfile_name("sys$login:anethack.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, "aNetHack.cnf");
1953 else
1954 Sprintf(tmp_config, "%s%s%s", envp,
1955 !index(":]>/", envp[strlen(envp) - 1]) ? "/" : "",
1956 "aNetHack.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, ".anethackrc");
1964 else
1965 Sprintf(tmp_config, "%s/%s", envp, ".anethackrc");
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/aNetHack 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/aNetHack 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 aNetHack can't search home
1991 directory restricted to user */
1992 #if defined(NHSTDC) && !defined(NOTSTDC)
1993 if ((details = strerror(errno)) == 0)
1994 #endif
1995 details = "";
1996 raw_printf("Couldn't open default config file %s %s(%d).",
1997 configfile, details, errno);
1998 wait_synch();
2000 #endif /* !VMS => Unix */
2001 #endif /* !(MICRO || MAC || __BEOS__ || WIN32) */
2002 return (FILE *) 0;
2006 * Retrieve a list of integers from a file into a uchar array.
2008 * NOTE: zeros are inserted unless modlist is TRUE, in which case the list
2009 * location is unchanged. Callers must handle zeros if modlist is FALSE.
2011 STATIC_OVL int
2012 get_uchars(fp, buf, bufp, list, modlist, size, name)
2013 FILE *fp; /* input file pointer */
2014 char *buf; /* read buffer, must be of size BUFSZ */
2015 char *bufp; /* current pointer */
2016 uchar *list; /* return list */
2017 boolean modlist; /* TRUE: list is being modified in place */
2018 int size; /* return list size */
2019 const char *name; /* name of option for error message */
2021 unsigned int num = 0;
2022 int count = 0;
2023 boolean havenum = FALSE;
2025 while (1) {
2026 switch (*bufp) {
2027 case ' ':
2028 case '\0':
2029 case '\t':
2030 case '\n':
2031 if (havenum) {
2032 /* if modifying in place, don't insert zeros */
2033 if (num || !modlist)
2034 list[count] = num;
2035 count++;
2036 num = 0;
2037 havenum = FALSE;
2039 if (count == size || !*bufp)
2040 return count;
2041 bufp++;
2042 break;
2044 case '0':
2045 case '1':
2046 case '2':
2047 case '3':
2048 case '4':
2049 case '5':
2050 case '6':
2051 case '7':
2052 case '8':
2053 case '9':
2054 havenum = TRUE;
2055 num = num * 10 + (*bufp - '0');
2056 bufp++;
2057 break;
2059 case '\\':
2060 if (fp == (FILE *) 0)
2061 goto gi_error;
2062 do {
2063 if (!fgets(buf, BUFSZ, fp))
2064 goto gi_error;
2065 } while (buf[0] == '#');
2066 bufp = buf;
2067 break;
2069 default:
2070 gi_error:
2071 raw_printf("Syntax error in %s", name);
2072 wait_synch();
2073 return count;
2076 /*NOTREACHED*/
2079 #ifdef NOCWD_ASSUMPTIONS
2080 STATIC_OVL void
2081 adjust_prefix(bufp, prefixid)
2082 char *bufp;
2083 int prefixid;
2085 char *ptr;
2087 if (!bufp)
2088 return;
2089 /* Backward compatibility, ignore trailing ;n */
2090 if ((ptr = index(bufp, ';')) != 0)
2091 *ptr = '\0';
2092 if (strlen(bufp) > 0) {
2093 fqn_prefix[prefixid] = (char *) alloc(strlen(bufp) + 2);
2094 Strcpy(fqn_prefix[prefixid], bufp);
2095 append_slash(fqn_prefix[prefixid]);
2098 #endif
2100 #define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE)
2103 parse_config_line(fp, origbuf, src)
2104 FILE *fp;
2105 char *origbuf;
2106 int src;
2108 #if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
2109 static boolean ramdisk_specified = FALSE;
2110 #endif
2111 #ifdef SYSCF
2112 int n;
2113 #endif
2114 char *bufp, *altp, buf[4 * BUFSZ];
2115 uchar translate[MAXPCHARS];
2116 int len;
2118 /* convert any tab to space, condense consecutive spaces into one,
2119 remove leading and trailing spaces (exception: if there is nothing
2120 but spaces, one of them will be kept even though it leads/trails) */
2121 mungspaces(strcpy(buf, origbuf));
2122 /* lines beginning with '#' are comments; accept empty lines too */
2123 if (!*buf || *buf == '#' || !strcmp(buf, " "))
2124 return 1;
2126 /* find the '=' or ':' */
2127 bufp = index(buf, '=');
2128 altp = index(buf, ':');
2129 if (!bufp || (altp && altp < bufp))
2130 bufp = altp;
2131 if (!bufp)
2132 return 0;
2133 /* skip past '=', then space between it and value, if any */
2134 ++bufp;
2135 if (*bufp == ' ')
2136 ++bufp;
2138 /* Go through possible variables */
2139 /* some of these (at least LEVELS and SAVE) should now set the
2140 * appropriate fqn_prefix[] rather than specialized variables
2142 if (match_varname(buf, "OPTIONS", 4)) {
2143 /* hack: un-mungspaces to allow consecutive spaces in
2144 general options until we verify that this is unnecessary;
2145 '=' or ':' is guaranteed to be present */
2146 bufp = index(origbuf, '=');
2147 altp = index(origbuf, ':');
2148 if (!bufp || (altp && altp < bufp))
2149 bufp = altp;
2150 ++bufp; /* skip '='; parseoptions() handles spaces */
2152 parseoptions(bufp, TRUE, TRUE);
2153 } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) {
2154 add_autopickup_exception(bufp);
2155 } else if (match_varname(buf, "BINDINGS", 4)) {
2156 parsebindings(bufp);
2157 } else if (match_varname(buf, "AUTOCOMPLETE", 5)) {
2158 parseautocomplete(bufp, TRUE);
2159 } else if (match_varname(buf, "MSGTYPE", 7)) {
2160 (void) msgtype_parse_add(bufp);
2161 #ifdef NOCWD_ASSUMPTIONS
2162 } else if (match_varname(buf, "HACKDIR", 4)) {
2163 adjust_prefix(bufp, HACKPREFIX);
2164 } else if (match_varname(buf, "LEVELDIR", 4)
2165 || match_varname(buf, "LEVELS", 4)) {
2166 adjust_prefix(bufp, LEVELPREFIX);
2167 } else if (match_varname(buf, "SAVEDIR", 4)) {
2168 adjust_prefix(bufp, SAVEPREFIX);
2169 } else if (match_varname(buf, "BONESDIR", 5)) {
2170 adjust_prefix(bufp, BONESPREFIX);
2171 } else if (match_varname(buf, "DATADIR", 4)) {
2172 adjust_prefix(bufp, DATAPREFIX);
2173 } else if (match_varname(buf, "SCOREDIR", 4)) {
2174 adjust_prefix(bufp, SCOREPREFIX);
2175 } else if (match_varname(buf, "LOCKDIR", 4)) {
2176 adjust_prefix(bufp, LOCKPREFIX);
2177 } else if (match_varname(buf, "CONFIGDIR", 4)) {
2178 adjust_prefix(bufp, CONFIGPREFIX);
2179 } else if (match_varname(buf, "TROUBLEDIR", 4)) {
2180 adjust_prefix(bufp, TROUBLEPREFIX);
2181 #else /*NOCWD_ASSUMPTIONS*/
2182 #ifdef MICRO
2183 } else if (match_varname(buf, "HACKDIR", 4)) {
2184 (void) strncpy(hackdir, bufp, PATHLEN - 1);
2185 #ifdef MFLOPPY
2186 } else if (match_varname(buf, "RAMDISK", 3)) {
2187 /* The following ifdef is NOT in the wrong
2188 * place. For now, we accept and silently
2189 * ignore RAMDISK */
2190 #ifndef AMIGA
2191 if (strlen(bufp) >= PATHLEN)
2192 bufp[PATHLEN - 1] = '\0';
2193 Strcpy(levels, bufp);
2194 ramdisk = (strcmp(permbones, levels) != 0);
2195 ramdisk_specified = TRUE;
2196 #endif
2197 #endif
2198 } else if (match_varname(buf, "LEVELS", 4)) {
2199 if (strlen(bufp) >= PATHLEN)
2200 bufp[PATHLEN - 1] = '\0';
2201 Strcpy(permbones, bufp);
2202 if (!ramdisk_specified || !*levels)
2203 Strcpy(levels, bufp);
2204 ramdisk = (strcmp(permbones, levels) != 0);
2205 } else if (match_varname(buf, "SAVE", 4)) {
2206 #ifdef MFLOPPY
2207 extern int saveprompt;
2208 #endif
2209 char *ptr;
2211 if ((ptr = index(bufp, ';')) != 0) {
2212 *ptr = '\0';
2213 #ifdef MFLOPPY
2214 if (*(ptr + 1) == 'n' || *(ptr + 1) == 'N') {
2215 saveprompt = FALSE;
2217 #endif
2219 #if defined(SYSFLAGS) && defined(MFLOPPY)
2220 else
2221 saveprompt = sysflags.asksavedisk;
2222 #endif
2224 (void) strncpy(SAVEP, bufp, SAVESIZE - 1);
2225 append_slash(SAVEP);
2226 #endif /* MICRO */
2227 #endif /*NOCWD_ASSUMPTIONS*/
2229 } else if (match_varname(buf, "NAME", 4)) {
2230 (void) strncpy(plname, bufp, PL_NSIZ - 1);
2231 } else if (match_varname(buf, "ROLE", 4)
2232 || match_varname(buf, "CHARACTER", 4)) {
2233 if ((len = str2role(bufp)) >= 0)
2234 flags.initrole = len;
2235 } else if (match_varname(buf, "DOGNAME", 3)) {
2236 (void) strncpy(dogname, bufp, PL_PSIZ - 1);
2237 } else if (match_varname(buf, "CATNAME", 3)) {
2238 (void) strncpy(catname, bufp, PL_PSIZ - 1);
2240 #ifdef SYSCF
2241 } else if (src == SET_IN_SYS && match_varname(buf, "WIZARDS", 7)) {
2242 if (sysopt.wizards)
2243 free((genericptr_t) sysopt.wizards);
2244 sysopt.wizards = dupstr(bufp);
2245 if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) {
2246 /* pre-format WIZARDS list now; it's displayed during a panic
2247 and since that panic might be due to running out of memory,
2248 we don't want to risk attempting to allocate any memory then */
2249 if (sysopt.fmtd_wizard_list)
2250 free((genericptr_t) sysopt.fmtd_wizard_list);
2251 sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards);
2253 } else if (src == SET_IN_SYS && match_varname(buf, "SHELLERS", 8)) {
2254 if (sysopt.shellers)
2255 free((genericptr_t) sysopt.shellers);
2256 sysopt.shellers = dupstr(bufp);
2257 } else if (src == SET_IN_SYS && match_varname(buf, "EXPLORERS", 7)) {
2258 if (sysopt.explorers)
2259 free((genericptr_t) sysopt.explorers);
2260 sysopt.explorers = dupstr(bufp);
2261 } else if (src == SET_IN_SYS && match_varname(buf, "DEBUGFILES", 5)) {
2262 /* if showdebug() has already been called (perhaps we've added
2263 some debugpline() calls to option processing) and has found
2264 a value for getenv("DEBUGFILES"), don't override that */
2265 if (sysopt.env_dbgfl <= 0) {
2266 if (sysopt.debugfiles)
2267 free((genericptr_t) sysopt.debugfiles);
2268 sysopt.debugfiles = dupstr(bufp);
2270 } else if (src == SET_IN_SYS && match_varname(buf, "DUMPLOGFILE", 7)) {
2271 #ifdef DUMPLOG
2272 if (sysopt.dumplogfile)
2273 free((genericptr_t) sysopt.dumplogfile);
2274 sysopt.dumplogfile = dupstr(bufp);
2275 #endif
2276 } else if (src == SET_IN_SYS && match_varname(buf, "GENERICUSERS", 12)) {
2277 if (sysopt.genericusers)
2278 free((genericptr_t) sysopt.genericusers);
2279 sysopt.genericusers = dupstr(bufp);
2280 } else if (src == SET_IN_SYS && match_varname(buf, "SUPPORT", 7)) {
2281 if (sysopt.support)
2282 free((genericptr_t) sysopt.support);
2283 sysopt.support = dupstr(bufp);
2284 } else if (src == SET_IN_SYS && match_varname(buf, "RECOVER", 7)) {
2285 if (sysopt.recover)
2286 free((genericptr_t) sysopt.recover);
2287 sysopt.recover = dupstr(bufp);
2288 } else if (src == SET_IN_SYS
2289 && match_varname(buf, "CHECK_SAVE_UID", 14)) {
2290 n = atoi(bufp);
2291 sysopt.check_save_uid = n;
2292 } else if (src == SET_IN_SYS
2293 && match_varname(buf, "CHECK_PLNAME", 12)) {
2294 n = atoi(bufp);
2295 sysopt.check_plname = n;
2296 } else if (match_varname(buf, "SEDUCE", 6)) {
2297 n = !!atoi(bufp); /* XXX this could be tighter */
2298 /* allow anyone to turn it off, but only sysconf to turn it on*/
2299 if (src != SET_IN_SYS && n != 0) {
2300 raw_printf("Illegal value in SEDUCE");
2301 return 0;
2303 sysopt.seduce = n;
2304 sysopt_seduce_set(sysopt.seduce);
2305 } else if (src == SET_IN_SYS && match_varname(buf, "MAXPLAYERS", 10)) {
2306 n = atoi(bufp);
2307 /* XXX to get more than 25, need to rewrite all lock code */
2308 if (n < 0 || n > 25) {
2309 raw_printf("Illegal value in MAXPLAYERS (maximum is 25).");
2310 return 0;
2312 sysopt.maxplayers = n;
2313 } else if (src == SET_IN_SYS && match_varname(buf, "PERSMAX", 7)) {
2314 n = atoi(bufp);
2315 if (n < 1) {
2316 raw_printf("Illegal value in PERSMAX (minimum is 1).");
2317 return 0;
2319 sysopt.persmax = n;
2320 } else if (src == SET_IN_SYS && match_varname(buf, "PERS_IS_UID", 11)) {
2321 n = atoi(bufp);
2322 if (n != 0 && n != 1) {
2323 raw_printf("Illegal value in PERS_IS_UID (must be 0 or 1).");
2324 return 0;
2326 sysopt.pers_is_uid = n;
2327 } else if (src == SET_IN_SYS && match_varname(buf, "ENTRYMAX", 8)) {
2328 n = atoi(bufp);
2329 if (n < 10) {
2330 raw_printf("Illegal value in ENTRYMAX (minimum is 10).");
2331 return 0;
2333 sysopt.entrymax = n;
2334 } else if ((src == SET_IN_SYS) && match_varname(buf, "POINTSMIN", 9)) {
2335 n = atoi(bufp);
2336 if (n < 1) {
2337 raw_printf("Illegal value in POINTSMIN (minimum is 1).");
2338 return 0;
2340 sysopt.pointsmin = n;
2341 } else if (src == SET_IN_SYS
2342 && match_varname(buf, "MAX_STATUENAME_RANK", 10)) {
2343 n = atoi(bufp);
2344 if (n < 1) {
2345 raw_printf(
2346 "Illegal value in MAX_STATUENAME_RANK (minimum is 1).");
2347 return 0;
2349 sysopt.tt_oname_maxrank = n;
2351 /* SYSCF PANICTRACE options */
2352 } else if (src == SET_IN_SYS
2353 && match_varname(buf, "PANICTRACE_LIBC", 15)) {
2354 n = atoi(bufp);
2355 #if defined(PANICTRACE) && defined(PANICTRACE_LIBC)
2356 if (n < 0 || n > 2) {
2357 raw_printf("Illegal value in PANICTRACE_LIBC (not 0,1,2).");
2358 return 0;
2360 #endif
2361 sysopt.panictrace_libc = n;
2362 } else if (src == SET_IN_SYS
2363 && match_varname(buf, "PANICTRACE_GDB", 14)) {
2364 n = atoi(bufp);
2365 #if defined(PANICTRACE)
2366 if (n < 0 || n > 2) {
2367 raw_printf("Illegal value in PANICTRACE_GDB (not 0,1,2).");
2368 return 0;
2370 #endif
2371 sysopt.panictrace_gdb = n;
2372 } else if (src == SET_IN_SYS && match_varname(buf, "GDBPATH", 7)) {
2373 #if defined(PANICTRACE) && !defined(VMS)
2374 if (!file_exists(bufp)) {
2375 raw_printf("File specified in GDBPATH does not exist.");
2376 return 0;
2378 #endif
2379 if (sysopt.gdbpath)
2380 free((genericptr_t) sysopt.gdbpath);
2381 sysopt.gdbpath = dupstr(bufp);
2382 } else if (src == SET_IN_SYS && match_varname(buf, "GREPPATH", 7)) {
2383 #if defined(PANICTRACE) && !defined(VMS)
2384 if (!file_exists(bufp)) {
2385 raw_printf("File specified in GREPPATH does not exist.");
2386 return 0;
2388 #endif
2389 if (sysopt.greppath)
2390 free((genericptr_t) sysopt.greppath);
2391 sysopt.greppath = dupstr(bufp);
2392 #endif /* SYSCF */
2394 } else if (match_varname(buf, "BOULDER", 3)) {
2395 (void) get_uchars(fp, buf, bufp, &iflags.bouldersym, TRUE, 1,
2396 "BOULDER");
2397 } else if (match_varname(buf, "MENUCOLOR", 9)) {
2398 (void) add_menu_coloring(bufp);
2399 } else if (match_varname(buf, "WARNINGS", 5)) {
2400 (void) get_uchars(fp, buf, bufp, translate, FALSE, WARNCOUNT,
2401 "WARNINGS");
2402 assign_warnings(translate);
2403 } else if (match_varname(buf, "SYMBOLS", 4)) {
2404 char *op, symbuf[BUFSZ];
2405 boolean morelines;
2407 do {
2408 /* check for line continuation (trailing '\') */
2409 op = eos(bufp);
2410 morelines = (--op >= bufp && *op == '\\');
2411 if (morelines) {
2412 *op = '\0';
2413 /* strip trailing space now that '\' is gone */
2414 if (--op >= bufp && *op == ' ')
2415 *op = '\0';
2417 /* parse here */
2418 if (!parsesymbols(bufp)) {
2419 raw_printf("Error in SYMBOLS definition '%s'.\n", bufp);
2420 wait_synch();
2422 if (morelines) {
2423 do {
2424 *symbuf = '\0';
2425 if (!fgets(symbuf, BUFSZ, fp)) {
2426 morelines = FALSE;
2427 break;
2429 mungspaces(symbuf);
2430 bufp = symbuf;
2431 } while (*bufp == '#');
2433 } while (morelines);
2434 switch_symbols(TRUE);
2435 } else if (match_varname(buf, "WIZKIT", 6)) {
2436 (void) strncpy(wizkit, bufp, WIZKIT_MAX - 1);
2437 #ifdef AMIGA
2438 } else if (match_varname(buf, "FONT", 4)) {
2439 char *t;
2441 if (t = strchr(buf + 5, ':')) {
2442 *t = 0;
2443 amii_set_text_font(buf + 5, atoi(t + 1));
2444 *t = ':';
2446 } else if (match_varname(buf, "PATH", 4)) {
2447 (void) strncpy(PATH, bufp, PATHLEN - 1);
2448 } else if (match_varname(buf, "DEPTH", 5)) {
2449 extern int amii_numcolors;
2450 int val = atoi(bufp);
2452 amii_numcolors = 1L << min(DEPTH, val);
2453 #ifdef SYSFLAGS
2454 } else if (match_varname(buf, "DRIPENS", 7)) {
2455 int i, val;
2456 char *t;
2458 for (i = 0, t = strtok(bufp, ",/"); t != (char *) 0;
2459 i < 20 && (t = strtok((char *) 0, ",/")), ++i) {
2460 sscanf(t, "%d", &val);
2461 sysflags.amii_dripens[i] = val;
2463 #endif
2464 } else if (match_varname(buf, "SCREENMODE", 10)) {
2465 extern long amii_scrnmode;
2467 if (!stricmp(bufp, "req"))
2468 amii_scrnmode = 0xffffffff; /* Requester */
2469 else if (sscanf(bufp, "%x", &amii_scrnmode) != 1)
2470 amii_scrnmode = 0;
2471 } else if (match_varname(buf, "MSGPENS", 7)) {
2472 extern int amii_msgAPen, amii_msgBPen;
2473 char *t = strtok(bufp, ",/");
2475 if (t) {
2476 sscanf(t, "%d", &amii_msgAPen);
2477 if (t = strtok((char *) 0, ",/"))
2478 sscanf(t, "%d", &amii_msgBPen);
2480 } else if (match_varname(buf, "TEXTPENS", 8)) {
2481 extern int amii_textAPen, amii_textBPen;
2482 char *t = strtok(bufp, ",/");
2484 if (t) {
2485 sscanf(t, "%d", &amii_textAPen);
2486 if (t = strtok((char *) 0, ",/"))
2487 sscanf(t, "%d", &amii_textBPen);
2489 } else if (match_varname(buf, "MENUPENS", 8)) {
2490 extern int amii_menuAPen, amii_menuBPen;
2491 char *t = strtok(bufp, ",/");
2493 if (t) {
2494 sscanf(t, "%d", &amii_menuAPen);
2495 if (t = strtok((char *) 0, ",/"))
2496 sscanf(t, "%d", &amii_menuBPen);
2498 } else if (match_varname(buf, "STATUSPENS", 10)) {
2499 extern int amii_statAPen, amii_statBPen;
2500 char *t = strtok(bufp, ",/");
2502 if (t) {
2503 sscanf(t, "%d", &amii_statAPen);
2504 if (t = strtok((char *) 0, ",/"))
2505 sscanf(t, "%d", &amii_statBPen);
2507 } else if (match_varname(buf, "OTHERPENS", 9)) {
2508 extern int amii_otherAPen, amii_otherBPen;
2509 char *t = strtok(bufp, ",/");
2511 if (t) {
2512 sscanf(t, "%d", &amii_otherAPen);
2513 if (t = strtok((char *) 0, ",/"))
2514 sscanf(t, "%d", &amii_otherBPen);
2516 } else if (match_varname(buf, "PENS", 4)) {
2517 extern unsigned short amii_init_map[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, "%hx", &amii_init_map[i]);
2526 amii_setpens(amii_numcolors = i);
2527 } else if (match_varname(buf, "FGPENS", 6)) {
2528 extern int foreg[AMII_MAXCOLORS];
2529 int i;
2530 char *t;
2532 for (i = 0, t = strtok(bufp, ",/");
2533 i < AMII_MAXCOLORS && t != (char *) 0;
2534 t = strtok((char *) 0, ",/"), ++i) {
2535 sscanf(t, "%d", &foreg[i]);
2537 } else if (match_varname(buf, "BGPENS", 6)) {
2538 extern int backg[AMII_MAXCOLORS];
2539 int i;
2540 char *t;
2542 for (i = 0, t = strtok(bufp, ",/");
2543 i < AMII_MAXCOLORS && t != (char *) 0;
2544 t = strtok((char *) 0, ",/"), ++i) {
2545 sscanf(t, "%d", &backg[i]);
2547 #endif /*AMIGA*/
2548 #ifdef USER_SOUNDS
2549 } else if (match_varname(buf, "SOUNDDIR", 8)) {
2550 sounddir = dupstr(bufp);
2551 } else if (match_varname(buf, "SOUND", 5)) {
2552 add_sound_mapping(bufp);
2553 #endif
2554 #ifdef QT_GRAPHICS
2555 /* These should move to wc_ options */
2556 } else if (match_varname(buf, "QT_TILEWIDTH", 12)) {
2557 extern char *qt_tilewidth;
2559 if (qt_tilewidth == NULL)
2560 qt_tilewidth = dupstr(bufp);
2561 } else if (match_varname(buf, "QT_TILEHEIGHT", 13)) {
2562 extern char *qt_tileheight;
2564 if (qt_tileheight == NULL)
2565 qt_tileheight = dupstr(bufp);
2566 } else if (match_varname(buf, "QT_FONTSIZE", 11)) {
2567 extern char *qt_fontsize;
2569 if (qt_fontsize == NULL)
2570 qt_fontsize = dupstr(bufp);
2571 } else if (match_varname(buf, "QT_COMPACT", 10)) {
2572 extern int qt_compact_mode;
2574 qt_compact_mode = atoi(bufp);
2575 #endif
2576 } else
2577 return 0;
2578 return 1;
2581 #ifdef USER_SOUNDS
2582 boolean
2583 can_read_file(filename)
2584 const char *filename;
2586 return (boolean) (access(filename, 4) == 0);
2588 #endif /* USER_SOUNDS */
2590 boolean
2591 read_config_file(filename, src)
2592 const char *filename;
2593 int src;
2595 char buf[4 * BUFSZ];
2596 FILE *fp;
2597 boolean rv = TRUE; /* assume successful parse */
2599 if (!(fp = fopen_config_file(filename, src)))
2600 return FALSE;
2602 /* begin detection of duplicate configfile options */
2603 set_duplicate_opt_detection(1);
2605 while (fgets(buf, sizeof buf, fp)) {
2606 #ifdef notyet
2608 XXX Don't call read() in parse_config_line, read as callback or reassemble
2609 line at this level.
2610 OR: Forbid multiline stuff for alternate config sources.
2612 #endif
2613 if (!parse_config_line(fp, strip_newline(buf), src)) {
2614 static const char badoptionline[] = "Bad option line: \"%s\"";
2616 /* truncate buffer if it's long; this is actually conservative */
2617 if (strlen(buf) > BUFSZ - sizeof badoptionline)
2618 buf[BUFSZ - sizeof badoptionline] = '\0';
2620 raw_printf(badoptionline, buf);
2621 wait_synch();
2622 rv = FALSE;
2625 (void) fclose(fp);
2627 /* turn off detection of duplicate configfile options */
2628 set_duplicate_opt_detection(0);
2629 return rv;
2632 STATIC_OVL FILE *
2633 fopen_wizkit_file()
2635 FILE *fp;
2636 #if defined(VMS) || defined(UNIX)
2637 char tmp_wizkit[BUFSZ];
2638 #endif
2639 char *envp;
2641 envp = nh_getenv("WIZKIT");
2642 if (envp && *envp)
2643 (void) strncpy(wizkit, envp, WIZKIT_MAX - 1);
2644 if (!wizkit[0])
2645 return (FILE *) 0;
2647 #ifdef UNIX
2648 if (access(wizkit, 4) == -1) {
2649 /* 4 is R_OK on newer systems */
2650 /* nasty sneaky attempt to read file through
2651 * aNetHack's setuid permissions -- this is a
2652 * place a file name may be wholly under the player's
2653 * control
2655 raw_printf("Access to %s denied (%d).", wizkit, errno);
2656 wait_synch();
2657 /* fall through to standard names */
2658 } else
2659 #endif
2660 if ((fp = fopenp(wizkit, "r")) != (FILE *) 0) {
2661 return fp;
2662 #if defined(UNIX) || defined(VMS)
2663 } else {
2664 /* access() above probably caught most problems for UNIX */
2665 raw_printf("Couldn't open requested config file %s (%d).", wizkit,
2666 errno);
2667 wait_synch();
2668 #endif
2671 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
2672 if ((fp = fopenp(fqname(wizkit, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
2673 return fp;
2674 #else
2675 #ifdef VMS
2676 envp = nh_getenv("HOME");
2677 if (envp)
2678 Sprintf(tmp_wizkit, "%s%s", envp, wizkit);
2679 else
2680 Sprintf(tmp_wizkit, "%s%s", "sys$login:", wizkit);
2681 if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2682 return fp;
2683 #else /* should be only UNIX left */
2684 envp = nh_getenv("HOME");
2685 if (envp)
2686 Sprintf(tmp_wizkit, "%s/%s", envp, wizkit);
2687 else
2688 Strcpy(tmp_wizkit, wizkit);
2689 if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2690 return fp;
2691 else if (errno != ENOENT) {
2692 /* e.g., problems when setuid aNetHack can't search home
2693 * directory restricted to user */
2694 raw_printf("Couldn't open default wizkit file %s (%d).", tmp_wizkit,
2695 errno);
2696 wait_synch();
2698 #endif
2699 #endif
2700 return (FILE *) 0;
2703 /* add to hero's inventory if there's room, otherwise put item on floor */
2704 STATIC_DCL void
2705 wizkit_addinv(obj)
2706 struct obj *obj;
2708 if (!obj || obj == &zeroobj)
2709 return;
2711 /* subset of starting inventory pre-ID */
2712 obj->dknown = 1;
2713 if (Role_if(PM_PRIEST))
2714 obj->bknown = 1;
2715 /* same criteria as lift_object()'s check for available inventory slot */
2716 if (obj->oclass != COIN_CLASS && inv_cnt(FALSE) >= 52
2717 && !merge_choice(invent, obj)) {
2718 /* inventory overflow; can't just place & stack object since
2719 hero isn't in position yet, so schedule for arrival later */
2720 add_to_migration(obj);
2721 obj->ox = 0; /* index of main dungeon */
2722 obj->oy = 1; /* starting level number */
2723 obj->owornmask =
2724 (long) (MIGR_WITH_HERO | MIGR_NOBREAK | MIGR_NOSCATTER);
2725 } else {
2726 (void) addinv(obj);
2730 void
2731 read_wizkit()
2733 FILE *fp;
2734 char *ep, buf[BUFSZ];
2735 struct obj *otmp;
2736 boolean bad_items = FALSE, skip = FALSE;
2738 if (!wizard || !(fp = fopen_wizkit_file()))
2739 return;
2741 program_state.wizkit_wishing = 1;
2742 while (fgets(buf, (int) (sizeof buf), fp)) {
2743 ep = index(buf, '\n');
2744 if (skip) { /* in case previous line was too long */
2745 if (ep)
2746 skip = FALSE; /* found newline; next line is normal */
2747 } else {
2748 if (!ep)
2749 skip = TRUE; /* newline missing; discard next fgets */
2750 else
2751 *ep = '\0'; /* remove newline */
2753 if (buf[0]) {
2754 otmp = readobjnam(buf, (struct obj *) 0);
2755 if (otmp) {
2756 if (otmp != &zeroobj)
2757 wizkit_addinv(otmp);
2758 } else {
2759 /* .60 limits output line width to 79 chars */
2760 raw_printf("Bad wizkit item: \"%.60s\"", buf);
2761 bad_items = TRUE;
2766 program_state.wizkit_wishing = 0;
2767 if (bad_items)
2768 wait_synch();
2769 (void) fclose(fp);
2770 return;
2773 extern struct symsetentry *symset_list; /* options.c */
2774 extern struct symparse loadsyms[]; /* drawing.c */
2775 extern const char *known_handling[]; /* drawing.c */
2776 extern const char *known_restrictions[]; /* drawing.c */
2777 static int symset_count = 0; /* for pick-list building only */
2778 static boolean chosen_symset_start = FALSE, chosen_symset_end = FALSE;
2780 STATIC_OVL
2781 FILE *
2782 fopen_sym_file()
2784 FILE *fp;
2786 fp = fopen_datafile(SYMBOLS, "r", HACKPREFIX);
2787 return fp;
2791 * Returns 1 if the chose symset was found and loaded.
2792 * 0 if it wasn't found in the sym file or other problem.
2795 read_sym_file(which_set)
2796 int which_set;
2798 char buf[4 * BUFSZ];
2799 FILE *fp;
2801 if (!(fp = fopen_sym_file()))
2802 return 0;
2804 symset_count = 0;
2805 chosen_symset_start = chosen_symset_end = FALSE;
2806 while (fgets(buf, 4 * BUFSZ, fp)) {
2807 if (!parse_sym_line(buf, which_set)) {
2808 raw_printf("Bad symbol line: \"%.50s\"", buf);
2809 wait_synch();
2812 (void) fclose(fp);
2813 if (!chosen_symset_start && !chosen_symset_end) {
2814 /* name caller put in symset[which_set].name was not found;
2815 if it looks like "Default symbols", null it out and return
2816 success to use the default; otherwise, return failure */
2817 if (symset[which_set].name
2818 && (fuzzymatch(symset[which_set].name, "Default symbols",
2819 " -_", TRUE)
2820 || !strcmpi(symset[which_set].name, "default")))
2821 clear_symsetentry(which_set, TRUE);
2822 return (symset[which_set].name == 0) ? 1 : 0;
2824 if (!chosen_symset_end) {
2825 raw_printf("Missing finish for symset \"%s\"",
2826 symset[which_set].name ? symset[which_set].name
2827 : "unknown");
2828 wait_synch();
2830 return 1;
2833 /* returns 0 on error */
2835 parse_sym_line(buf, which_set)
2836 char *buf;
2837 int which_set;
2839 int val, i;
2840 struct symparse *symp = (struct symparse *) 0;
2841 char *bufp, *commentp, *altp;
2843 /* convert each instance of whitespace (tabs, consecutive spaces)
2844 into a single space; leading and trailing spaces are stripped */
2845 mungspaces(buf);
2846 if (!*buf || *buf == '#' || !strcmp(buf, " "))
2847 return 1;
2848 /* remove trailing comment, if any (this isn't strictly needed for
2849 individual symbols, and it won't matter if "X#comment" without
2850 separating space slips through; for handling or set description,
2851 symbol set creator is responsible for preceding '#' with a space
2852 and that comment itself doesn't contain " #") */
2853 if ((commentp = rindex(buf, '#')) != 0 && commentp[-1] == ' ')
2854 commentp[-1] = '\0';
2856 /* find the '=' or ':' */
2857 bufp = index(buf, '=');
2858 altp = index(buf, ':');
2859 if (!bufp || (altp && altp < bufp))
2860 bufp = altp;
2861 if (!bufp) {
2862 if (strncmpi(buf, "finish", 6) == 0) {
2863 /* end current graphics set */
2864 if (chosen_symset_start)
2865 chosen_symset_end = TRUE;
2866 chosen_symset_start = FALSE;
2867 return 1;
2869 return 0;
2871 /* skip '=' and space which follows, if any */
2872 ++bufp;
2873 if (*bufp == ' ')
2874 ++bufp;
2876 symp = match_sym(buf);
2877 if (!symp)
2878 return 0;
2880 if (!symset[which_set].name) {
2881 /* A null symset name indicates that we're just
2882 building a pick-list of possible symset
2883 values from the file, so only do that */
2884 if (symp->range == SYM_CONTROL) {
2885 struct symsetentry *tmpsp;
2887 switch (symp->idx) {
2888 case 0:
2889 tmpsp =
2890 (struct symsetentry *) alloc(sizeof (struct symsetentry));
2891 tmpsp->next = (struct symsetentry *) 0;
2892 if (!symset_list) {
2893 symset_list = tmpsp;
2894 symset_count = 0;
2895 } else {
2896 symset_count++;
2897 tmpsp->next = symset_list;
2898 symset_list = tmpsp;
2900 tmpsp->idx = symset_count;
2901 tmpsp->name = dupstr(bufp);
2902 tmpsp->desc = (char *) 0;
2903 tmpsp->nocolor = 0;
2904 /* initialize restriction bits */
2905 tmpsp->primary = 0;
2906 tmpsp->rogue = 0;
2907 break;
2908 case 2:
2909 /* handler type identified */
2910 tmpsp = symset_list; /* most recent symset */
2911 tmpsp->handling = H_UNK;
2912 i = 0;
2913 while (known_handling[i]) {
2914 if (!strcmpi(known_handling[i], bufp)) {
2915 tmpsp->handling = i;
2916 break; /* while loop */
2918 i++;
2920 break;
2921 case 3: /* description:something */
2922 tmpsp = symset_list; /* most recent symset */
2923 if (tmpsp && !tmpsp->desc)
2924 tmpsp->desc = dupstr(bufp);
2925 break;
2926 case 5:
2927 /* restrictions: xxxx*/
2928 tmpsp = symset_list; /* most recent symset */
2929 for (i = 0; known_restrictions[i]; ++i) {
2930 if (!strcmpi(known_restrictions[i], bufp)) {
2931 switch (i) {
2932 case 0:
2933 tmpsp->primary = 1;
2934 break;
2935 case 1:
2936 tmpsp->rogue = 1;
2937 break;
2939 break; /* while loop */
2942 break;
2945 return 1;
2947 if (symp->range) {
2948 if (symp->range == SYM_CONTROL) {
2949 switch (symp->idx) {
2950 case 0:
2951 /* start of symset */
2952 if (!strcmpi(bufp, symset[which_set].name)) {
2953 /* matches desired one */
2954 chosen_symset_start = TRUE;
2955 /* these init_*() functions clear symset fields too */
2956 if (which_set == ROGUESET)
2957 init_r_symbols();
2958 else if (which_set == PRIMARY)
2959 init_l_symbols();
2961 break;
2962 case 1:
2963 /* finish symset */
2964 if (chosen_symset_start)
2965 chosen_symset_end = TRUE;
2966 chosen_symset_start = FALSE;
2967 break;
2968 case 2:
2969 /* handler type identified */
2970 if (chosen_symset_start)
2971 set_symhandling(bufp, which_set);
2972 break;
2973 /* case 3: (description) is ignored here */
2974 case 4: /* color:off */
2975 if (chosen_symset_start) {
2976 if (bufp) {
2977 if (!strcmpi(bufp, "true") || !strcmpi(bufp, "yes")
2978 || !strcmpi(bufp, "on"))
2979 symset[which_set].nocolor = 0;
2980 else if (!strcmpi(bufp, "false")
2981 || !strcmpi(bufp, "no")
2982 || !strcmpi(bufp, "off"))
2983 symset[which_set].nocolor = 1;
2986 break;
2987 case 5: /* restrictions: xxxx*/
2988 if (chosen_symset_start) {
2989 int n = 0;
2991 while (known_restrictions[n]) {
2992 if (!strcmpi(known_restrictions[n], bufp)) {
2993 switch (n) {
2994 case 0:
2995 symset[which_set].primary = 1;
2996 break;
2997 case 1:
2998 symset[which_set].rogue = 1;
2999 break;
3001 break; /* while loop */
3003 n++;
3006 break;
3008 } else { /* !SYM_CONTROL */
3009 val = sym_val(bufp);
3010 if (chosen_symset_start) {
3011 if (which_set == PRIMARY) {
3012 update_l_symset(symp, val);
3013 } else if (which_set == ROGUESET) {
3014 update_r_symset(symp, val);
3019 return 1;
3022 STATIC_OVL void
3023 set_symhandling(handling, which_set)
3024 char *handling;
3025 int which_set;
3027 int i = 0;
3029 symset[which_set].handling = H_UNK;
3030 while (known_handling[i]) {
3031 if (!strcmpi(known_handling[i], handling)) {
3032 symset[which_set].handling = i;
3033 return;
3035 i++;
3039 /* ---------- END CONFIG FILE HANDLING ----------- */
3041 /* ---------- BEGIN SCOREBOARD CREATION ----------- */
3043 #ifdef OS2_CODEVIEW
3044 #define UNUSED_if_not_OS2_CODEVIEW /*empty*/
3045 #else
3046 #define UNUSED_if_not_OS2_CODEVIEW UNUSED
3047 #endif
3049 /* verify that we can write to scoreboard file; if not, try to create one */
3050 /*ARGUSED*/
3051 void
3052 check_recordfile(dir)
3053 const char *dir UNUSED_if_not_OS2_CODEVIEW;
3055 #if defined(PRAGMA_UNUSED) && !defined(OS2_CODEVIEW)
3056 #pragma unused(dir)
3057 #endif
3058 const char *fq_record;
3059 int fd;
3061 #if defined(UNIX) || defined(VMS)
3062 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3063 fd = open(fq_record, O_RDWR, 0);
3064 if (fd >= 0) {
3065 #ifdef VMS /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */
3066 if (!file_is_stmlf(fd)) {
3067 raw_printf(
3068 "Warning: scoreboard file %s is not in stream_lf format",
3069 fq_record);
3070 wait_synch();
3072 #endif
3073 (void) nhclose(fd); /* RECORD is accessible */
3074 } else if ((fd = open(fq_record, O_CREAT | O_RDWR, FCMASK)) >= 0) {
3075 (void) nhclose(fd); /* RECORD newly created */
3076 #if defined(VMS) && !defined(SECURE)
3077 /* Re-protect RECORD with world:read+write+execute+delete access. */
3078 (void) chmod(fq_record, FCMASK | 007);
3079 #endif /* VMS && !SECURE */
3080 } else {
3081 raw_printf("Warning: cannot write scoreboard file %s", fq_record);
3082 wait_synch();
3084 #endif /* !UNIX && !VMS */
3085 #if defined(MICRO) || defined(WIN32)
3086 char tmp[PATHLEN];
3088 #ifdef OS2_CODEVIEW /* explicit path on opening for OS/2 */
3089 /* how does this work when there isn't an explicit path or fopenp
3090 * for later access to the file via fopen_datafile? ? */
3091 (void) strncpy(tmp, dir, PATHLEN - 1);
3092 tmp[PATHLEN - 1] = '\0';
3093 if ((strlen(tmp) + 1 + strlen(RECORD)) < (PATHLEN - 1)) {
3094 append_slash(tmp);
3095 Strcat(tmp, RECORD);
3097 fq_record = tmp;
3098 #else
3099 Strcpy(tmp, RECORD);
3100 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3101 #endif
3103 if ((fd = open(fq_record, O_RDWR)) < 0) {
3104 /* try to create empty record */
3105 #if defined(AZTEC_C) || defined(_DCC) \
3106 || (defined(__GNUC__) && defined(__AMIGA__))
3107 /* Aztec doesn't use the third argument */
3108 /* DICE doesn't like it */
3109 if ((fd = open(fq_record, O_CREAT | O_RDWR)) < 0) {
3110 #else
3111 if ((fd = open(fq_record, O_CREAT | O_RDWR, S_IREAD | S_IWRITE))
3112 < 0) {
3113 #endif
3114 raw_printf("Warning: cannot write record %s", tmp);
3115 wait_synch();
3116 } else
3117 (void) nhclose(fd);
3118 } else /* open succeeded */
3119 (void) nhclose(fd);
3120 #else /* MICRO || WIN32*/
3122 #ifdef MAC
3123 /* Create the "record" file, if necessary */
3124 fq_record = fqname(RECORD, SCOREPREFIX, 0);
3125 fd = macopen(fq_record, O_RDWR | O_CREAT, TEXT_TYPE);
3126 if (fd != -1)
3127 macclose(fd);
3128 #endif /* MAC */
3130 #endif /* MICRO || WIN32*/
3133 /* ---------- END SCOREBOARD CREATION ----------- */
3135 /* ---------- BEGIN PANIC/IMPOSSIBLE LOG ----------- */
3137 /*ARGSUSED*/
3138 void
3139 paniclog(type, reason)
3140 const char *type; /* panic, impossible, trickery */
3141 const char *reason; /* explanation */
3143 #ifdef PANICLOG
3144 FILE *lfile;
3145 char buf[BUFSZ];
3147 if (!program_state.in_paniclog) {
3148 program_state.in_paniclog = 1;
3149 lfile = fopen_datafile(PANICLOG, "a", TROUBLEPREFIX);
3150 if (lfile) {
3151 #ifdef PANICLOG_FMT2
3152 (void) fprintf(lfile, "%ld %s: %s %s\n",
3153 ubirthday, (plname ? plname : "(none)"),
3154 type, reason);
3155 #else
3156 time_t now = getnow();
3157 int uid = getuid();
3158 char playmode = wizard ? 'D' : discover ? 'X' : '-';
3160 (void) fprintf(lfile, "%s %08ld %06ld %d %c: %s %s\n",
3161 version_string(buf), yyyymmdd(now), hhmmss(now),
3162 uid, playmode, type, reason);
3163 #endif /* !PANICLOG_FMT2 */
3164 (void) fclose(lfile);
3166 program_state.in_paniclog = 0;
3168 #endif /* PANICLOG */
3169 return;
3172 /* ---------- END PANIC/IMPOSSIBLE LOG ----------- */
3174 #ifdef SELF_RECOVER
3176 /* ---------- BEGIN INTERNAL RECOVER ----------- */
3177 boolean
3178 recover_savefile()
3180 int gfd, lfd, sfd;
3181 int lev, savelev, hpid, pltmpsiz;
3182 xchar levc;
3183 struct version_info version_data;
3184 int processed[256];
3185 char savename[SAVESIZE], errbuf[BUFSZ];
3186 struct savefile_info sfi;
3187 char tmpplbuf[PL_NSIZ];
3189 for (lev = 0; lev < 256; lev++)
3190 processed[lev] = 0;
3192 /* level 0 file contains:
3193 * pid of creating process (ignored here)
3194 * level number for current level of save file
3195 * name of save file anethack would have created
3196 * savefile info
3197 * player name
3198 * and game state
3200 gfd = open_levelfile(0, errbuf);
3201 if (gfd < 0) {
3202 raw_printf("%s\n", errbuf);
3203 return FALSE;
3205 if (read(gfd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) {
3206 raw_printf("\n%s\n%s\n",
3207 "Checkpoint data incompletely written or subsequently clobbered.",
3208 "Recovery impossible.");
3209 (void) nhclose(gfd);
3210 return FALSE;
3212 if (read(gfd, (genericptr_t) &savelev, sizeof(savelev))
3213 != sizeof(savelev)) {
3214 raw_printf(
3215 "\nCheckpointing was not in effect for %s -- recovery impossible.\n",
3216 lock);
3217 (void) nhclose(gfd);
3218 return FALSE;
3220 if ((read(gfd, (genericptr_t) savename, sizeof savename)
3221 != sizeof savename)
3222 || (read(gfd, (genericptr_t) &version_data, sizeof version_data)
3223 != sizeof version_data)
3224 || (read(gfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi)
3225 || (read(gfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3226 != sizeof pltmpsiz) || (pltmpsiz > PL_NSIZ)
3227 || (read(gfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz)) {
3228 raw_printf("\nError reading %s -- can't recover.\n", lock);
3229 (void) nhclose(gfd);
3230 return FALSE;
3233 /* save file should contain:
3234 * version info
3235 * savefile info
3236 * player name
3237 * current level (including pets)
3238 * (non-level-based) game state
3239 * other levels
3241 set_savefile_name(TRUE);
3242 sfd = create_savefile();
3243 if (sfd < 0) {
3244 raw_printf("\nCannot recover savefile %s.\n", SAVEF);
3245 (void) nhclose(gfd);
3246 return FALSE;
3249 lfd = open_levelfile(savelev, errbuf);
3250 if (lfd < 0) {
3251 raw_printf("\n%s\n", errbuf);
3252 (void) nhclose(gfd);
3253 (void) nhclose(sfd);
3254 delete_savefile();
3255 return FALSE;
3258 if (write(sfd, (genericptr_t) &version_data, sizeof version_data)
3259 != sizeof version_data) {
3260 raw_printf("\nError writing %s; recovery failed.", SAVEF);
3261 (void) nhclose(gfd);
3262 (void) nhclose(sfd);
3263 delete_savefile();
3264 return FALSE;
3267 if (write(sfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi) {
3268 raw_printf("\nError writing %s; recovery failed (savefile_info).\n",
3269 SAVEF);
3270 (void) nhclose(gfd);
3271 (void) nhclose(sfd);
3272 delete_savefile();
3273 return FALSE;
3276 if (write(sfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3277 != sizeof pltmpsiz) {
3278 raw_printf("Error writing %s; recovery failed (player name size).\n",
3279 SAVEF);
3280 (void) nhclose(gfd);
3281 (void) nhclose(sfd);
3282 delete_savefile();
3283 return FALSE;
3286 if (write(sfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz) {
3287 raw_printf("Error writing %s; recovery failed (player name).\n",
3288 SAVEF);
3289 (void) nhclose(gfd);
3290 (void) nhclose(sfd);
3291 delete_savefile();
3292 return FALSE;
3295 if (!copy_bytes(lfd, sfd)) {
3296 (void) nhclose(lfd);
3297 (void) nhclose(sfd);
3298 delete_savefile();
3299 return FALSE;
3301 (void) nhclose(lfd);
3302 processed[savelev] = 1;
3304 if (!copy_bytes(gfd, sfd)) {
3305 (void) nhclose(lfd);
3306 (void) nhclose(sfd);
3307 delete_savefile();
3308 return FALSE;
3310 (void) nhclose(gfd);
3311 processed[0] = 1;
3313 for (lev = 1; lev < 256; lev++) {
3314 /* level numbers are kept in xchars in save.c, so the
3315 * maximum level number (for the endlevel) must be < 256
3317 if (lev != savelev) {
3318 lfd = open_levelfile(lev, (char *) 0);
3319 if (lfd >= 0) {
3320 /* any or all of these may not exist */
3321 levc = (xchar) lev;
3322 write(sfd, (genericptr_t) &levc, sizeof(levc));
3323 if (!copy_bytes(lfd, sfd)) {
3324 (void) nhclose(lfd);
3325 (void) nhclose(sfd);
3326 delete_savefile();
3327 return FALSE;
3329 (void) nhclose(lfd);
3330 processed[lev] = 1;
3334 (void) nhclose(sfd);
3336 #ifdef HOLD_LOCKFILE_OPEN
3337 really_close();
3338 #endif
3340 * We have a successful savefile!
3341 * Only now do we erase the level files.
3343 for (lev = 0; lev < 256; lev++) {
3344 if (processed[lev]) {
3345 const char *fq_lock;
3346 set_levelfile_name(lock, lev);
3347 fq_lock = fqname(lock, LEVELPREFIX, 3);
3348 (void) unlink(fq_lock);
3351 return TRUE;
3354 boolean
3355 copy_bytes(ifd, ofd)
3356 int ifd, ofd;
3358 char buf[BUFSIZ];
3359 int nfrom, nto;
3361 do {
3362 nfrom = read(ifd, buf, BUFSIZ);
3363 nto = write(ofd, buf, nfrom);
3364 if (nto != nfrom)
3365 return FALSE;
3366 } while (nfrom == BUFSIZ);
3367 return TRUE;
3370 /* ---------- END INTERNAL RECOVER ----------- */
3371 #endif /*SELF_RECOVER*/
3373 /* ---------- OTHER ----------- */
3375 #ifdef SYSCF
3376 #ifdef SYSCF_FILE
3377 void
3378 assure_syscf_file()
3380 int fd;
3383 * All we really care about is the end result - can we read the file?
3384 * So just check that directly.
3386 * Not tested on most of the old platforms (which don't attempt
3387 * to implement SYSCF).
3388 * Some ports don't like open()'s optional third argument;
3389 * VMS overrides open() usage with a macro which requires it.
3391 #ifndef VMS
3392 # if defined(NOCWD_ASSUMPTIONS) && defined(WIN32)
3393 fd = open(fqname(SYSCF_FILE, SYSCONFPREFIX, 0), O_RDONLY);
3394 # else
3395 fd = open(SYSCF_FILE, O_RDONLY);
3396 # endif
3397 #else
3398 fd = open(SYSCF_FILE, O_RDONLY, 0);
3399 #endif
3400 if (fd >= 0) {
3401 /* readable */
3402 close(fd);
3403 return;
3405 raw_printf("Unable to open SYSCF_FILE.\n");
3406 exit(EXIT_FAILURE);
3409 #endif /* SYSCF_FILE */
3410 #endif /* SYSCF */
3412 #ifdef DEBUG
3413 /* used by debugpline() to decide whether to issue a message
3414 * from a particular source file; caller passes __FILE__ and we check
3415 * whether it is in the source file list supplied by SYSCF's DEBUGFILES
3417 * pass FALSE to override wildcard matching; useful for files
3418 * like dungeon.c and questpgr.c, which generate a ridiculous amount of
3419 * output if DEBUG is defined and effectively block the use of a wildcard */
3420 boolean
3421 debugcore(filename, wildcards)
3422 const char *filename;
3423 boolean wildcards;
3425 const char *debugfiles, *p;
3427 if (!filename || !*filename)
3428 return FALSE; /* sanity precaution */
3430 if (sysopt.env_dbgfl == 0) {
3431 /* check once for DEBUGFILES in the environment;
3432 if found, it supersedes the sysconf value
3433 [note: getenv() rather than nh_getenv() since a long value
3434 is valid and doesn't pose any sort of overflow risk here] */
3435 if ((p = getenv("DEBUGFILES")) != 0) {
3436 if (sysopt.debugfiles)
3437 free((genericptr_t) sysopt.debugfiles);
3438 sysopt.debugfiles = dupstr(p);
3439 sysopt.env_dbgfl = 1;
3440 } else
3441 sysopt.env_dbgfl = -1;
3444 debugfiles = sysopt.debugfiles;
3445 /* usual case: sysopt.debugfiles will be empty */
3446 if (!debugfiles || !*debugfiles)
3447 return FALSE;
3449 /* strip filename's path if present */
3450 #ifdef UNIX
3451 if ((p = rindex(filename, '/')) != 0)
3452 filename = p + 1;
3453 #endif
3454 #ifdef VMS
3455 filename = vms_basename(filename);
3456 /* vms_basename strips off 'type' suffix as well as path and version;
3457 we want to put suffix back (".c" assumed); since it always returns
3458 a pointer to a static buffer, we can safely modify its result */
3459 Strcat((char *) filename, ".c");
3460 #endif
3463 * Wildcard match will only work if there's a single pattern (which
3464 * might be a single file name without any wildcarding) rather than
3465 * a space-separated list.
3466 * [to NOT do: We could step through the space-separated list and
3467 * attempt a wildcard match against each element, but that would be
3468 * overkill for the intended usage.]
3470 if (wildcards && pmatch(debugfiles, filename))
3471 return TRUE;
3473 /* check whether filename is an element of the list */
3474 if ((p = strstr(debugfiles, filename)) != 0) {
3475 int l = (int) strlen(filename);
3477 if ((p == debugfiles || p[-1] == ' ' || p[-1] == '/')
3478 && (p[l] == ' ' || p[l] == '\0'))
3479 return TRUE;
3481 return FALSE;
3484 #endif /*DEBUG*/
3486 /* ---------- BEGIN TRIBUTE ----------- */
3488 /* 3.6 tribute code
3491 #define SECTIONSCOPE 1
3492 #define TITLESCOPE 2
3493 #define PASSAGESCOPE 3
3495 #define MAXPASSAGES SIZE(context.novel.pasg) /* 20 */
3497 static int FDECL(choose_passage, (int, unsigned));
3499 /* choose a random passage that hasn't been chosen yet; once all have
3500 been chosen, reset the tracking to make all passages available again */
3501 static int
3502 choose_passage(passagecnt, oid)
3503 int passagecnt; /* total of available passages */
3504 unsigned oid; /* book.o_id, used to determine whether re-reading same book */
3506 int idx, res;
3508 if (passagecnt < 1)
3509 return 0;
3511 /* if a different book or we've used up all the passages already,
3512 reset in order to have all 'passagecnt' passages available */
3513 if (oid != context.novel.id || context.novel.count == 0) {
3514 int i, range = passagecnt, limit = MAXPASSAGES;
3516 context.novel.id = oid;
3517 if (range <= limit) {
3518 /* collect all of the N indices */
3519 context.novel.count = passagecnt;
3520 for (idx = 0; idx < MAXPASSAGES; idx++)
3521 context.novel.pasg[idx] = (xchar) ((idx < passagecnt)
3522 ? idx + 1 : 0);
3523 } else {
3524 /* collect MAXPASSAGES of the N indices */
3525 context.novel.count = MAXPASSAGES;
3526 for (idx = i = 0; i < passagecnt; ++i, --range)
3527 if (range > 0 && rn2(range) < limit) {
3528 context.novel.pasg[idx++] = (xchar) (i + 1);
3529 --limit;
3534 idx = rn2(context.novel.count);
3535 res = (int) context.novel.pasg[idx];
3536 /* move the last slot's passage index into the slot just used
3537 and reduce the number of passages available */
3538 context.novel.pasg[idx] = context.novel.pasg[--context.novel.count];
3539 return res;
3542 /* Returns True if you were able to read something. */
3543 boolean
3544 read_tribute(tribsection, tribtitle, tribpassage, nowin_buf, bufsz, oid)
3545 const char *tribsection, *tribtitle;
3546 int tribpassage, bufsz;
3547 char *nowin_buf;
3548 unsigned oid; /* book identifier */
3550 dlb *fp;
3551 char line[BUFSZ], lastline[BUFSZ];
3553 int scope = 0;
3554 int linect = 0, passagecnt = 0, targetpassage = 0;
3555 const char *badtranslation = "an incomprehensible foreign translation";
3556 boolean matchedsection = FALSE, matchedtitle = FALSE;
3557 winid tribwin = WIN_ERR;
3558 boolean grasped = FALSE;
3559 boolean foundpassage = FALSE;
3561 if (nowin_buf)
3562 *nowin_buf = '\0';
3564 /* check for mandatories */
3565 if (!tribsection || !tribtitle) {
3566 if (!nowin_buf)
3567 pline("It's %s of \"%s\"!", badtranslation, tribtitle);
3568 return grasped;
3571 debugpline3("read_tribute %s, %s, %d.", tribsection, tribtitle,
3572 tribpassage);
3574 fp = dlb_fopen(TRIBUTEFILE, "r");
3575 if (!fp) {
3576 /* this is actually an error - cannot open tribute file! */
3577 if (!nowin_buf)
3578 pline("You feel too overwhelmed to continue!");
3579 return grasped;
3583 * Syntax (not case-sensitive):
3584 * %section books
3586 * In the books section:
3587 * %title booktitle (n)
3588 * where booktitle=book title without quotes
3589 * (n)= total number of passages present for this title
3590 * %passage k
3591 * where k=sequential passage number
3593 * %e ends the passage/book/section
3594 * If in a passage, it marks the end of that passage.
3595 * If in a book, it marks the end of that book.
3596 * If in a section, it marks the end of that section.
3598 * %section death
3601 *line = *lastline = '\0';
3602 while (dlb_fgets(line, sizeof line, fp) != 0) {
3603 linect++;
3604 (void) strip_newline(line);
3605 switch (line[0]) {
3606 case '%':
3607 if (!strncmpi(&line[1], "section ", sizeof "section " - 1)) {
3608 char *st = &line[9]; /* 9 from "%section " */
3610 scope = SECTIONSCOPE;
3611 matchedsection = !strcmpi(st, tribsection) ? TRUE : FALSE;
3612 } else if (!strncmpi(&line[1], "title ", sizeof "title " - 1)) {
3613 char *st = &line[7]; /* 7 from "%title " */
3614 char *p1, *p2;
3616 if ((p1 = index(st, '(')) != 0) {
3617 *p1++ = '\0';
3618 (void) mungspaces(st);
3619 if ((p2 = index(p1, ')')) != 0) {
3620 *p2 = '\0';
3621 passagecnt = atoi(p1);
3622 scope = TITLESCOPE;
3623 if (matchedsection && !strcmpi(st, tribtitle)) {
3624 matchedtitle = TRUE;
3625 targetpassage = !tribpassage
3626 ? choose_passage(passagecnt, oid)
3627 : (tribpassage <= passagecnt)
3628 ? tribpassage : 0;
3629 } else {
3630 matchedtitle = FALSE;
3634 } else if (!strncmpi(&line[1], "passage ",
3635 sizeof "passage " - 1)) {
3636 int passagenum = 0;
3637 char *st = &line[9]; /* 9 from "%passage " */
3639 mungspaces(st);
3640 passagenum = atoi(st);
3641 if (passagenum > 0 && passagenum <= passagecnt) {
3642 scope = PASSAGESCOPE;
3643 if (matchedtitle && passagenum == targetpassage) {
3644 foundpassage = TRUE;
3645 if (!nowin_buf) {
3646 tribwin = create_nhwindow(NHW_MENU);
3647 if (tribwin == WIN_ERR)
3648 goto cleanup;
3652 } else if (!strncmpi(&line[1], "e ", sizeof "e " - 1)) {
3653 if (foundpassage)
3654 goto cleanup;
3655 if (scope == TITLESCOPE)
3656 matchedtitle = FALSE;
3657 if (scope == SECTIONSCOPE)
3658 matchedsection = FALSE;
3659 if (scope)
3660 --scope;
3661 } else {
3662 debugpline1("tribute file error: bad %% command, line %d.",
3663 linect);
3665 break;
3666 case '#':
3667 /* comment only, next! */
3668 break;
3669 default:
3670 if (foundpassage) {
3671 if (!nowin_buf) {
3672 /* outputting multi-line passage to text window */
3673 putstr(tribwin, 0, line);
3674 if (*line)
3675 Strcpy(lastline, line);
3676 } else {
3677 /* fetching one-line passage into buffer */
3678 copynchars(nowin_buf, line, bufsz - 1);
3679 goto cleanup; /* don't wait for "%e passage" */
3685 cleanup:
3686 (void) dlb_fclose(fp);
3687 if (nowin_buf) {
3688 /* one-line buffer */
3689 grasped = *nowin_buf ? TRUE : FALSE;
3690 } else {
3691 if (tribwin != WIN_ERR) { /* implies 'foundpassage' */
3692 /* multi-line window, normal case;
3693 if lastline is empty, there were no non-empty lines between
3694 "%passage n" and "%e passage" so we leave 'grasped' False */
3695 if (*lastline) {
3696 display_nhwindow(tribwin, FALSE);
3697 /* put the final attribution line into message history,
3698 analogous to the summary line from long quest messages */
3699 if (index(lastline, '['))
3700 mungspaces(lastline); /* to remove leading spaces */
3701 else /* construct one if necessary */
3702 Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle);
3703 putmsghistory(lastline, FALSE);
3704 grasped = TRUE;
3706 destroy_nhwindow(tribwin);
3708 if (!grasped)
3709 /* multi-line window, problem */
3710 pline("It seems to be %s of \"%s\"!", badtranslation, tribtitle);
3712 return grasped;
3715 boolean
3716 Death_quote(buf, bufsz)
3717 char *buf;
3718 int bufsz;
3720 unsigned death_oid = 1; /* chance of oid #1 being a novel is negligible */
3722 return read_tribute("Death", "Death Quotes", 0, buf, bufsz, death_oid);
3725 /* ---------- END TRIBUTE ----------- */
3727 /*files.c*/