OSDN Git Service

Initial Import
[nethackexpress/trunk.git] / src / files.c
1 /*      SCCS Id: @(#)files.c    3.4     2003/11/14      */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed.  See license for details. */
4
5 #include "hack.h"
6 #include "dlb.h"
7
8 #ifdef TTY_GRAPHICS
9 #include "wintty.h" /* more() */
10 #endif
11
12 #include <ctype.h>
13
14 #if !defined(MAC) && !defined(O_WRONLY) && !defined(AZTEC_C)
15 #include <fcntl.h>
16 #endif
17
18 #include <errno.h>
19 #ifdef _MSC_VER /* MSC 6.0 defines errno quite differently */
20 # if (_MSC_VER >= 600)
21 #  define SKIP_ERRNO
22 # endif
23 #else
24 # ifdef NHSTDC
25 #  define SKIP_ERRNO
26 # endif
27 #endif
28 #ifndef SKIP_ERRNO
29 # ifdef _DCC
30 const
31 # endif
32 extern int errno;
33 #endif
34
35 #if defined(UNIX) && defined(QT_GRAPHICS)
36 #include <dirent.h>
37 #endif
38
39 #if defined(UNIX) || defined(VMS)
40 #include <signal.h>
41 #endif
42
43 #if defined(MSDOS) || defined(OS2) || defined(TOS) || defined(WIN32)
44 # ifndef GNUDOS
45 #include <sys\stat.h>
46 # else
47 #include <sys/stat.h>
48 # endif
49 #endif
50 #ifndef O_BINARY        /* used for micros, no-op for others */
51 # define O_BINARY 0
52 #endif
53
54 #ifdef PREFIXES_IN_USE
55 #define FQN_NUMBUF 4
56 static char fqn_filename_buffer[FQN_NUMBUF][FQN_MAX_FILENAME];
57 #endif
58
59 #if !defined(MFLOPPY) && !defined(VMS) && !defined(WIN32)
60 char bones[] = "bonesnn.xxx";
61 char lock[PL_NSIZ+14] = "1lock"; /* long enough for uid+name+.99 */
62 #else
63 # if defined(MFLOPPY)
64 char bones[FILENAME];           /* pathname of bones files */
65 char lock[FILENAME];            /* pathname of level files */
66 # endif
67 # if defined(VMS)
68 char bones[] = "bonesnn.xxx;1";
69 char lock[PL_NSIZ+17] = "1lock"; /* long enough for _uid+name+.99;1 */
70 # endif
71 # if defined(WIN32)
72 char bones[] = "bonesnn.xxx";
73 char lock[PL_NSIZ+25];          /* long enough for username+-+name+.99 */
74 # endif
75 #endif
76
77 #if defined(UNIX) || defined(__BEOS__)
78 #define SAVESIZE        (PL_NSIZ + 13)  /* save/99999player.e */
79 #else
80 # ifdef VMS
81 #define SAVESIZE        (PL_NSIZ + 22)  /* [.save]<uid>player.e;1 */
82 # else
83 #  if defined(WIN32)
84 #define SAVESIZE        (PL_NSIZ + 40)  /* username-player.NetHack-saved-game */
85 #  else
86 #define SAVESIZE        FILENAME        /* from macconf.h or pcconf.h */
87 #  endif
88 # endif
89 #endif
90
91 char SAVEF[SAVESIZE];   /* holds relative path of save file from playground */
92 #ifdef MICRO
93 char SAVEP[SAVESIZE];   /* holds path of directory for save file */
94 #endif
95
96 #ifdef HOLD_LOCKFILE_OPEN
97 struct level_ftrack {
98 int init;
99 int fd;                                 /* file descriptor for level file     */
100 int oflag;                              /* open flags                         */
101 boolean nethack_thinks_it_is_open;      /* Does NetHack think it's open?       */
102 } lftrack;
103 # if defined(WIN32)
104 #include <share.h>
105 # endif
106 #endif /*HOLD_LOCKFILE_OPEN*/
107
108 #ifdef WIZARD
109 #define WIZKIT_MAX 128
110 static char wizkit[WIZKIT_MAX];
111 STATIC_DCL FILE *NDECL(fopen_wizkit_file);
112 #endif
113
114 #ifdef AMIGA
115 extern char PATH[];     /* see sys/amiga/amidos.c */
116 extern char bbs_id[];
117 static int lockptr;
118 # ifdef __SASC_60
119 #include <proto/dos.h>
120 # endif
121
122 #include <libraries/dos.h>
123 extern void FDECL(amii_set_text_font, ( char *, int ));
124 #endif
125
126 #if defined(WIN32) || defined(MSDOS)
127 static int lockptr;
128 # ifdef MSDOS
129 #define Delay(a) msleep(a)
130 # endif
131 #define Close close
132 #ifndef WIN_CE
133 #define DeleteFile unlink
134 #endif
135 #endif
136
137 #ifdef MAC
138 # define unlink macunlink
139 #endif
140
141 #ifdef USER_SOUNDS
142 extern char *sounddir;
143 #endif
144
145 extern int n_dgns;              /* from dungeon.c */
146
147 STATIC_DCL char *FDECL(set_bonesfile_name, (char *,d_level*));
148 STATIC_DCL char *NDECL(set_bonestemp_name);
149 #ifdef COMPRESS
150 STATIC_DCL void FDECL(redirect, (const char *,const char *,FILE *,BOOLEAN_P));
151 STATIC_DCL void FDECL(docompress_file, (const char *,BOOLEAN_P));
152 #endif
153 STATIC_DCL char *FDECL(make_lockname, (const char *,char *));
154 STATIC_DCL FILE *FDECL(fopen_config_file, (const char *));
155 STATIC_DCL int FDECL(get_uchars, (FILE *,char *,char *,uchar *,BOOLEAN_P,int,const char *));
156 int FDECL(parse_config_line, (FILE *,char *,char *,char *));
157 #ifdef NOCWD_ASSUMPTIONS
158 STATIC_DCL void FDECL(adjust_prefix, (char *, int));
159 #endif
160 #ifdef SELF_RECOVER
161 STATIC_DCL boolean FDECL(copy_bytes, (int, int));
162 #endif
163 #ifdef HOLD_LOCKFILE_OPEN
164 STATIC_DCL int FDECL(open_levelfile_exclusively, (const char *, int, int));
165 #endif
166
167 /*
168  * fname_encode()
169  *
170  *   Args:
171  *      legal           zero-terminated list of acceptable file name characters
172  *      quotechar       lead-in character used to quote illegal characters as hex digits
173  *      s               string to encode
174  *      callerbuf       buffer to house result
175  *      bufsz           size of callerbuf
176  *
177  *   Notes:
178  *      The hex digits 0-9 and A-F are always part of the legal set due to
179  *      their use in the encoding scheme, even if not explicitly included in 'legal'.
180  *
181  *   Sample:
182  *      The following call:
183  *          (void)fname_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
184  *                              '%', "This is a % test!", buf, 512);
185  *      results in this encoding:
186  *          "This%20is%20a%20%25%20test%21"
187  */
188 char *
189 fname_encode(legal, quotechar, s, callerbuf, bufsz)
190 const char *legal;
191 char quotechar;
192 char *s, *callerbuf;
193 int bufsz;
194 {
195         char *sp, *op;
196         int cnt = 0;
197         static char hexdigits[] = "0123456789ABCDEF";
198
199         sp = s;
200         op = callerbuf;
201         *op = '\0';
202         
203         while (*sp) {
204                 /* Do we have room for one more character or encoding? */
205                 if ((bufsz - cnt) <= 4) return callerbuf;
206
207                 if (*sp == quotechar) {
208                         (void)sprintf(op, "%c%02X", quotechar, *sp);
209                          op += 3;
210                          cnt += 3;
211                 } else if ((index(legal, *sp) != 0) || (index(hexdigits, *sp) != 0)) {
212                         *op++ = *sp;
213                         *op = '\0';
214                         cnt++;
215                 } else {
216                         (void)sprintf(op,"%c%02X", quotechar, *sp);
217                         op += 3;
218                         cnt += 3;
219                 }
220                 sp++;
221         }
222         return callerbuf;
223 }
224
225 /*
226  * fname_decode()
227  *
228  *   Args:
229  *      quotechar       lead-in character used to quote illegal characters as hex digits
230  *      s               string to decode
231  *      callerbuf       buffer to house result
232  *      bufsz           size of callerbuf
233  */
234 char *
235 fname_decode(quotechar, s, callerbuf, bufsz)
236 char quotechar;
237 char *s, *callerbuf;
238 int bufsz;
239 {
240         char *sp, *op;
241         int k,calc,cnt = 0;
242         static char hexdigits[] = "0123456789ABCDEF";
243
244         sp = s;
245         op = callerbuf;
246         *op = '\0';
247         calc = 0;
248
249         while (*sp) {
250                 /* Do we have room for one more character? */
251                 if ((bufsz - cnt) <= 2) return callerbuf;
252                 if (*sp == quotechar) {
253                         sp++;
254                         for (k=0; k < 16; ++k) if (*sp == hexdigits[k]) break;
255                         if (k >= 16) return callerbuf;  /* impossible, so bail */
256                         calc = k << 4; 
257                         sp++;
258                         for (k=0; k < 16; ++k) if (*sp == hexdigits[k]) break;
259                         if (k >= 16) return callerbuf;  /* impossible, so bail */
260                         calc += k; 
261                         sp++;
262                         *op++ = calc;
263                         *op = '\0';
264                 } else {
265                         *op++ = *sp++;
266                         *op = '\0';
267                 }
268                 cnt++;
269         }
270         return callerbuf;
271 }
272
273 #ifndef PREFIXES_IN_USE
274 /*ARGSUSED*/
275 #endif
276 const char *
277 fqname(basename, whichprefix, buffnum)
278 const char *basename;
279 int whichprefix, buffnum;
280 {
281 #ifndef PREFIXES_IN_USE
282         return basename;
283 #else
284         if (!basename || whichprefix < 0 || whichprefix >= PREFIX_COUNT)
285                 return basename;
286         if (!fqn_prefix[whichprefix])
287                 return basename;
288         if (buffnum < 0 || buffnum >= FQN_NUMBUF) {
289                 impossible("Invalid fqn_filename_buffer specified: %d",
290                                                                 buffnum);
291                 buffnum = 0;
292         }
293         if (strlen(fqn_prefix[whichprefix]) + strlen(basename) >=
294                                                     FQN_MAX_FILENAME) {
295                 impossible("fqname too long: %s + %s", fqn_prefix[whichprefix],
296                                                 basename);
297                 return basename;        /* XXX */
298         }
299         Strcpy(fqn_filename_buffer[buffnum], fqn_prefix[whichprefix]);
300         return strcat(fqn_filename_buffer[buffnum], basename);
301 #endif
302 }
303
304 /* reasonbuf must be at least BUFSZ, supplied by caller */
305 /*ARGSUSED*/
306 int
307 validate_prefix_locations(reasonbuf)
308 char *reasonbuf;
309 {
310 #if defined(NOCWD_ASSUMPTIONS)
311         FILE *fp;
312         const char *filename;
313         int prefcnt, failcount = 0;
314         char panicbuf1[BUFSZ], panicbuf2[BUFSZ], *details;
315
316         if (reasonbuf) reasonbuf[0] = '\0';
317         for (prefcnt = 1; prefcnt < PREFIX_COUNT; prefcnt++) {
318                 /* don't test writing to configdir or datadir; they're readonly */
319                 if (prefcnt == CONFIGPREFIX || prefcnt == DATAPREFIX) continue;
320                 filename = fqname("validate", prefcnt, 3);
321                 if ((fp = fopen(filename, "w"))) {
322                         fclose(fp);
323                         (void) unlink(filename);
324                 } else {
325                         if (reasonbuf) {
326                                 if (failcount) Strcat(reasonbuf,", ");
327                                 Strcat(reasonbuf, fqn_prefix_names[prefcnt]);
328                         }
329                         /* the paniclog entry gets the value of errno as well */
330                         Sprintf(panicbuf1,"Invalid %s", fqn_prefix_names[prefcnt]);
331 #if defined (NHSTDC) && !defined(NOTSTDC)
332                         if (!(details = strerror(errno)))
333 #endif
334                         details = "";
335                         Sprintf(panicbuf2,"\"%s\", (%d) %s",
336                                 fqn_prefix[prefcnt], errno, details);
337                         paniclog(panicbuf1, panicbuf2);
338                         failcount++;
339                 }       
340         }
341         if (failcount)
342                 return 0;
343         else
344 #endif
345         return 1;
346 }
347
348 /* fopen a file, with OS-dependent bells and whistles */
349 /* NOTE: a simpler version of this routine also exists in util/dlb_main.c */
350 FILE *
351 fopen_datafile(filename, mode, prefix)
352 const char *filename, *mode;
353 int prefix;
354 {
355         FILE *fp;
356
357         filename = fqname(filename, prefix, prefix == TROUBLEPREFIX ? 3 : 0);
358 #ifdef VMS      /* essential to have punctuation, to avoid logical names */
359     {
360         char tmp[BUFSIZ];
361
362         if (!index(filename, '.') && !index(filename, ';'))
363                 filename = strcat(strcpy(tmp, filename), ";0");
364         fp = fopen(filename, mode, "mbc=16");
365     }
366 #else
367         fp = fopen(filename, mode);
368 #endif
369         return fp;
370 }
371
372 /* ----------  BEGIN LEVEL FILE HANDLING ----------- */
373
374 #ifdef MFLOPPY
375 /* Set names for bones[] and lock[] */
376 void
377 set_lock_and_bones()
378 {
379         if (!ramdisk) {
380                 Strcpy(levels, permbones);
381                 Strcpy(bones, permbones);
382         }
383         append_slash(permbones);
384         append_slash(levels);
385 #ifdef AMIGA
386         strncat(levels, bbs_id, PATHLEN);
387 #endif
388         append_slash(bones);
389         Strcat(bones, "bonesnn.*");
390         Strcpy(lock, levels);
391 #ifndef AMIGA
392         Strcat(lock, alllevels);
393 #endif
394         return;
395 }
396 #endif /* MFLOPPY */
397
398
399 /* Construct a file name for a level-type file, which is of the form
400  * something.level (with any old level stripped off).
401  * This assumes there is space on the end of 'file' to append
402  * a two digit number.  This is true for 'level'
403  * but be careful if you use it for other things -dgk
404  */
405 void
406 set_levelfile_name(file, lev)
407 char *file;
408 int lev;
409 {
410         char *tf;
411
412         tf = rindex(file, '.');
413         if (!tf) tf = eos(file);
414         Sprintf(tf, ".%d", lev);
415 #ifdef VMS
416         Strcat(tf, ";1");
417 #endif
418         return;
419 }
420
421 int
422 create_levelfile(lev, errbuf)
423 int lev;
424 char errbuf[];
425 {
426         int fd;
427         const char *fq_lock;
428
429         if (errbuf) *errbuf = '\0';
430         set_levelfile_name(lock, lev);
431         fq_lock = fqname(lock, LEVELPREFIX, 0);
432
433 #if defined(MICRO) || defined(WIN32)
434         /* Use O_TRUNC to force the file to be shortened if it already
435          * exists and is currently longer.
436          */
437 # ifdef HOLD_LOCKFILE_OPEN
438         if (lev == 0)
439                 fd = open_levelfile_exclusively(fq_lock, lev,
440                                 O_WRONLY |O_CREAT | O_TRUNC | O_BINARY);
441         else
442 # endif
443         fd = open(fq_lock, O_WRONLY |O_CREAT | O_TRUNC | O_BINARY, FCMASK);
444 #else
445 # ifdef MAC
446         fd = maccreat(fq_lock, LEVL_TYPE);
447 # else
448         fd = creat(fq_lock, FCMASK);
449 # endif
450 #endif /* MICRO || WIN32 */
451
452         if (fd >= 0)
453             level_info[lev].flags |= LFILE_EXISTS;
454         else if (errbuf)        /* failure explanation */
455             Sprintf(errbuf,
456                     "Cannot create file \"%s\" for level %d (errno %d).",
457                     lock, lev, errno);
458
459         return fd;
460 }
461
462
463 int
464 open_levelfile(lev, errbuf)
465 int lev;
466 char errbuf[];
467 {
468         int fd;
469         const char *fq_lock;
470
471         if (errbuf) *errbuf = '\0';
472         set_levelfile_name(lock, lev);
473         fq_lock = fqname(lock, LEVELPREFIX, 0);
474 #ifdef MFLOPPY
475         /* If not currently accessible, swap it in. */
476         if (level_info[lev].where != ACTIVE)
477                 swapin_file(lev);
478 #endif
479 #ifdef MAC
480         fd = macopen(fq_lock, O_RDONLY | O_BINARY, LEVL_TYPE);
481 #else
482 # ifdef HOLD_LOCKFILE_OPEN
483         if (lev == 0)
484                 fd = open_levelfile_exclusively(fq_lock, lev, O_RDONLY | O_BINARY );
485         else
486 # endif
487         fd = open(fq_lock, O_RDONLY | O_BINARY, 0);
488 #endif
489
490         /* for failure, return an explanation that our caller can use;
491            settle for `lock' instead of `fq_lock' because the latter
492            might end up being too big for nethack's BUFSZ */
493         if (fd < 0 && errbuf)
494             Sprintf(errbuf,
495                     "Cannot open file \"%s\" for level %d (errno %d).",
496                     lock, lev, errno);
497
498         return fd;
499 }
500
501
502 void
503 delete_levelfile(lev)
504 int lev;
505 {
506         /*
507          * Level 0 might be created by port specific code that doesn't
508          * call create_levfile(), so always assume that it exists.
509          */
510         if (lev == 0 || (level_info[lev].flags & LFILE_EXISTS)) {
511                 set_levelfile_name(lock, lev);
512 #ifdef HOLD_LOCKFILE_OPEN
513                 if (lev == 0) really_close();
514 #endif
515                 (void) unlink(fqname(lock, LEVELPREFIX, 0));
516                 level_info[lev].flags &= ~LFILE_EXISTS;
517         }
518 }
519
520
521 void
522 clearlocks()
523 {
524 #if !defined(PC_LOCKING) && defined(MFLOPPY) && !defined(AMIGA)
525         eraseall(levels, alllevels);
526         if (ramdisk)
527                 eraseall(permbones, alllevels);
528 #else
529         register int x;
530
531 # if defined(UNIX) || defined(VMS)
532         (void) signal(SIGHUP, SIG_IGN);
533 # endif
534         /* can't access maxledgerno() before dungeons are created -dlc */
535         for (x = (n_dgns ? maxledgerno() : 0); x >= 0; x--)
536                 delete_levelfile(x);    /* not all levels need be present */
537 #endif
538 }
539
540 #ifdef HOLD_LOCKFILE_OPEN
541 STATIC_OVL int
542 open_levelfile_exclusively(name, lev, oflag)
543 const char *name;
544 int lev, oflag;
545 {
546         int reslt, fd;
547         if (!lftrack.init) {
548                 lftrack.init = 1;
549                 lftrack.fd = -1;
550         }
551         if (lftrack.fd >= 0) {
552                 /* check for compatible access */
553                 if (lftrack.oflag == oflag) {
554                         fd = lftrack.fd;
555                         reslt = lseek(fd, 0L, SEEK_SET);
556                         if (reslt == -1L)
557                             panic("open_levelfile_exclusively: lseek failed %d", errno);
558                         lftrack.nethack_thinks_it_is_open = TRUE;
559                 } else {
560                         really_close();
561                         fd = sopen(name, oflag,SH_DENYRW, FCMASK);
562                         lftrack.fd = fd;
563                         lftrack.oflag = oflag;
564                         lftrack.nethack_thinks_it_is_open = TRUE;
565                 }
566         } else {
567                         fd = sopen(name, oflag,SH_DENYRW, FCMASK);
568                         lftrack.fd = fd;
569                         lftrack.oflag = oflag;
570                         if (fd >= 0)
571                             lftrack.nethack_thinks_it_is_open = TRUE;
572         }
573         return fd;
574 }
575
576 void
577 really_close()
578 {
579         int fd = lftrack.fd;
580         lftrack.nethack_thinks_it_is_open = FALSE;
581         lftrack.fd = -1;
582         lftrack.oflag = 0;
583         (void)_close(fd);
584         return;
585 }
586
587 int
588 close(fd)
589 int fd;
590 {
591         if (lftrack.fd == fd) {
592                 really_close(); /* close it, but reopen it to hold it */
593                 fd = open_levelfile(0, (char *)0);
594                 lftrack.nethack_thinks_it_is_open = FALSE;
595                 return 0;
596         }
597         return _close(fd);
598 }
599 #endif
600         
601 /* ----------  END LEVEL FILE HANDLING ----------- */
602
603
604 /* ----------  BEGIN BONES FILE HANDLING ----------- */
605
606 /* set up "file" to be file name for retrieving bones, and return a
607  * bonesid to be read/written in the bones file.
608  */
609 STATIC_OVL char *
610 set_bonesfile_name(file, lev)
611 char *file;
612 d_level *lev;
613 {
614         s_level *sptr;
615         char *dptr;
616
617         Sprintf(file, "bon%c%s", dungeons[lev->dnum].boneid,
618                         In_quest(lev) ? urole.filecode : "0");
619         dptr = eos(file);
620         if ((sptr = Is_special(lev)) != 0)
621             Sprintf(dptr, ".%c", sptr->boneid);
622         else
623             Sprintf(dptr, ".%d", lev->dlevel);
624 #ifdef VMS
625         Strcat(dptr, ";1");
626 #endif
627         return(dptr-2);
628 }
629
630 /* set up temporary file name for writing bones, to avoid another game's
631  * trying to read from an uncompleted bones file.  we want an uncontentious
632  * name, so use one in the namespace reserved for this game's level files.
633  * (we are not reading or writing level files while writing bones files, so
634  * the same array may be used instead of copying.)
635  */
636 STATIC_OVL char *
637 set_bonestemp_name()
638 {
639         char *tf;
640
641         tf = rindex(lock, '.');
642         if (!tf) tf = eos(lock);
643         Sprintf(tf, ".bn");
644 #ifdef VMS
645         Strcat(tf, ";1");
646 #endif
647         return lock;
648 }
649
650 int
651 create_bonesfile(lev, bonesid, errbuf)
652 d_level *lev;
653 char **bonesid;
654 char errbuf[];
655 {
656         const char *file;
657         int fd;
658
659         if (errbuf) *errbuf = '\0';
660         *bonesid = set_bonesfile_name(bones, lev);
661         file = set_bonestemp_name();
662         file = fqname(file, BONESPREFIX, 0);
663
664 #if defined(MICRO) || defined(WIN32)
665         /* Use O_TRUNC to force the file to be shortened if it already
666          * exists and is currently longer.
667          */
668         fd = open(file, O_WRONLY |O_CREAT | O_TRUNC | O_BINARY, FCMASK);
669 #else
670 # ifdef MAC
671         fd = maccreat(file, BONE_TYPE);
672 # else
673         fd = creat(file, FCMASK);
674 # endif
675 #endif
676         if (fd < 0 && errbuf) /* failure explanation */
677             Sprintf(errbuf,
678                     "Cannot create bones \"%s\", id %s (errno %d).",
679                     lock, *bonesid, errno);
680
681 # if defined(VMS) && !defined(SECURE)
682         /*
683            Re-protect bones file with world:read+write+execute+delete access.
684            umask() doesn't seem very reliable; also, vaxcrtl won't let us set
685            delete access without write access, which is what's really wanted.
686            Can't simply create it with the desired protection because creat
687            ANDs the mask with the user's default protection, which usually
688            denies some or all access to world.
689          */
690         (void) chmod(file, FCMASK | 007);  /* allow other users full access */
691 # endif /* VMS && !SECURE */
692
693         return fd;
694 }
695
696 #ifdef MFLOPPY
697 /* remove partial bonesfile in process of creation */
698 void
699 cancel_bonesfile()
700 {
701         const char *tempname;
702
703         tempname = set_bonestemp_name();
704         tempname = fqname(tempname, BONESPREFIX, 0);
705         (void) unlink(tempname);
706 }
707 #endif /* MFLOPPY */
708
709 /* move completed bones file to proper name */
710 void
711 commit_bonesfile(lev)
712 d_level *lev;
713 {
714         const char *fq_bones, *tempname;
715         int ret;
716
717         (void) set_bonesfile_name(bones, lev);
718         fq_bones = fqname(bones, BONESPREFIX, 0);
719         tempname = set_bonestemp_name();
720         tempname = fqname(tempname, BONESPREFIX, 1);
721
722 #if (defined(SYSV) && !defined(SVR4)) || defined(GENIX)
723         /* old SYSVs don't have rename.  Some SVR3's may, but since they
724          * also have link/unlink, it doesn't matter. :-)
725          */
726         (void) unlink(fq_bones);
727         ret = link(tempname, fq_bones);
728         ret += unlink(tempname);
729 #else
730         ret = rename(tempname, fq_bones);
731 #endif
732 #ifdef WIZARD
733         if (wizard && ret != 0)
734                 pline("couldn't rename %s to %s.", tempname, fq_bones);
735 #endif
736 }
737
738
739 int
740 open_bonesfile(lev, bonesid)
741 d_level *lev;
742 char **bonesid;
743 {
744         const char *fq_bones;
745         int fd;
746
747         *bonesid = set_bonesfile_name(bones, lev);
748         fq_bones = fqname(bones, BONESPREFIX, 0);
749         uncompress(fq_bones);   /* no effect if nonexistent */
750 #ifdef MAC
751         fd = macopen(fq_bones, O_RDONLY | O_BINARY, BONE_TYPE);
752 #else
753         fd = open(fq_bones, O_RDONLY | O_BINARY, 0);
754 #endif
755         return fd;
756 }
757
758
759 int
760 delete_bonesfile(lev)
761 d_level *lev;
762 {
763         (void) set_bonesfile_name(bones, lev);
764         return !(unlink(fqname(bones, BONESPREFIX, 0)) < 0);
765 }
766
767
768 /* assume we're compressing the recently read or created bonesfile, so the
769  * file name is already set properly */
770 void
771 compress_bonesfile()
772 {
773         compress(fqname(bones, BONESPREFIX, 0));
774 }
775
776 /* ----------  END BONES FILE HANDLING ----------- */
777
778
779 /* ----------  BEGIN SAVE FILE HANDLING ----------- */
780
781 /* set savefile name in OS-dependent manner from pre-existing plname,
782  * avoiding troublesome characters */
783 void
784 set_savefile_name()
785 {
786 #if defined(WIN32)
787         char fnamebuf[BUFSZ], encodedfnamebuf[BUFSZ];
788 #endif
789 #ifdef VMS
790         Sprintf(SAVEF, "[.save]%d%s", getuid(), plname);
791         regularize(SAVEF+7);
792         Strcat(SAVEF, ";1");
793 #else
794 # if defined(MICRO)
795         Strcpy(SAVEF, SAVEP);
796 #  ifdef AMIGA
797         strncat(SAVEF, bbs_id, PATHLEN);
798 #  endif
799         {
800                 int i = strlen(SAVEP);
801 #  ifdef AMIGA
802                 /* plname has to share space with SAVEP and ".sav" */
803                 (void)strncat(SAVEF, plname, FILENAME - i - 4);
804 #  else
805                 (void)strncat(SAVEF, plname, 8);
806 #  endif
807                 regularize(SAVEF+i);
808         }
809         Strcat(SAVEF, ".sav");
810 # else
811 #  if defined(WIN32)
812         /* Obtain the name of the logged on user and incorporate
813          * it into the name. */
814         Sprintf(fnamebuf, "%s-%s", get_username(0), plname);
815         (void)fname_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.",
816                                 '%', fnamebuf, encodedfnamebuf, BUFSZ);
817         Sprintf(SAVEF, "%s.NetHack-saved-game", encodedfnamebuf);
818 #  else
819         Sprintf(SAVEF, "save/%d%s", (int)getuid(), plname);
820         regularize(SAVEF+5);    /* avoid . or / in name */
821 #  endif /* WIN32 */
822 # endif /* MICRO */
823 #endif /* VMS   */
824 }
825
826 #ifdef INSURANCE
827 void
828 save_savefile_name(fd)
829 int fd;
830 {
831         (void) write(fd, (genericptr_t) SAVEF, sizeof(SAVEF));
832 }
833 #endif
834
835
836 #if defined(WIZARD) && !defined(MICRO)
837 /* change pre-existing savefile name to indicate an error savefile */
838 void
839 set_error_savefile()
840 {
841 # ifdef VMS
842       {
843         char *semi_colon = rindex(SAVEF, ';');
844         if (semi_colon) *semi_colon = '\0';
845       }
846         Strcat(SAVEF, ".e;1");
847 # else
848 #  ifdef MAC
849         Strcat(SAVEF, "-e");
850 #  else
851         Strcat(SAVEF, ".e");
852 #  endif
853 # endif
854 }
855 #endif
856
857
858 /* create save file, overwriting one if it already exists */
859 int
860 create_savefile()
861 {
862         const char *fq_save;
863         int fd;
864
865         fq_save = fqname(SAVEF, SAVEPREFIX, 0);
866 #if defined(MICRO) || defined(WIN32)
867         fd = open(fq_save, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, FCMASK);
868 #else
869 # ifdef MAC
870         fd = maccreat(fq_save, SAVE_TYPE);
871 # else
872         fd = creat(fq_save, FCMASK);
873 # endif
874 # if defined(VMS) && !defined(SECURE)
875         /*
876            Make sure the save file is owned by the current process.  That's
877            the default for non-privileged users, but for priv'd users the
878            file will be owned by the directory's owner instead of the user.
879          */
880 #  ifdef getuid /*(see vmsunix.c)*/
881 #   undef getuid
882 #  endif
883         (void) chown(fq_save, getuid(), getgid());
884 # endif /* VMS && !SECURE */
885 #endif  /* MICRO */
886
887         return fd;
888 }
889
890
891 /* open savefile for reading */
892 int
893 open_savefile()
894 {
895         const char *fq_save;
896         int fd;
897
898         fq_save = fqname(SAVEF, SAVEPREFIX, 0);
899 #ifdef MAC
900         fd = macopen(fq_save, O_RDONLY | O_BINARY, SAVE_TYPE);
901 #else
902         fd = open(fq_save, O_RDONLY | O_BINARY, 0);
903 #endif
904         return fd;
905 }
906
907
908 /* delete savefile */
909 int
910 delete_savefile()
911 {
912         (void) unlink(fqname(SAVEF, SAVEPREFIX, 0));
913         return 0;       /* for restore_saved_game() (ex-xxxmain.c) test */
914 }
915
916
917 /* try to open up a save file and prepare to restore it */
918 int
919 restore_saved_game()
920 {
921         const char *fq_save;
922         int fd;
923
924         set_savefile_name();
925 #ifdef MFLOPPY
926         if (!saveDiskPrompt(1))
927             return -1;
928 #endif /* MFLOPPY */
929         fq_save = fqname(SAVEF, SAVEPREFIX, 0);
930
931         uncompress(fq_save);
932         if ((fd = open_savefile()) < 0) return fd;
933
934         if (!uptodate(fd, fq_save)) {
935             (void) close(fd),  fd = -1;
936             (void) delete_savefile();
937         }
938         return fd;
939 }
940
941 #if defined(UNIX) && defined(QT_GRAPHICS)
942 /*ARGSUSED*/
943 static char*
944 plname_from_file(filename)
945 const char* filename;
946 {
947 #ifdef STORE_PLNAME_IN_FILE
948     int fd;
949     char* result = 0;
950
951     Strcpy(SAVEF,filename);
952 #ifdef COMPRESS_EXTENSION
953     SAVEF[strlen(SAVEF)-strlen(COMPRESS_EXTENSION)] = '\0';
954 #endif
955     uncompress(SAVEF);
956     if ((fd = open_savefile()) >= 0) {
957         if (uptodate(fd, filename)) {
958             char tplname[PL_NSIZ];
959             mread(fd, (genericptr_t) tplname, PL_NSIZ);
960             result = strdup(tplname);
961         }
962         (void) close(fd);
963     }
964     compress(SAVEF);
965
966     return result;
967 #else
968 # if defined(UNIX) && defined(QT_GRAPHICS)
969     /* Name not stored in save file, so we have to extract it from
970        the filename, which loses information
971        (eg. "/", "_", and "." characters are lost. */
972     int k;
973     int uid;
974     char name[64]; /* more than PL_NSIZ */
975 #ifdef COMPRESS_EXTENSION
976 #define EXTSTR COMPRESS_EXTENSION
977 #else
978 #define EXTSTR ""
979 #endif
980     if ( sscanf( filename, "%*[^/]/%d%63[^.]" EXTSTR, &uid, name ) == 2 ) {
981 #undef EXTSTR
982     /* "_" most likely means " ", which certainly looks nicer */
983         for (k=0; name[k]; k++)
984             if ( name[k]=='_' )
985                 name[k]=' ';
986         return strdup(name);
987     } else
988 # endif
989     {
990         return 0;
991     }
992 #endif
993 }
994 #endif /* defined(UNIX) && defined(QT_GRAPHICS) */
995
996 char**
997 get_saved_games()
998 {
999 #if defined(UNIX) && defined(QT_GRAPHICS)
1000     int myuid=getuid();
1001     struct dirent **namelist;
1002     int n = scandir("save", &namelist, 0, alphasort);;
1003     if ( n > 0 ) {
1004         int i,j=0;
1005         char** result = (char**)alloc((n+1)*sizeof(char*)); /* at most */
1006         for (i=0; i<n; i++) {
1007             int uid;
1008             char name[64]; /* more than PL_NSIZ */
1009             if ( sscanf( namelist[i]->d_name, "%d%63s", &uid, name ) == 2 ) {
1010                 if ( uid == myuid ) {
1011                     char filename[BUFSZ];
1012                     char* r;
1013                     Sprintf(filename,"save/%d%s",uid,name);
1014                     r = plname_from_file(filename);
1015                     if ( r )
1016                         result[j++] = r;
1017                 }
1018             }
1019         }
1020         result[j++] = 0;
1021         return result;
1022     } else
1023 #endif
1024     {
1025         return 0;
1026     }
1027 }
1028
1029 void
1030 free_saved_games(saved)
1031 char** saved;
1032 {
1033     if ( saved ) {
1034         int i=0;
1035         while (saved[i]) free((genericptr_t)saved[i++]);
1036         free((genericptr_t)saved);
1037     }
1038 }
1039
1040
1041 /* ----------  END SAVE FILE HANDLING ----------- */
1042
1043
1044 /* ----------  BEGIN FILE COMPRESSION HANDLING ----------- */
1045
1046 #ifdef COMPRESS
1047
1048 STATIC_OVL void
1049 redirect(filename, mode, stream, uncomp)
1050 const char *filename, *mode;
1051 FILE *stream;
1052 boolean uncomp;
1053 {
1054         if (freopen(filename, mode, stream) == (FILE *)0) {
1055                 (void) fprintf(stderr, "freopen of %s for %scompress failed\n",
1056                         filename, uncomp ? "un" : "");
1057                 terminate(EXIT_FAILURE);
1058         }
1059 }
1060
1061 /*
1062  * using system() is simpler, but opens up security holes and causes
1063  * problems on at least Interactive UNIX 3.0.1 (SVR3.2), where any
1064  * setuid is renounced by /bin/sh, so the files cannot be accessed.
1065  *
1066  * cf. child() in unixunix.c.
1067  */
1068 STATIC_OVL void
1069 docompress_file(filename, uncomp)
1070 const char *filename;
1071 boolean uncomp;
1072 {
1073         char cfn[80];
1074         FILE *cf;
1075         const char *args[10];
1076 # ifdef COMPRESS_OPTIONS
1077         char opts[80];
1078 # endif
1079         int i = 0;
1080         int f;
1081 # ifdef TTY_GRAPHICS
1082         boolean istty = !strncmpi(windowprocs.name, "tty", 3);
1083 # endif
1084
1085         Strcpy(cfn, filename);
1086 # ifdef COMPRESS_EXTENSION
1087         Strcat(cfn, COMPRESS_EXTENSION);
1088 # endif
1089         /* when compressing, we know the file exists */
1090         if (uncomp) {
1091             if ((cf = fopen(cfn, RDBMODE)) == (FILE *)0)
1092                     return;
1093             (void) fclose(cf);
1094         }
1095
1096         args[0] = COMPRESS;
1097         if (uncomp) args[++i] = "-d";   /* uncompress */
1098 # ifdef COMPRESS_OPTIONS
1099         {
1100             /* we can't guarantee there's only one additional option, sigh */
1101             char *opt;
1102             boolean inword = FALSE;
1103
1104             Strcpy(opts, COMPRESS_OPTIONS);
1105             opt = opts;
1106             while (*opt) {
1107                 if ((*opt == ' ') || (*opt == '\t')) {
1108                     if (inword) {
1109                         *opt = '\0';
1110                         inword = FALSE;
1111                     }
1112                 } else if (!inword) {
1113                     args[++i] = opt;
1114                     inword = TRUE;
1115                 }
1116                 opt++;
1117             }
1118         }
1119 # endif
1120         args[++i] = (char *)0;
1121
1122 # ifdef TTY_GRAPHICS
1123         /* If we don't do this and we are right after a y/n question *and*
1124          * there is an error message from the compression, the 'y' or 'n' can
1125          * end up being displayed after the error message.
1126          */
1127         if (istty)
1128             mark_synch();
1129 # endif
1130         f = fork();
1131         if (f == 0) {   /* child */
1132 # ifdef TTY_GRAPHICS
1133                 /* any error messages from the compression must come out after
1134                  * the first line, because the more() to let the user read
1135                  * them will have to clear the first line.  This should be
1136                  * invisible if there are no error messages.
1137                  */
1138                 if (istty)
1139                     raw_print("");
1140 # endif
1141                 /* run compressor without privileges, in case other programs
1142                  * have surprises along the line of gzip once taking filenames
1143                  * in GZIP.
1144                  */
1145                 /* assume all compressors will compress stdin to stdout
1146                  * without explicit filenames.  this is true of at least
1147                  * compress and gzip, those mentioned in config.h.
1148                  */
1149                 if (uncomp) {
1150                         redirect(cfn, RDBMODE, stdin, uncomp);
1151                         redirect(filename, WRBMODE, stdout, uncomp);
1152                 } else {
1153                         redirect(filename, RDBMODE, stdin, uncomp);
1154                         redirect(cfn, WRBMODE, stdout, uncomp);
1155                 }
1156                 (void) setgid(getgid());
1157                 (void) setuid(getuid());
1158                 (void) execv(args[0], (char *const *) args);
1159                 perror((char *)0);
1160                 (void) fprintf(stderr, "Exec to %scompress %s failed.\n",
1161                         uncomp ? "un" : "", filename);
1162                 terminate(EXIT_FAILURE);
1163         } else if (f == -1) {
1164                 perror((char *)0);
1165                 pline("Fork to %scompress %s failed.",
1166                         uncomp ? "un" : "", filename);
1167                 return;
1168         }
1169         (void) signal(SIGINT, SIG_IGN);
1170         (void) signal(SIGQUIT, SIG_IGN);
1171         (void) wait((int *)&i);
1172         (void) signal(SIGINT, (SIG_RET_TYPE) done1);
1173 # ifdef WIZARD
1174         if (wizard) (void) signal(SIGQUIT, SIG_DFL);
1175 # endif
1176         if (i == 0) {
1177             /* (un)compress succeeded: remove file left behind */
1178             if (uncomp)
1179                 (void) unlink(cfn);
1180             else
1181                 (void) unlink(filename);
1182         } else {
1183             /* (un)compress failed; remove the new, bad file */
1184             if (uncomp) {
1185                 raw_printf("Unable to uncompress %s", filename);
1186                 (void) unlink(filename);
1187             } else {
1188                 /* no message needed for compress case; life will go on */
1189                 (void) unlink(cfn);
1190             }
1191 #ifdef TTY_GRAPHICS
1192             /* Give them a chance to read any error messages from the
1193              * compression--these would go to stdout or stderr and would get
1194              * overwritten only in tty mode.  It's still ugly, since the
1195              * messages are being written on top of the screen, but at least
1196              * the user can read them.
1197              */
1198             if (istty)
1199             {
1200                 clear_nhwindow(WIN_MESSAGE);
1201                 more();
1202                 /* No way to know if this is feasible */
1203                 /* doredraw(); */
1204             }
1205 #endif
1206         }
1207 }
1208 #endif  /* COMPRESS */
1209
1210 /* compress file */
1211 void
1212 compress(filename)
1213 const char *filename;
1214 {
1215 #ifndef COMPRESS
1216 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) || defined(__MWERKS__)
1217 # pragma unused(filename)
1218 #endif
1219 #else
1220         docompress_file(filename, FALSE);
1221 #endif
1222 }
1223
1224
1225 /* uncompress file if it exists */
1226 void
1227 uncompress(filename)
1228 const char *filename;
1229 {
1230 #ifndef COMPRESS
1231 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) || defined(__MWERKS__)
1232 # pragma unused(filename)
1233 #endif
1234 #else
1235         docompress_file(filename, TRUE);
1236 #endif
1237 }
1238
1239 /* ----------  END FILE COMPRESSION HANDLING ----------- */
1240
1241
1242 /* ----------  BEGIN FILE LOCKING HANDLING ----------- */
1243
1244 static int nesting = 0;
1245
1246 #ifdef NO_FILE_LINKS    /* implies UNIX */
1247 static int lockfd;      /* for lock_file() to pass to unlock_file() */
1248 #endif
1249
1250 #define HUP     if (!program_state.done_hup)
1251
1252 STATIC_OVL char *
1253 make_lockname(filename, lockname)
1254 const char *filename;
1255 char *lockname;
1256 {
1257 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) || defined(__MWERKS__)
1258 # pragma unused(filename,lockname)
1259         return (char*)0;
1260 #else
1261 # if defined(UNIX) || defined(VMS) || defined(AMIGA) || defined(WIN32) || defined(MSDOS)
1262 #  ifdef NO_FILE_LINKS
1263         Strcpy(lockname, LOCKDIR);
1264         Strcat(lockname, "/");
1265         Strcat(lockname, filename);
1266 #  else
1267         Strcpy(lockname, filename);
1268 #  endif
1269 #  ifdef VMS
1270       {
1271         char *semi_colon = rindex(lockname, ';');
1272         if (semi_colon) *semi_colon = '\0';
1273       }
1274         Strcat(lockname, ".lock;1");
1275 #  else
1276         Strcat(lockname, "_lock");
1277 #  endif
1278         return lockname;
1279 # else
1280         lockname[0] = '\0';
1281         return (char*)0;
1282 # endif  /* UNIX || VMS || AMIGA || WIN32 || MSDOS */
1283 #endif
1284 }
1285
1286
1287 /* lock a file */
1288 boolean
1289 lock_file(filename, whichprefix, retryct)
1290 const char *filename;
1291 int whichprefix;
1292 int retryct;
1293 {
1294 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) || defined(__MWERKS__)
1295 # pragma unused(filename, retryct)
1296 #endif
1297         char locknambuf[BUFSZ];
1298         const char *lockname;
1299
1300         nesting++;
1301         if (nesting > 1) {
1302             impossible("TRIED TO NEST LOCKS");
1303             return TRUE;
1304         }
1305
1306         lockname = make_lockname(filename, locknambuf);
1307         filename = fqname(filename, whichprefix, 0);
1308 #ifndef NO_FILE_LINKS   /* LOCKDIR should be subsumed by LOCKPREFIX */
1309         lockname = fqname(lockname, LOCKPREFIX, 2);
1310 #endif
1311
1312 #if defined(UNIX) || defined(VMS)
1313 # ifdef NO_FILE_LINKS
1314         while ((lockfd = open(lockname, O_RDWR|O_CREAT|O_EXCL, 0666)) == -1) {
1315 # else
1316         while (link(filename, lockname) == -1) {
1317 # endif
1318             register int errnosv = errno;
1319
1320             switch (errnosv) {  /* George Barbanis */
1321             case EEXIST:
1322                 if (retryct--) {
1323                     HUP raw_printf(
1324                             "Waiting for access to %s.  (%d retries left).",
1325                             filename, retryct);
1326 # if defined(SYSV) || defined(ULTRIX) || defined(VMS)
1327                     (void)
1328 # endif
1329                         sleep(1);
1330                 } else {
1331                     HUP (void) raw_print("I give up.  Sorry.");
1332                     HUP raw_printf("Perhaps there is an old %s around?",
1333                                         lockname);
1334                     nesting--;
1335                     return FALSE;
1336                 }
1337
1338                 break;
1339             case ENOENT:
1340                 HUP raw_printf("Can't find file %s to lock!", filename);
1341                 nesting--;
1342                 return FALSE;
1343             case EACCES:
1344                 HUP raw_printf("No write permission to lock %s!", filename);
1345                 nesting--;
1346                 return FALSE;
1347 # ifdef VMS                     /* c__translate(vmsfiles.c) */
1348             case EPERM:
1349                 /* could be misleading, but usually right */
1350                 HUP raw_printf("Can't lock %s due to directory protection.",
1351                                filename);
1352                 nesting--;
1353                 return FALSE;
1354 # endif
1355             default:
1356                 HUP perror(lockname);
1357                 HUP raw_printf(
1358                              "Cannot lock %s for unknown reason (%d).",
1359                                filename, errnosv);
1360                 nesting--;
1361                 return FALSE;
1362             }
1363
1364         }
1365 #endif  /* UNIX || VMS */
1366
1367 #if defined(AMIGA) || defined(WIN32) || defined(MSDOS)
1368 # ifdef AMIGA
1369 #define OPENFAILURE(fd) (!fd)
1370     lockptr = 0;
1371 # else
1372 #define OPENFAILURE(fd) (fd < 0)
1373     lockptr = -1;
1374 # endif
1375     while (--retryct && OPENFAILURE(lockptr)) {
1376 # if defined(WIN32) && !defined(WIN_CE)
1377         lockptr = sopen(lockname, O_RDWR|O_CREAT, SH_DENYRW, S_IWRITE);
1378 # else
1379         (void)DeleteFile(lockname); /* in case dead process was here first */
1380 #  ifdef AMIGA
1381         lockptr = Open(lockname,MODE_NEWFILE);
1382 #  else
1383         lockptr = open(lockname, O_RDWR|O_CREAT|O_EXCL, S_IWRITE);
1384 #  endif
1385 # endif
1386         if (OPENFAILURE(lockptr)) {
1387             raw_printf("Waiting for access to %s.  (%d retries left).",
1388                         filename, retryct);
1389             Delay(50);
1390         }
1391     }
1392     if (!retryct) {
1393         raw_printf("I give up.  Sorry.");
1394         nesting--;
1395         return FALSE;
1396     }
1397 #endif /* AMIGA || WIN32 || MSDOS */
1398         return TRUE;
1399 }
1400
1401
1402 #ifdef VMS      /* for unlock_file, use the unlink() routine in vmsunix.c */
1403 # ifdef unlink
1404 #  undef unlink
1405 # endif
1406 # define unlink(foo) vms_unlink(foo)
1407 #endif
1408
1409 /* unlock file, which must be currently locked by lock_file */
1410 void
1411 unlock_file(filename)
1412 const char *filename;
1413 #if defined(macintosh) && (defined(__SC__) || defined(__MRC__))
1414 # pragma unused(filename)
1415 #endif
1416 {
1417         char locknambuf[BUFSZ];
1418         const char *lockname;
1419
1420         if (nesting == 1) {
1421                 lockname = make_lockname(filename, locknambuf);
1422 #ifndef NO_FILE_LINKS   /* LOCKDIR should be subsumed by LOCKPREFIX */
1423                 lockname = fqname(lockname, LOCKPREFIX, 2);
1424 #endif
1425
1426 #if defined(UNIX) || defined(VMS)
1427                 if (unlink(lockname) < 0)
1428                         HUP raw_printf("Can't unlink %s.", lockname);
1429 # ifdef NO_FILE_LINKS
1430                 (void) close(lockfd);
1431 # endif
1432
1433 #endif  /* UNIX || VMS */
1434
1435 #if defined(AMIGA) || defined(WIN32) || defined(MSDOS)
1436                 if (lockptr) Close(lockptr);
1437                 DeleteFile(lockname);
1438                 lockptr = 0;
1439 #endif /* AMIGA || WIN32 || MSDOS */
1440         }
1441
1442         nesting--;
1443 }
1444
1445 /* ----------  END FILE LOCKING HANDLING ----------- */
1446
1447
1448 /* ----------  BEGIN CONFIG FILE HANDLING ----------- */
1449
1450 const char *configfile =
1451 #ifdef UNIX
1452                         ".nethackrc";
1453 #else
1454 # if defined(MAC) || defined(__BEOS__)
1455                         "NetHack Defaults";
1456 # else
1457 #  if defined(MSDOS) || defined(WIN32)
1458                         "defaults.nh";
1459 #  else
1460                         "NetHack.cnf";
1461 #  endif
1462 # endif
1463 #endif
1464
1465
1466 #ifdef MSDOS
1467 /* conflict with speed-dial under windows
1468  * for XXX.cnf file so support of NetHack.cnf
1469  * is for backward compatibility only.
1470  * Preferred name (and first tried) is now defaults.nh but
1471  * the game will try the old name if there
1472  * is no defaults.nh.
1473  */
1474 const char *backward_compat_configfile = "nethack.cnf"; 
1475 #endif
1476
1477 #ifndef MFLOPPY
1478 #define fopenp fopen
1479 #endif
1480
1481 STATIC_OVL FILE *
1482 fopen_config_file(filename)
1483 const char *filename;
1484 {
1485         FILE *fp;
1486 #if defined(UNIX) || defined(VMS)
1487         char    tmp_config[BUFSZ];
1488         char *envp;
1489 #endif
1490
1491         /* "filename" is an environment variable, so it should hang around */
1492         /* if set, it is expected to be a full path name (if relevant) */
1493         if (filename) {
1494 #ifdef UNIX
1495                 if (access(filename, 4) == -1) {
1496                         /* 4 is R_OK on newer systems */
1497                         /* nasty sneaky attempt to read file through
1498                          * NetHack's setuid permissions -- this is the only
1499                          * place a file name may be wholly under the player's
1500                          * control
1501                          */
1502                         raw_printf("Access to %s denied (%d).",
1503                                         filename, errno);
1504                         wait_synch();
1505                         /* fall through to standard names */
1506                 } else
1507 #endif
1508                 if ((fp = fopenp(filename, "r")) != (FILE *)0) {
1509                     configfile = filename;
1510                     return(fp);
1511 #if defined(UNIX) || defined(VMS)
1512                 } else {
1513                     /* access() above probably caught most problems for UNIX */
1514                     raw_printf("Couldn't open requested config file %s (%d).",
1515                                         filename, errno);
1516                     wait_synch();
1517                     /* fall through to standard names */
1518 #endif
1519                 }
1520         }
1521
1522 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
1523         if ((fp = fopenp(fqname(configfile, CONFIGPREFIX, 0), "r"))
1524                                                                 != (FILE *)0)
1525                 return(fp);
1526 # ifdef MSDOS
1527         else if ((fp = fopenp(fqname(backward_compat_configfile,
1528                                         CONFIGPREFIX, 0), "r")) != (FILE *)0)
1529                 return(fp);
1530 # endif
1531 #else
1532         /* constructed full path names don't need fqname() */
1533 # ifdef VMS
1534         if ((fp = fopenp(fqname("nethackini", CONFIGPREFIX, 0), "r"))
1535                                                                 != (FILE *)0) {
1536                 configfile = "nethackini";
1537                 return(fp);
1538         }
1539         if ((fp = fopenp("sys$login:nethack.ini", "r")) != (FILE *)0) {
1540                 configfile = "nethack.ini";
1541                 return(fp);
1542         }
1543
1544         envp = nh_getenv("HOME");
1545         if (!envp)
1546                 Strcpy(tmp_config, "NetHack.cnf");
1547         else
1548                 Sprintf(tmp_config, "%s%s", envp, "NetHack.cnf");
1549         if ((fp = fopenp(tmp_config, "r")) != (FILE *)0)
1550                 return(fp);
1551 # else  /* should be only UNIX left */
1552         envp = nh_getenv("HOME");
1553         if (!envp)
1554                 Strcpy(tmp_config, configfile);
1555         else
1556                 Sprintf(tmp_config, "%s/%s", envp, configfile);
1557         if ((fp = fopenp(tmp_config, "r")) != (FILE *)0)
1558                 return(fp);
1559 # if defined(__APPLE__)
1560         /* try an alternative */
1561         if (envp) {
1562                 Sprintf(tmp_config, "%s/%s", envp, "Library/Preferences/NetHack Defaults");
1563                 if ((fp = fopenp(tmp_config, "r")) != (FILE *)0)
1564                         return(fp);
1565                 Sprintf(tmp_config, "%s/%s", envp, "Library/Preferences/NetHack Defaults.txt");
1566                 if ((fp = fopenp(tmp_config, "r")) != (FILE *)0)
1567                         return(fp);
1568         }
1569 # endif
1570         if (errno != ENOENT) {
1571             char *details;
1572
1573             /* e.g., problems when setuid NetHack can't search home
1574              * directory restricted to user */
1575
1576 #if defined (NHSTDC) && !defined(NOTSTDC)
1577             if ((details = strerror(errno)) == 0)
1578 #endif
1579                 details = "";
1580             raw_printf("Couldn't open default config file %s %s(%d).",
1581                        tmp_config, details, errno);
1582             wait_synch();
1583         }
1584 # endif
1585 #endif
1586         return (FILE *)0;
1587
1588 }
1589
1590
1591 /*
1592  * Retrieve a list of integers from a file into a uchar array.
1593  *
1594  * NOTE: zeros are inserted unless modlist is TRUE, in which case the list
1595  *  location is unchanged.  Callers must handle zeros if modlist is FALSE.
1596  */
1597 STATIC_OVL int
1598 get_uchars(fp, buf, bufp, list, modlist, size, name)
1599     FILE *fp;           /* input file pointer */
1600     char *buf;          /* read buffer, must be of size BUFSZ */
1601     char *bufp;         /* current pointer */
1602     uchar *list;        /* return list */
1603     boolean modlist;    /* TRUE: list is being modified in place */
1604     int  size;          /* return list size */
1605     const char *name;           /* name of option for error message */
1606 {
1607     unsigned int num = 0;
1608     int count = 0;
1609     boolean havenum = FALSE;
1610
1611     while (1) {
1612         switch(*bufp) {
1613             case ' ':  case '\0':
1614             case '\t': case '\n':
1615                 if (havenum) {
1616                     /* if modifying in place, don't insert zeros */
1617                     if (num || !modlist) list[count] = num;
1618                     count++;
1619                     num = 0;
1620                     havenum = FALSE;
1621                 }
1622                 if (count == size || !*bufp) return count;
1623                 bufp++;
1624                 break;
1625
1626             case '0': case '1': case '2': case '3':
1627             case '4': case '5': case '6': case '7':
1628             case '8': case '9':
1629                 havenum = TRUE;
1630                 num = num*10 + (*bufp-'0');
1631                 bufp++;
1632                 break;
1633
1634             case '\\':
1635                 if (fp == (FILE *)0)
1636                     goto gi_error;
1637                 do  {
1638                     if (!fgets(buf, BUFSZ, fp)) goto gi_error;
1639                 } while (buf[0] == '#');
1640                 bufp = buf;
1641                 break;
1642
1643             default:
1644 gi_error:
1645                 raw_printf("Syntax error in %s", name);
1646                 wait_synch();
1647                 return count;
1648         }
1649     }
1650     /*NOTREACHED*/
1651 }
1652
1653 #ifdef NOCWD_ASSUMPTIONS
1654 STATIC_OVL void
1655 adjust_prefix(bufp, prefixid)
1656 char *bufp;
1657 int prefixid;
1658 {
1659         char *ptr;
1660
1661         if (!bufp) return;
1662         /* Backward compatibility, ignore trailing ;n */ 
1663         if ((ptr = index(bufp, ';')) != 0) *ptr = '\0';
1664         if (strlen(bufp) > 0) {
1665                 fqn_prefix[prefixid] = (char *)alloc(strlen(bufp)+2);
1666                 Strcpy(fqn_prefix[prefixid], bufp);
1667                 append_slash(fqn_prefix[prefixid]);
1668         }
1669 }
1670 #endif
1671
1672 #define match_varname(INP,NAM,LEN) match_optname(INP, NAM, LEN, TRUE)
1673
1674 /*ARGSUSED*/
1675 int
1676 parse_config_line(fp, buf, tmp_ramdisk, tmp_levels)
1677 FILE            *fp;
1678 char            *buf;
1679 char            *tmp_ramdisk;
1680 char            *tmp_levels;
1681 {
1682 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) || defined(__MWERKS__)
1683 # pragma unused(tmp_ramdisk,tmp_levels)
1684 #endif
1685         char            *bufp, *altp;
1686         uchar   translate[MAXPCHARS];
1687         int   len;
1688
1689         if (*buf == '#')
1690                 return 1;
1691
1692         /* remove trailing whitespace */
1693         bufp = eos(buf);
1694         while (--bufp > buf && isspace(*bufp))
1695                 continue;
1696
1697         if (bufp <= buf)
1698                 return 1;               /* skip all-blank lines */
1699         else
1700                 *(bufp + 1) = '\0';     /* terminate line */
1701
1702         /* find the '=' or ':' */
1703         bufp = index(buf, '=');
1704         altp = index(buf, ':');
1705         if (!bufp || (altp && altp < bufp)) bufp = altp;
1706         if (!bufp) return 0;
1707
1708         /* skip  whitespace between '=' and value */
1709         do { ++bufp; } while (isspace(*bufp));
1710
1711         /* Go through possible variables */
1712         /* some of these (at least LEVELS and SAVE) should now set the
1713          * appropriate fqn_prefix[] rather than specialized variables
1714          */
1715         if (match_varname(buf, "OPTIONS", 4)) {
1716                 parseoptions(bufp, TRUE, TRUE);
1717                 if (plname[0])          /* If a name was given */
1718                         plnamesuffix(); /* set the character class */
1719 #ifdef AUTOPICKUP_EXCEPTIONS
1720         } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) {
1721                 add_autopickup_exception(bufp);
1722 #endif
1723 #ifdef NOCWD_ASSUMPTIONS
1724         } else if (match_varname(buf, "HACKDIR", 4)) {
1725                 adjust_prefix(bufp, HACKPREFIX);
1726         } else if (match_varname(buf, "LEVELDIR", 4) ||
1727                    match_varname(buf, "LEVELS", 4)) {
1728                 adjust_prefix(bufp, LEVELPREFIX);
1729         } else if (match_varname(buf, "SAVEDIR", 4)) {
1730                 adjust_prefix(bufp, SAVEPREFIX);
1731         } else if (match_varname(buf, "BONESDIR", 5)) {
1732                 adjust_prefix(bufp, BONESPREFIX);
1733         } else if (match_varname(buf, "DATADIR", 4)) {
1734                 adjust_prefix(bufp, DATAPREFIX);
1735         } else if (match_varname(buf, "SCOREDIR", 4)) {
1736                 adjust_prefix(bufp, SCOREPREFIX);
1737         } else if (match_varname(buf, "LOCKDIR", 4)) {
1738                 adjust_prefix(bufp, LOCKPREFIX);
1739         } else if (match_varname(buf, "CONFIGDIR", 4)) {
1740                 adjust_prefix(bufp, CONFIGPREFIX);
1741         } else if (match_varname(buf, "TROUBLEDIR", 4)) {
1742                 adjust_prefix(bufp, TROUBLEPREFIX);
1743 #else /*NOCWD_ASSUMPTIONS*/
1744 # ifdef MICRO
1745         } else if (match_varname(buf, "HACKDIR", 4)) {
1746                 (void) strncpy(hackdir, bufp, PATHLEN-1);
1747 #  ifdef MFLOPPY
1748         } else if (match_varname(buf, "RAMDISK", 3)) {
1749                                 /* The following ifdef is NOT in the wrong
1750                                  * place.  For now, we accept and silently
1751                                  * ignore RAMDISK */
1752 #   ifndef AMIGA
1753                 (void) strncpy(tmp_ramdisk, bufp, PATHLEN-1);
1754 #   endif
1755 #  endif
1756         } else if (match_varname(buf, "LEVELS", 4)) {
1757                 (void) strncpy(tmp_levels, bufp, PATHLEN-1);
1758
1759         } else if (match_varname(buf, "SAVE", 4)) {
1760 #  ifdef MFLOPPY
1761                 extern  int saveprompt;
1762 #  endif
1763                 char *ptr;
1764                 if ((ptr = index(bufp, ';')) != 0) {
1765                         *ptr = '\0';
1766 #  ifdef MFLOPPY
1767                         if (*(ptr+1) == 'n' || *(ptr+1) == 'N') {
1768                                 saveprompt = FALSE;
1769                         }
1770 #  endif
1771                 }
1772 # ifdef MFLOPPY
1773                 else
1774                     saveprompt = flags.asksavedisk;
1775 # endif
1776
1777                 (void) strncpy(SAVEP, bufp, SAVESIZE-1);
1778                 append_slash(SAVEP);
1779 # endif /* MICRO */
1780 #endif /*NOCWD_ASSUMPTIONS*/
1781
1782         } else if (match_varname(buf, "NAME", 4)) {
1783             (void) strncpy(plname, bufp, PL_NSIZ-1);
1784             plnamesuffix();
1785         } else if (match_varname(buf, "ROLE", 4) ||
1786                    match_varname(buf, "CHARACTER", 4)) {
1787             if ((len = str2role(bufp)) >= 0)
1788                 flags.initrole = len;
1789         } else if (match_varname(buf, "DOGNAME", 3)) {
1790             (void) strncpy(dogname, bufp, PL_PSIZ-1);
1791         } else if (match_varname(buf, "CATNAME", 3)) {
1792             (void) strncpy(catname, bufp, PL_PSIZ-1);
1793
1794         } else if (match_varname(buf, "BOULDER", 3)) {
1795             (void) get_uchars(fp, buf, bufp, &iflags.bouldersym, TRUE,
1796                               1, "BOULDER");
1797         } else if (match_varname(buf, "GRAPHICS", 4)) {
1798             len = get_uchars(fp, buf, bufp, translate, FALSE,
1799                              MAXPCHARS, "GRAPHICS");
1800             assign_graphics(translate, len, MAXPCHARS, 0);
1801         } else if (match_varname(buf, "DUNGEON", 4)) {
1802             len = get_uchars(fp, buf, bufp, translate, FALSE,
1803                              MAXDCHARS, "DUNGEON");
1804             assign_graphics(translate, len, MAXDCHARS, 0);
1805         } else if (match_varname(buf, "TRAPS", 4)) {
1806             len = get_uchars(fp, buf, bufp, translate, FALSE,
1807                              MAXTCHARS, "TRAPS");
1808             assign_graphics(translate, len, MAXTCHARS, MAXDCHARS);
1809         } else if (match_varname(buf, "EFFECTS", 4)) {
1810             len = get_uchars(fp, buf, bufp, translate, FALSE,
1811                              MAXECHARS, "EFFECTS");
1812             assign_graphics(translate, len, MAXECHARS, MAXDCHARS+MAXTCHARS);
1813
1814         } else if (match_varname(buf, "OBJECTS", 3)) {
1815             /* oc_syms[0] is the RANDOM object, unused */
1816             (void) get_uchars(fp, buf, bufp, &(oc_syms[1]), TRUE,
1817                                         MAXOCLASSES-1, "OBJECTS");
1818         } else if (match_varname(buf, "MONSTERS", 3)) {
1819             /* monsyms[0] is unused */
1820             (void) get_uchars(fp, buf, bufp, &(monsyms[1]), TRUE,
1821                                         MAXMCLASSES-1, "MONSTERS");
1822         } else if (match_varname(buf, "WARNINGS", 5)) {
1823             (void) get_uchars(fp, buf, bufp, translate, FALSE,
1824                                         WARNCOUNT, "WARNINGS");
1825             assign_warnings(translate);
1826 #ifdef WIZARD
1827         } else if (match_varname(buf, "WIZKIT", 6)) {
1828             (void) strncpy(wizkit, bufp, WIZKIT_MAX-1);
1829 #endif
1830 #ifdef AMIGA
1831         } else if (match_varname(buf, "FONT", 4)) {
1832                 char *t;
1833
1834                 if( t = strchr( buf+5, ':' ) )
1835                 {
1836                     *t = 0;
1837                     amii_set_text_font( buf+5, atoi( t + 1 ) );
1838                     *t = ':';
1839                 }
1840         } else if (match_varname(buf, "PATH", 4)) {
1841                 (void) strncpy(PATH, bufp, PATHLEN-1);
1842         } else if (match_varname(buf, "DEPTH", 5)) {
1843                 extern int amii_numcolors;
1844                 int val = atoi( bufp );
1845                 amii_numcolors = 1L << min( DEPTH, val );
1846         } else if (match_varname(buf, "DRIPENS", 7)) {
1847                 int i, val;
1848                 char *t;
1849                 for (i = 0, t = strtok(bufp, ",/"); t != (char *)0;
1850                                 i < 20 && (t = strtok((char*)0, ",/")), ++i) {
1851                         sscanf(t, "%d", &val );
1852                         flags.amii_dripens[i] = val;
1853                 }
1854         } else if (match_varname(buf, "SCREENMODE", 10 )) {
1855                 extern long amii_scrnmode;
1856                 if (!stricmp(bufp,"req"))
1857                     amii_scrnmode = 0xffffffff; /* Requester */
1858                 else if( sscanf(bufp, "%x", &amii_scrnmode) != 1 )
1859                     amii_scrnmode = 0;
1860         } else if (match_varname(buf, "MSGPENS", 7)) {
1861                 extern int amii_msgAPen, amii_msgBPen;
1862                 char *t = strtok(bufp, ",/");
1863                 if( t )
1864                 {
1865                     sscanf(t, "%d", &amii_msgAPen);
1866                     if( t = strtok((char*)0, ",/") )
1867                                 sscanf(t, "%d", &amii_msgBPen);
1868                 }
1869         } else if (match_varname(buf, "TEXTPENS", 8)) {
1870                 extern int amii_textAPen, amii_textBPen;
1871                 char *t = strtok(bufp, ",/");
1872                 if( t )
1873                 {
1874                     sscanf(t, "%d", &amii_textAPen);
1875                     if( t = strtok((char*)0, ",/") )
1876                                 sscanf(t, "%d", &amii_textBPen);
1877                 }
1878         } else if (match_varname(buf, "MENUPENS", 8)) {
1879                 extern int amii_menuAPen, amii_menuBPen;
1880                 char *t = strtok(bufp, ",/");
1881                 if( t )
1882                 {
1883                     sscanf(t, "%d", &amii_menuAPen);
1884                     if( t = strtok((char*)0, ",/") )
1885                                 sscanf(t, "%d", &amii_menuBPen);
1886                 }
1887         } else if (match_varname(buf, "STATUSPENS", 10)) {
1888                 extern int amii_statAPen, amii_statBPen;
1889                 char *t = strtok(bufp, ",/");
1890                 if( t )
1891                 {
1892                     sscanf(t, "%d", &amii_statAPen);
1893                     if( t = strtok((char*)0, ",/") )
1894                                 sscanf(t, "%d", &amii_statBPen);
1895                 }
1896         } else if (match_varname(buf, "OTHERPENS", 9)) {
1897                 extern int amii_otherAPen, amii_otherBPen;
1898                 char *t = strtok(bufp, ",/");
1899                 if( t )
1900                 {
1901                     sscanf(t, "%d", &amii_otherAPen);
1902                     if( t = strtok((char*)0, ",/") )
1903                                 sscanf(t, "%d", &amii_otherBPen);
1904                 }
1905         } else if (match_varname(buf, "PENS", 4)) {
1906                 extern unsigned short amii_init_map[ AMII_MAXCOLORS ];
1907                 int i;
1908                 char *t;
1909
1910                 for (i = 0, t = strtok(bufp, ",/");
1911                         i < AMII_MAXCOLORS && t != (char *)0;
1912                         t = strtok((char *)0, ",/"), ++i)
1913                 {
1914                         sscanf(t, "%hx", &amii_init_map[i]);
1915                 }
1916                 amii_setpens( amii_numcolors = i );
1917         } else if (match_varname(buf, "FGPENS", 6)) {
1918                 extern int foreg[ AMII_MAXCOLORS ];
1919                 int i;
1920                 char *t;
1921
1922                 for (i = 0, t = strtok(bufp, ",/");
1923                         i < AMII_MAXCOLORS && t != (char *)0;
1924                         t = strtok((char *)0, ",/"), ++i)
1925                 {
1926                         sscanf(t, "%d", &foreg[i]);
1927                 }
1928         } else if (match_varname(buf, "BGPENS", 6)) {
1929                 extern int backg[ AMII_MAXCOLORS ];
1930                 int i;
1931                 char *t;
1932
1933                 for (i = 0, t = strtok(bufp, ",/");
1934                         i < AMII_MAXCOLORS && t != (char *)0;
1935                         t = strtok((char *)0, ",/"), ++i)
1936                 {
1937                         sscanf(t, "%d", &backg[i]);
1938                 }
1939 #endif
1940 #ifdef USER_SOUNDS
1941         } else if (match_varname(buf, "SOUNDDIR", 8)) {
1942                 sounddir = (char *)strdup(bufp);
1943         } else if (match_varname(buf, "SOUND", 5)) {
1944                 add_sound_mapping(bufp);
1945 #endif
1946 #ifdef QT_GRAPHICS
1947         /* These should move to wc_ options */
1948         } else if (match_varname(buf, "QT_TILEWIDTH", 12)) {
1949                 extern char *qt_tilewidth;
1950                 if (qt_tilewidth == NULL)       
1951                         qt_tilewidth=(char *)strdup(bufp);
1952         } else if (match_varname(buf, "QT_TILEHEIGHT", 13)) {
1953                 extern char *qt_tileheight;
1954                 if (qt_tileheight == NULL)      
1955                         qt_tileheight=(char *)strdup(bufp);
1956         } else if (match_varname(buf, "QT_FONTSIZE", 11)) {
1957                 extern char *qt_fontsize;
1958                 if (qt_fontsize == NULL)
1959                         qt_fontsize=(char *)strdup(bufp);
1960         } else if (match_varname(buf, "QT_COMPACT", 10)) {
1961                 extern int qt_compact_mode;
1962                 qt_compact_mode = atoi(bufp);
1963 #endif
1964         } else
1965                 return 0;
1966         return 1;
1967 }
1968
1969 #ifdef USER_SOUNDS
1970 boolean
1971 can_read_file(filename)
1972 const char *filename;
1973 {
1974         return (access(filename, 4) == 0);
1975 }
1976 #endif /* USER_SOUNDS */
1977
1978 void
1979 read_config_file(filename)
1980 const char *filename;
1981 {
1982 #define tmp_levels      (char *)0
1983 #define tmp_ramdisk     (char *)0
1984
1985 #if defined(MICRO) || defined(WIN32)
1986 #undef tmp_levels
1987         char    tmp_levels[PATHLEN];
1988 # ifdef MFLOPPY
1989 #  ifndef AMIGA
1990 #undef tmp_ramdisk
1991         char    tmp_ramdisk[PATHLEN];
1992 #  endif
1993 # endif
1994 #endif
1995         char    buf[4*BUFSZ];
1996         FILE    *fp;
1997
1998         if (!(fp = fopen_config_file(filename))) return;
1999
2000 #if defined(MICRO) || defined(WIN32)
2001 # ifdef MFLOPPY
2002 #  ifndef AMIGA
2003         tmp_ramdisk[0] = 0;
2004 #  endif
2005 # endif
2006         tmp_levels[0] = 0;
2007 #endif
2008         /* begin detection of duplicate configfile options */
2009         set_duplicate_opt_detection(1);
2010
2011         while (fgets(buf, 4*BUFSZ, fp)) {
2012                 if (!parse_config_line(fp, buf, tmp_ramdisk, tmp_levels)) {
2013                         raw_printf("Bad option line:  \"%.50s\"", buf);
2014                         wait_synch();
2015                 }
2016         }
2017         (void) fclose(fp);
2018         
2019         /* turn off detection of duplicate configfile options */
2020         set_duplicate_opt_detection(0);
2021
2022 #if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
2023         /* should be superseded by fqn_prefix[] */
2024 # ifdef MFLOPPY
2025         Strcpy(permbones, tmp_levels);
2026 #  ifndef AMIGA
2027         if (tmp_ramdisk[0]) {
2028                 Strcpy(levels, tmp_ramdisk);
2029                 if (strcmp(permbones, levels))          /* if not identical */
2030                         ramdisk = TRUE;
2031         } else
2032 #  endif /* AMIGA */
2033                 Strcpy(levels, tmp_levels);
2034
2035         Strcpy(bones, levels);
2036 # endif /* MFLOPPY */
2037 #endif /* MICRO */
2038         return;
2039 }
2040
2041 #ifdef WIZARD
2042 STATIC_OVL FILE *
2043 fopen_wizkit_file()
2044 {
2045         FILE *fp;
2046 #if defined(VMS) || defined(UNIX)
2047         char    tmp_wizkit[BUFSZ];
2048 #endif
2049         char *envp;
2050
2051         envp = nh_getenv("WIZKIT");
2052         if (envp && *envp) (void) strncpy(wizkit, envp, WIZKIT_MAX - 1);
2053         if (!wizkit[0]) return (FILE *)0;
2054
2055 #ifdef UNIX
2056         if (access(wizkit, 4) == -1) {
2057                 /* 4 is R_OK on newer systems */
2058                 /* nasty sneaky attempt to read file through
2059                  * NetHack's setuid permissions -- this is a
2060                  * place a file name may be wholly under the player's
2061                  * control
2062                  */
2063                 raw_printf("Access to %s denied (%d).",
2064                                 wizkit, errno);
2065                 wait_synch();
2066                 /* fall through to standard names */
2067         } else
2068 #endif
2069         if ((fp = fopenp(wizkit, "r")) != (FILE *)0) {
2070             return(fp);
2071 #if defined(UNIX) || defined(VMS)
2072         } else {
2073             /* access() above probably caught most problems for UNIX */
2074             raw_printf("Couldn't open requested config file %s (%d).",
2075                                 wizkit, errno);
2076             wait_synch();
2077 #endif
2078         }
2079
2080 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
2081         if ((fp = fopenp(fqname(wizkit, CONFIGPREFIX, 0), "r"))
2082                                                                 != (FILE *)0)
2083                 return(fp);
2084 #else
2085 # ifdef VMS
2086         envp = nh_getenv("HOME");
2087         if (envp)
2088                 Sprintf(tmp_wizkit, "%s%s", envp, wizkit);
2089         else
2090                 Sprintf(tmp_wizkit, "%s%s", "sys$login:", wizkit);
2091         if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *)0)
2092                 return(fp);
2093 # else  /* should be only UNIX left */
2094         envp = nh_getenv("HOME");
2095         if (envp)
2096                 Sprintf(tmp_wizkit, "%s/%s", envp, wizkit);
2097         else    Strcpy(tmp_wizkit, wizkit);
2098         if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *)0)
2099                 return(fp);
2100         else if (errno != ENOENT) {
2101                 /* e.g., problems when setuid NetHack can't search home
2102                  * directory restricted to user */
2103                 raw_printf("Couldn't open default wizkit file %s (%d).",
2104                                         tmp_wizkit, errno);
2105                 wait_synch();
2106         }
2107 # endif
2108 #endif
2109         return (FILE *)0;
2110 }
2111
2112 void
2113 read_wizkit()
2114 {
2115         FILE *fp;
2116         char *ep, buf[BUFSZ];
2117         struct obj *otmp;
2118         boolean bad_items = FALSE, skip = FALSE;
2119
2120         if (!wizard || !(fp = fopen_wizkit_file())) return;
2121
2122         while (fgets(buf, (int)(sizeof buf), fp)) {
2123             ep = index(buf, '\n');
2124             if (skip) { /* in case previous line was too long */
2125                 if (ep) skip = FALSE; /* found newline; next line is normal */
2126             } else {
2127                 if (!ep) skip = TRUE; /* newline missing; discard next fgets */
2128                 else *ep = '\0';                /* remove newline */
2129
2130                 if (buf[0]) {
2131                         otmp = readobjnam(buf, (struct obj *)0, FALSE);
2132                         if (otmp) {
2133                             if (otmp != &zeroobj)
2134                                 otmp = addinv(otmp);
2135                         } else {
2136                             /* .60 limits output line width to 79 chars */
2137                             raw_printf("Bad wizkit item: \"%.60s\"", buf);
2138                             bad_items = TRUE;
2139                         }
2140                 }
2141             }
2142         }
2143         if (bad_items)
2144             wait_synch();
2145         (void) fclose(fp);
2146         return;
2147 }
2148
2149 #endif /*WIZARD*/
2150
2151 /* ----------  END CONFIG FILE HANDLING ----------- */
2152
2153 /* ----------  BEGIN SCOREBOARD CREATION ----------- */
2154
2155 /* verify that we can write to the scoreboard file; if not, try to create one */
2156 void
2157 check_recordfile(dir)
2158 const char *dir;
2159 {
2160 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) || defined(__MWERKS__)
2161 # pragma unused(dir)
2162 #endif
2163         const char *fq_record;
2164         int fd;
2165
2166 #if defined(UNIX) || defined(VMS)
2167         fq_record = fqname(RECORD, SCOREPREFIX, 0);
2168         fd = open(fq_record, O_RDWR, 0);
2169         if (fd >= 0) {
2170 # ifdef VMS     /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */
2171                 if (!file_is_stmlf(fd)) {
2172                     raw_printf(
2173                   "Warning: scoreboard file %s is not in stream_lf format",
2174                                 fq_record);
2175                     wait_synch();
2176                 }
2177 # endif
2178             (void) close(fd);   /* RECORD is accessible */
2179         } else if ((fd = open(fq_record, O_CREAT|O_RDWR, FCMASK)) >= 0) {
2180             (void) close(fd);   /* RECORD newly created */
2181 # if defined(VMS) && !defined(SECURE)
2182             /* Re-protect RECORD with world:read+write+execute+delete access. */
2183             (void) chmod(fq_record, FCMASK | 007);
2184 # endif /* VMS && !SECURE */
2185         } else {
2186             raw_printf("Warning: cannot write scoreboard file %s", fq_record);
2187             wait_synch();
2188         }
2189 #endif  /* !UNIX && !VMS */
2190 #if defined(MICRO) || defined(WIN32)
2191         char tmp[PATHLEN];
2192
2193 # ifdef OS2_CODEVIEW   /* explicit path on opening for OS/2 */
2194         /* how does this work when there isn't an explicit path or fopenp
2195          * for later access to the file via fopen_datafile? ? */
2196         (void) strncpy(tmp, dir, PATHLEN - 1);
2197         tmp[PATHLEN-1] = '\0';
2198         if ((strlen(tmp) + 1 + strlen(RECORD)) < (PATHLEN - 1)) {
2199                 append_slash(tmp);
2200                 Strcat(tmp, RECORD);
2201         }
2202         fq_record = tmp;
2203 # else
2204         Strcpy(tmp, RECORD);
2205         fq_record = fqname(RECORD, SCOREPREFIX, 0);
2206 # endif
2207
2208         if ((fd = open(fq_record, O_RDWR)) < 0) {
2209             /* try to create empty record */
2210 # if defined(AZTEC_C) || defined(_DCC) || (defined(__GNUC__) && defined(__AMIGA__))
2211             /* Aztec doesn't use the third argument */
2212             /* DICE doesn't like it */
2213             if ((fd = open(fq_record, O_CREAT|O_RDWR)) < 0) {
2214 # else
2215             if ((fd = open(fq_record, O_CREAT|O_RDWR, S_IREAD|S_IWRITE)) < 0) {
2216 # endif
2217         raw_printf("Warning: cannot write record %s", tmp);
2218                 wait_synch();
2219             } else
2220                 (void) close(fd);
2221         } else          /* open succeeded */
2222             (void) close(fd);
2223 #else /* MICRO || WIN32*/
2224
2225 # ifdef MAC
2226         /* Create the "record" file, if necessary */
2227         fq_record = fqname(RECORD, SCOREPREFIX, 0);
2228         fd = macopen (fq_record, O_RDWR | O_CREAT, TEXT_TYPE);
2229         if (fd != -1) macclose (fd);
2230 # endif /* MAC */
2231
2232 #endif /* MICRO || WIN32*/
2233 }
2234
2235 /* ----------  END SCOREBOARD CREATION ----------- */
2236
2237 /* ----------  BEGIN PANIC/IMPOSSIBLE LOG ----------- */
2238
2239 /*ARGSUSED*/
2240 void
2241 paniclog(type, reason)
2242 const char *type;       /* panic, impossible, trickery */
2243 const char *reason;     /* explanation */
2244 {
2245 #ifdef PANICLOG
2246         FILE *lfile;
2247         char buf[BUFSZ];
2248
2249         if (!program_state.in_paniclog) {
2250                 program_state.in_paniclog = 1;
2251                 lfile = fopen_datafile(PANICLOG, "a", TROUBLEPREFIX);
2252                 if (lfile) {
2253                     (void) fprintf(lfile, "%s %08ld: %s %s\n",
2254                                    version_string(buf), yyyymmdd((time_t)0L),
2255                                    type, reason);
2256                     (void) fclose(lfile);
2257                 }
2258                 program_state.in_paniclog = 0;
2259         }
2260 #endif /* PANICLOG */
2261         return;
2262 }
2263
2264 /* ----------  END PANIC/IMPOSSIBLE LOG ----------- */
2265
2266 #ifdef SELF_RECOVER
2267
2268 /* ----------  BEGIN INTERNAL RECOVER ----------- */
2269 boolean
2270 recover_savefile()
2271 {
2272         int gfd, lfd, sfd;
2273         int lev, savelev, hpid;
2274         xchar levc;
2275         struct version_info version_data;
2276         int processed[256];
2277         char savename[SAVESIZE], errbuf[BUFSZ];
2278
2279         for (lev = 0; lev < 256; lev++)
2280                 processed[lev] = 0;
2281
2282         /* level 0 file contains:
2283          *      pid of creating process (ignored here)
2284          *      level number for current level of save file
2285          *      name of save file nethack would have created
2286          *      and game state
2287          */
2288         gfd = open_levelfile(0, errbuf);
2289         if (gfd < 0) {
2290             raw_printf("%s\n", errbuf);
2291             return FALSE;
2292         }
2293         if (read(gfd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) {
2294             raw_printf(
2295 "\nCheckpoint data incompletely written or subsequently clobbered. Recovery impossible.");
2296             (void)close(gfd);
2297             return FALSE;
2298         }
2299         if (read(gfd, (genericptr_t) &savelev, sizeof(savelev))
2300                                                         != sizeof(savelev)) {
2301             raw_printf("\nCheckpointing was not in effect for %s -- recovery impossible.\n",
2302                         lock);
2303             (void)close(gfd);
2304             return FALSE;
2305         }
2306         if ((read(gfd, (genericptr_t) savename, sizeof savename)
2307                 != sizeof savename) ||
2308             (read(gfd, (genericptr_t) &version_data, sizeof version_data)
2309                 != sizeof version_data)) {
2310             raw_printf("\nError reading %s -- can't recover.\n", lock);
2311             (void)close(gfd);
2312             return FALSE;
2313         }
2314
2315         /* save file should contain:
2316          *      version info
2317          *      current level (including pets)
2318          *      (non-level-based) game state
2319          *      other levels
2320          */
2321         set_savefile_name();
2322         sfd = create_savefile();
2323         if (sfd < 0) {
2324             raw_printf("\nCannot recover savefile %s.\n", SAVEF);
2325             (void)close(gfd);
2326             return FALSE;
2327         }
2328
2329         lfd = open_levelfile(savelev, errbuf);
2330         if (lfd < 0) {
2331             raw_printf("\n%s\n", errbuf);
2332             (void)close(gfd);
2333             (void)close(sfd);
2334             delete_savefile();
2335             return FALSE;
2336         }
2337
2338         if (write(sfd, (genericptr_t) &version_data, sizeof version_data)
2339                 != sizeof version_data) {
2340             raw_printf("\nError writing %s; recovery failed.", SAVEF);
2341             (void)close(gfd);
2342             (void)close(sfd);
2343             delete_savefile();
2344             return FALSE;
2345         }
2346
2347         if (!copy_bytes(lfd, sfd)) {
2348                 (void) close(lfd);
2349                 (void) close(sfd);
2350                 delete_savefile();
2351                 return FALSE;
2352         }
2353         (void)close(lfd);
2354         processed[savelev] = 1;
2355
2356         if (!copy_bytes(gfd, sfd)) {
2357                 (void) close(lfd);
2358                 (void) close(sfd);
2359                 delete_savefile();
2360                 return FALSE;
2361         }
2362         (void)close(gfd);
2363         processed[0] = 1;
2364
2365         for (lev = 1; lev < 256; lev++) {
2366                 /* level numbers are kept in xchars in save.c, so the
2367                  * maximum level number (for the endlevel) must be < 256
2368                  */
2369                 if (lev != savelev) {
2370                         lfd = open_levelfile(lev, (char *)0);
2371                         if (lfd >= 0) {
2372                                 /* any or all of these may not exist */
2373                                 levc = (xchar) lev;
2374                                 write(sfd, (genericptr_t) &levc, sizeof(levc));
2375                                 if (!copy_bytes(lfd, sfd)) {
2376                                         (void) close(lfd);
2377                                         (void) close(sfd);
2378                                         delete_savefile();
2379                                         return FALSE;
2380                                 }
2381                                 (void)close(lfd);
2382                                 processed[lev] = 1;
2383                         }
2384                 }
2385         }
2386         (void)close(sfd);
2387
2388 #ifdef HOLD_LOCKFILE_OPEN
2389         really_close();
2390 #endif
2391         /*
2392          * We have a successful savefile!
2393          * Only now do we erase the level files.
2394          */
2395         for (lev = 0; lev < 256; lev++) {
2396                 if (processed[lev]) {
2397                         const char *fq_lock;
2398                         set_levelfile_name(lock, lev);
2399                         fq_lock = fqname(lock, LEVELPREFIX, 3);
2400                         (void) unlink(fq_lock);
2401                 }
2402         }
2403         return TRUE;
2404 }
2405
2406 boolean
2407 copy_bytes(ifd, ofd)
2408 int ifd, ofd;
2409 {
2410         char buf[BUFSIZ];
2411         int nfrom, nto;
2412
2413         do {
2414                 nfrom = read(ifd, buf, BUFSIZ);
2415                 nto = write(ofd, buf, nfrom);
2416                 if (nto != nfrom) return FALSE;
2417         } while (nfrom == BUFSIZ);
2418         return TRUE;
2419 }
2420
2421 /* ----------  END INTERNAL RECOVER ----------- */
2422 #endif /*SELF_RECOVER*/
2423
2424 /*files.c*/