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