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