1 /*	SCCS Id: @(#)pcmain.c	3.4	2002/08/22	*/
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed.  See license for details. */
4 
5 /* main.c - MSDOS, OS/2, ST, Amiga, and NT NetHack */
6 
7 #include "hack.h"
8 #include "dlb.h"
9 
10 /* WAC for DEF_GAME_NAME */
11 #ifdef SHORT_FILENAMES
12 #include "patchlev.h"
13 #else
14 #include "patchlevel.h"
15 #endif
16 
17 #ifndef NO_SIGNAL
18 #include <signal.h>
19 #endif
20 
21 #include <ctype.h>
22 
23 #if !defined(AMIGA) && !defined(GNUDOS)
24 #include <sys\stat.h>
25 #else
26 # ifdef GNUDOS
27 #include <sys/stat.h>
28 # endif
29 #endif
30 
31 #ifdef WIN32
32 #include "win32api.h"			/* for GetModuleFileName */
33 #endif
34 
35 #ifdef __DJGPP__
36 #include <unistd.h>			/* for getcwd() prototype */
37 #endif
38 
39 #ifdef OVL0
40 #define SHARED_DCL
41 #else
42 #define SHARED_DCL extern
43 #endif
44 
45 
46 SHARED_DCL char orgdir[PATHLEN];	/* also used in pcsys.c, amidos.c */
47 
48 #ifdef TOS
49 boolean run_from_desktop = TRUE;	/* should we pause before exiting?? */
50 # ifdef __GNUC__
51 long _stksize = 16*1024;
52 # endif
53 #endif
54 
55 #ifdef AMIGA
56 extern int bigscreen;
57 void NDECL( preserve_icon );
58 #endif
59 
60 STATIC_DCL void FDECL(process_options,(int argc,char **argv));
61 STATIC_DCL void NDECL(nhusage);
62 
63 #if defined(MICRO) || defined(WIN32) || defined(OS2)
64 extern void FDECL(nethack_exit,(int));
65 #else
66 #define nethack_exit exit
67 #endif
68 
69 #ifdef WIN32
70 extern boolean getreturn_enabled;	/* from sys/share/pcsys.c */
71 #endif
72 
73 #if defined(MSWIN_GRAPHICS)
74 extern void NDECL(mswin_destroy_reg);
75 #endif
76 
77 #ifdef EXEPATH
78 STATIC_DCL char *FDECL(exepath,(char *));
79 #endif
80 
81 #ifdef OVL0
82 int FDECL(main, (int,char **));
83 #endif
84 
85 extern void FDECL(pcmain, (int,char **));
86 
87 #if defined(__BORLANDC__) && !defined(_WIN32)
88 void NDECL( startup );
89 # ifdef OVLB
90 unsigned _stklen = STKSIZ;
91 # else
92 extern unsigned _stklen;
93 # endif
94 #endif
95 
96 #ifdef OVL0
97 /* If the graphics version is built, we don't need a main; it is skipped
98  * to help MinGW decide which entry point to choose. If both main and
99  * WinMain exist, the resulting executable won't work correctly.
100  */
101 #ifndef MSWIN_GRAPHICS
102 int
main(argc,argv)103 main(argc,argv)
104 int argc;
105 char *argv[];
106 {
107      pcmain(argc,argv);
108 #ifdef LAN_FEATURES
109      init_lan_features();
110 #endif
111      moveloop();
112      nethack_exit(EXIT_SUCCESS);
113      /*NOTREACHED*/
114      return 0;
115 }
116 #endif /*MSWIN_GRAPHICS*/
117 #endif /*OVL0*/
118 #ifdef OVL1
119 
120 void
pcmain(argc,argv)121 pcmain(argc,argv)
122 int argc;
123 char *argv[];
124 {
125 
126 	register int fd;
127 	register char *dir;
128 
129 #if defined(__BORLANDC__) && !defined(_WIN32)
130 	startup();
131 #endif
132 
133 #ifdef TOS
134 	long clock_time;
135 	if (*argv[0]) { 		/* only a CLI can give us argv[0] */
136 		hname = argv[0];
137 		run_from_desktop = FALSE;
138 	} else
139 #endif
140 #ifdef __DJGPP__
141         if (*argv[0]) hname = argv[0];  /* DJGPP can give us argv[0] */
142         else
143 #endif
144                 hname = "NetHack";      /* used for syntax messages */
145 
146 	choose_windows(DEFAULT_WINDOW_SYS);
147 
148 #if !defined(AMIGA) && !defined(GNUDOS)
149 	/* Save current directory and make sure it gets restored when
150 	 * the game is exited.
151 	 */
152 	if (getcwd(orgdir, sizeof orgdir) == (char *)0)
153 		error("NetHack: current directory path too long");
154 # ifndef NO_SIGNAL
155 	signal(SIGINT, (SIG_RET_TYPE) nethack_exit);	/* restore original directory */
156 # endif
157 #endif /* !AMIGA && !GNUDOS */
158 
159 	dir = nh_getenv("NETHACKDIR");
160 	if (dir == (char *)0)
161 		dir = nh_getenv("HACKDIR");
162 #ifdef EXEPATH
163 	if (dir == (char *)0)
164 		dir = exepath(argv[0]);
165 #endif
166 	if (dir != (char *)0) {
167 		(void) strncpy(hackdir, dir, PATHLEN - 1);
168 		hackdir[PATHLEN-1] = '\0';
169 #ifdef NOCWD_ASSUMPTIONS
170 		{
171 		    int prefcnt;
172 
173 		    fqn_prefix[0] = (char *)alloc(strlen(hackdir)+2);
174 		    Strcpy(fqn_prefix[0], hackdir);
175 		    append_slash(fqn_prefix[0]);
176 		    for (prefcnt = 1; prefcnt < PREFIX_COUNT; prefcnt++)
177 			fqn_prefix[prefcnt] = fqn_prefix[0];
178 		}
179 #endif
180 #ifdef CHDIR
181 		chdirx (dir, 1);
182 #endif
183 	}
184 #ifdef AMIGA
185 # ifdef CHDIR
186 	/*
187 	 * If we're dealing with workbench, change the directory.  Otherwise
188 	 * we could get "Insert disk in drive 0" messages. (Must be done
189 	 * before initoptions())....
190 	 */
191 	if(argc == 0)
192 		chdirx(HACKDIR, 1);
193 # endif
194 	ami_wininit_data();
195 #endif
196 #if defined(WIN32) && defined(PROXY_GRAPHICS)
197 	/* Handle --proxy before options, if supported */
198 	if (argc > 1 && !strcmp(argv[1], "--proxy")) {
199 	    argv[1] = argv[0];
200 	    argc--;
201 	    argv++;
202 	    set_binary_mode(0, O_RDONLY | O_BINARY);
203 	    set_binary_mode(1, O_WRONLY | O_BINARY);
204 	    choose_windows("proxy");
205 	    lock_windows(TRUE);		/* Can't be overridden from options */
206 	}
207 #endif
208 	initoptions();
209 
210 #if defined(TOS) && defined(TEXTCOLOR)
211 	if (iflags.BIOS && iflags.use_color)
212 		set_colors();
213 #endif
214 	if (!hackdir[0])
215 #if !defined(LATTICE) && !defined(AMIGA)
216 		Strcpy(hackdir, orgdir);
217 #else
218 		Strcpy(hackdir, HACKDIR);
219 #endif
220 	if(argc > 1) {
221 	    if (!strncmp(argv[1], "-d", 2) && argv[1][2] != 'e') {
222 		/* avoid matching "-dec" for DECgraphics; since the man page
223 		 * says -d directory, hope nobody's using -desomething_else
224 		 */
225 		argc--;
226 		argv++;
227 		dir = argv[0]+2;
228 		if(*dir == '=' || *dir == ':') dir++;
229 		if(!*dir && argc > 1) {
230 			argc--;
231 			argv++;
232 			dir = argv[0];
233 		}
234 		if(!*dir)
235 		    error("Flag -d must be followed by a directory name.");
236 		Strcpy(hackdir, dir);
237 	    }
238 	    if (argc > 1) {
239 
240 		/*
241 		 * Now we know the directory containing 'record' and
242 		 * may do a prscore().
243 		 */
244 		if (!strncmp(argv[1], "-s", 2)) {
245 #ifdef CHDIR
246 			chdirx(hackdir,0);
247 #endif
248 			prscore(argc, argv);
249 			nethack_exit(EXIT_SUCCESS);
250 		}
251 
252 #ifdef MSWIN_GRAPHICS
253 		if (!strncmpi(argv[1], "-clearreg", 6)) {	/* clear registry */
254 			mswin_destroy_reg();
255 			nethack_exit(EXIT_SUCCESS);
256 		}
257 #endif
258 		/* Don't initialize the window system just to print usage */
259                 /* WAC '--help' inits help */
260 		if (!strncmp(argv[1], "-?", 2)
261                     || !strncmp(argv[1], "--help", 6)
262                     || !strncmp(argv[1], "/?", 2)) {
263 			nhusage();
264 			nethack_exit(EXIT_SUCCESS);
265 		}
266 	    }
267 	}
268 
269 	/*
270 	 * It seems you really want to play.
271 	 */
272 #ifdef TOS
273 	if (comp_times((long)time(&clock_time)))
274 		error("Your clock is incorrectly set!");
275 #endif
276 	u.uhp = 1;	/* prevent RIP on early quits */
277 	u.ux = 0;	/* prevent flush_screen() */
278 
279 	/* chdir shouldn't be called before this point to keep the
280 	 * code parallel to other ports.
281 	 */
282 #ifdef CHDIR
283 	chdirx(hackdir,1);
284 #endif
285 
286 #ifdef MSDOS
287 	process_options(argc, argv);
288 	init_nhwindows(&argc,argv);
289 #else
290 	init_nhwindows(&argc,argv);
291 	process_options(argc, argv);
292 #endif
293 
294 #ifdef MFLOPPY
295 	set_lock_and_bones();
296 # ifndef AMIGA
297 	copybones(FROMPERM);
298 # endif
299 #endif
300 
301 	if (!*plname)
302 		askname();
303 	plnamesuffix(); 	/* strip suffix from name; calls askname() */
304 				/* again if suffix was whole name */
305 				/* accepts any suffix */
306 #ifdef WIZARD
307 	if (wizard) {
308 # ifdef KR1ED
309 		if(!strcmp(plname, WIZARD_NAME))
310 # else
311 		if(!strcmp(plname, WIZARD))
312 # endif
313 			Strcpy(plname, "wizard");
314 		else {
315 			wizard = FALSE;
316 			discover = TRUE;
317 		}
318 	}
319 #endif /* WIZARD */
320 #if defined(PC_LOCKING)
321 	/* 3.3.0 added this to support detection of multiple games
322 	 * under the same plname on the same machine in a windowed
323 	 * or multitasking environment.
324 	 *
325 	 * That allows user confirmation prior to overwriting the
326 	 * level files of a game in progress.
327 	 *
328 	 * Also prevents an aborted game's level files from being
329 	 * overwritten without confirmation when a user starts up
330 	 * another game with the same player name.
331 	 */
332 # if defined(WIN32)
333 	/* Obtain the name of the logged on user and incorporate
334 	 * it into the name. */
335 	Sprintf(lock, "%s-%s",get_username(0),plname);
336 # else
337 	Strcpy(lock,plname);
338 	regularize(lock);
339 # endif
340 	getlock();
341 #else   /* PC_LOCKING */
342 # ifdef AMIGA /* We'll put the bones & levels in the user specified directory -jhsa */
343 	Strcat(lock,plname);
344 	Strcat(lock,".99");
345 # else
346 #  ifndef MFLOPPY
347 	/* I'm not sure what, if anything, is left here, but MFLOPPY has
348 	 * conflicts with set_lock_and_bones() in files.c.
349 	 */
350 	Strcpy(lock,plname);
351 	Strcat(lock,".99");
352 	regularize(lock);	/* is this necessary? */
353 				/* not compatible with full path a la AMIGA */
354 #  endif
355 # endif
356 #endif
357 
358 	/* Set up level 0 file to keep the game state.
359 	 */
360 	fd = create_levelfile(0, (char *)0);
361 	if (fd < 0) {
362 		raw_print("Cannot create lock file");
363 	} else {
364 		hackpid = 1;
365 		write(fd, (genericptr_t) &hackpid, sizeof(hackpid));
366 		close(fd);
367 	}
368 #ifdef MFLOPPY
369 	level_info[0].where = ACTIVE;
370 #endif
371 
372 	/*
373 	 * Initialisation of the boundaries of the mazes
374 	 * Both boundaries have to be even.
375 	 */
376 
377 	x_maze_max = COLNO-1;
378 	if (x_maze_max % 2)
379 		x_maze_max--;
380 	y_maze_max = ROWNO-1;
381 	if (y_maze_max % 2)
382 		y_maze_max--;
383 
384 	/*
385 	 *  Initialize the vision system.  This must be before mklev() on a
386 	 *  new game or before a level restore on a saved game.
387 	 */
388 	vision_init();
389 
390 	dlb_init();
391 
392 	display_gamewindows();
393 #ifdef WIN32
394 	getreturn_enabled = TRUE;
395 #endif
396 
397 	if ((fd = restore_saved_game()) >= 0) {
398 #ifdef WIZARD
399 		/* Since wizard is actually flags.debug, restoring might
400 		 * overwrite it.
401 		 */
402 		boolean remember_wiz_mode = wizard;
403 #endif
404 #ifndef NO_SIGNAL
405 		(void) signal(SIGINT, (SIG_RET_TYPE) done1);
406 #endif
407 #ifdef NEWS
408 		if(iflags.news){
409 		    display_file_area(NEWS_AREA, NEWS, FALSE);
410 		    iflags.news = FALSE;
411 		}
412 #endif
413 		pline("Restoring save file...");
414 		mark_synch();	/* flush output */
415 
416 		if(!dorecover(fd))
417 			goto not_recovered;
418 #ifdef WIZARD
419 		if(!wizard && remember_wiz_mode) wizard = TRUE;
420 #endif
421 		check_special_room(FALSE);
422 		if (discover)
423 			You("are in non-scoring discovery mode.");
424 
425 		if (discover || wizard) {
426 			if(yn("Do you want to keep the save file?") == 'n'){
427 				(void) delete_savefile();
428 			}
429 		}
430 
431 		flags.move = 0;
432 	} else {
433 not_recovered:
434 		player_selection();
435 		newgame();
436 		if (discover)
437 			You("are in non-scoring discovery mode.");
438 
439 		flags.move = 0;
440 		set_wear();
441 		(void) pickup(1);
442 		sense_engr_at(u.ux,u.uy,FALSE);
443 	}
444 #ifdef __DJGPP__
445         /* WAC try to set title bar for Win95 DOS Boxes */
446 /*        {
447                 char titlebuf[BUFSZ];
448                 sprintf(titlebuf,"%s - %s", DEF_GAME_NAME, plname);
449                 (void) w95_setapptitle (titlebuf);
450         }*/
451 #endif
452 
453 #ifndef NO_SIGNAL
454 	(void) signal(SIGINT, SIG_IGN);
455 #endif
456 #ifdef OS2
457 	gettty(); /* somehow ctrl-P gets turned back on during startup ... */
458 #endif
459 	return;
460 }
461 
462 STATIC_OVL void
process_options(argc,argv)463 process_options(argc, argv)
464 int argc;
465 char *argv[];
466 {
467 	int i;
468 
469 
470 	/*
471 	 * Process options.
472 	 */
473 	while(argc > 1 && argv[1][0] == '-'){
474 		argv++;
475 		argc--;
476 		switch(argv[0][1]){
477 		case 'a':
478 			if (argv[0][2]) {
479 			    if ((i = str2align(&argv[0][2])) >= 0)
480 			    	flags.initalign = i;
481 			} else if (argc > 1) {
482 				argc--;
483 				argv++;
484 			    if ((i = str2align(argv[0])) >= 0)
485 			    	flags.initalign = i;
486 			}
487 			break;
488 		case 'D':
489 		case 'Z':
490 # ifdef WIZARD
491 			/* If they don't have a valid wizard name, it'll be
492 			 * changed to discover later.  Cannot check for
493 			 * validity of the name right now--it might have a
494 			 * character class suffix, for instance.
495 			 */
496 				wizard = TRUE;
497 				break;
498 # endif
499 		case 'X':
500 			discover = TRUE;
501 			break;
502 #ifdef NEWS
503 		case 'n':
504 			iflags.news = FALSE;
505 			break;
506 #endif
507 		case 'u':
508 			if(argv[0][2])
509 			  (void) strncpy(plname, argv[0]+2, sizeof(plname)-1);
510 			else if(argc > 1) {
511 			  argc--;
512 			  argv++;
513 			  (void) strncpy(plname, argv[0], sizeof(plname)-1);
514 			} else
515 				raw_print("Player name expected after -u");
516 			break;
517 #ifndef AMIGA
518 		case 'i':
519 			if (!strncmpi(argv[0]+1, "IBM", 3))
520 				switch_graphics(IBM_GRAPHICS);
521 			break;
522 		case 'd':
523 			if (!strncmpi(argv[0]+1, "DEC", 3))
524 				switch_graphics(DEC_GRAPHICS);
525 			break;
526 #endif
527 		case 'g':
528 			if (argv[0][2]) {
529 			    if ((i = str2gend(&argv[0][2])) >= 0)
530 			    	flags.initgend = i;
531 			} else if (argc > 1) {
532 				argc--;
533 				argv++;
534 			    if ((i = str2gend(argv[0])) >= 0)
535 			    	flags.initgend = i;
536 			}
537 			break;
538 		case 'p': /* profession (role) */
539 			if (argv[0][2]) {
540 			    if ((i = str2role(&argv[0][2])) >= 0)
541 			    	flags.initrole = i;
542 			} else if (argc > 1) {
543 				argc--;
544 				argv++;
545 			    if ((i = str2role(argv[0])) >= 0)
546 			    	flags.initrole = i;
547 			}
548 			break;
549 		case 'r': /* race */
550 			if (argv[0][2]) {
551 			    if ((i = str2race(&argv[0][2])) >= 0)
552 			    	flags.initrace = i;
553 			} else if (argc > 1) {
554 				argc--;
555 				argv++;
556 			    if ((i = str2race(argv[0])) >= 0)
557 			    	flags.initrace = i;
558 			}
559 			break;
560 #ifdef MFLOPPY
561 # ifndef AMIGA
562 		/* Player doesn't want to use a RAM disk
563 		 */
564 		case 'R':
565 			ramdisk = FALSE;
566 			break;
567 # endif
568 #endif
569 #ifdef AMIGA
570 			/* interlaced and non-interlaced screens */
571 		case 'L':
572 			bigscreen = 1;
573 			break;
574 		case 'l':
575 			bigscreen = -1;
576 			break;
577 #endif
578 		case '@':
579 			flags.randomall = 1;
580 			break;
581 		default:
582 			if ((i = str2role(&argv[0][1])) >= 0) {
583 			    flags.initrole = i;
584 				break;
585 			} else raw_printf("\nUnknown switch: %s", argv[0]);
586 			/* FALL THROUGH */
587 		case '?':
588 			nhusage();
589 			nethack_exit(EXIT_SUCCESS);
590 		}
591 	}
592 }
593 
594 STATIC_OVL void
nhusage()595 nhusage()
596 {
597 	char buf1[BUFSZ];
598 
599 	/* -role still works for those cases which aren't already taken, but
600 	 * is deprecated and will not be listed here.
601 	 */
602 	(void) Sprintf(buf1,
603 "\nUsage: %s [-d dir] -s [-r race] [-p profession] [maxrank] [name]...\n       or",
604 		hname);
605 	if (!iflags.window_inited)
606 		raw_printf(buf1);
607 	else
608 		(void)	printf(buf1);
609 	(void) Sprintf(buf1,
610 	 "\n       %s [-d dir] [-u name] [-r race] [-p profession] [-[DX]]",
611 		hname);
612 #ifdef NEWS
613 	Strcat(buf1," [-n]");
614 #endif
615 #ifndef AMIGA
616 	Strcat(buf1," [-I] [-i] [-d]");
617 #endif
618 #ifdef MFLOPPY
619 # ifndef AMIGA
620 	Strcat(buf1," [-R]");
621 # endif
622 #endif
623 #ifdef AMIGA
624 	Strcat(buf1," [-[lL]]");
625 #endif
626 	if (!iflags.window_inited)
627 		raw_printf("%s\n",buf1);
628 	else
629 		(void) printf("%s\n",buf1);
630 }
631 
632 #ifdef CHDIR
633 void
chdirx(dir,wr)634 chdirx(dir, wr)
635 char *dir;
636 boolean wr;
637 {
638 # ifdef AMIGA
639 	static char thisdir[] = "";
640 # else
641 	static char thisdir[] = ".";
642 # endif
643 	if(dir && chdir(dir) < 0) {
644 		error("Cannot chdir to %s.", dir);
645 	}
646 
647 # if !defined(AMIGA) && !defined(__CYGWIN__)
648 	/* Change the default drive as well.
649 	 */
650 	chdrive(dir);
651 # endif
652 
653 	/* warn the player if we can't write the record file */
654 	/* perhaps we should also test whether . is writable */
655 	/* unfortunately the access system-call is worthless */
656 	if (wr) check_recordfile(dir ? dir : thisdir);
657 }
658 #endif /* CHDIR */
659 #endif /*OVL1*/
660 #ifdef OVLB
661 
662 #ifdef PORT_HELP
663 # if defined(MSDOS) || defined(WIN32)
664 void
port_help()665 port_help()
666 {
667     /* display port specific help file */
668     display_file_area(FILE_AREA_SHARE, PORT_HELP, 1 );
669 }
670 # endif /* MSDOS || WIN32 */
671 #endif /* PORT_HELP */
672 
673 #ifdef EXEPATH
674 #ifdef __DJGPP__
675 #define PATH_SEPARATOR '/'
676 #else
677 #define PATH_SEPARATOR '\\'
678 #endif
679 
680 #define EXEPATHBUFSZ 256
681 char exepathbuf[EXEPATHBUFSZ];
682 
exepath(str)683 char *exepath(str)
684 char *str;
685 {
686 	char *tmp, *tmp2;
687 	int bsize;
688 
689 	if (!str) return (char *)0;
690 	bsize = EXEPATHBUFSZ;
691 	tmp = exepathbuf;
692 #ifndef WIN32
693 	Strcpy (tmp, str);
694 #else
695 	#ifdef UNICODE
696 	{
697 		TCHAR wbuf[BUFSZ];
698 		GetModuleFileName((HANDLE)0, wbuf, BUFSZ);
699 		WideCharToMultiByte(CP_ACP, 0, wbuf, -1, tmp, bsize, NULL, NULL);
700 	}
701 	#else
702 		*(tmp + GetModuleFileName((HANDLE)0, tmp, bsize)) = '\0';
703 	#endif
704 #endif
705 	tmp2 = strrchr(tmp, PATH_SEPARATOR);
706 	if (tmp2) *tmp2 = '\0';
707 	return tmp;
708 }
709 #endif /* EXEPATH */
710 #endif /*OVLB*/
711 /*pcmain.c*/
712