xref: /openbsd/games/hack/hack.main.c (revision 25fdf802)
1 /*	$OpenBSD: hack.main.c,v 1.26 2023/09/06 11:53:56 jsg Exp $	*/
2 
3 /*
4  * Copyright (c) 1985, Stichting Centrum voor Wiskunde en Informatica,
5  * Amsterdam
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are
10  * met:
11  *
12  * - Redistributions of source code must retain the above copyright notice,
13  * this list of conditions and the following disclaimer.
14  *
15  * - Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in the
17  * documentation and/or other materials provided with the distribution.
18  *
19  * - Neither the name of the Stichting Centrum voor Wiskunde en
20  * Informatica, nor the names of its contributors may be used to endorse or
21  * promote products derived from this software without specific prior
22  * written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
25  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
27  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
28  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 /*
38  * Copyright (c) 1982 Jay Fenlason <hack@gnu.org>
39  * All rights reserved.
40  *
41  * Redistribution and use in source and binary forms, with or without
42  * modification, are permitted provided that the following conditions
43  * are met:
44  * 1. Redistributions of source code must retain the above copyright
45  *    notice, this list of conditions and the following disclaimer.
46  * 2. Redistributions in binary form must reproduce the above copyright
47  *    notice, this list of conditions and the following disclaimer in the
48  *    documentation and/or other materials provided with the distribution.
49  * 3. The name of the author may not be used to endorse or promote products
50  *    derived from this software without specific prior written permission.
51  *
52  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
53  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
54  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
55  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
56  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
57  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
58  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
59  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
60  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
61  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
62  */
63 
64 #include <sys/stat.h>
65 #include <errno.h>
66 
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <signal.h>
70 #include <unistd.h>
71 
72 #include "hack.h"
73 
74 #ifdef QUEST
75 #define	gamename	"quest"
76 #else
77 #define	gamename	"hack"
78 #endif
79 
80 extern char plname[PL_NSIZ], pl_character[PL_CSIZ];
81 extern struct permonst mons[CMNUM+2];
82 extern char genocided[60], fut_geno[60];
83 
84 void (*afternmv)(void);
85 int (*occupation)(void);
86 char *occtxt;			/* defined when occupation != NULL */
87 
88 int hackpid;				/* current pid */
89 int locknum;				/* max num of players */
90 #ifdef DEF_PAGER
91 char *catmore;				/* default pager */
92 #endif
93 char SAVEF[PL_NSIZ + 11] = "save/";	/* save/99999player */
94 char obuf[BUFSIZ];	/* BUFSIZ is defined in stdio.h */
95 
96 extern char *nomovemsg;
97 extern long wailmsg;
98 
99 #ifdef CHDIR
100 static void chdirx(char *, boolean);
101 #endif
102 
103 int
main(int argc,char ** argv)104 main(int argc, char **argv)
105 {
106 	int fd;
107 #ifdef CHDIR
108 	char *dir;
109 #endif
110 
111 	hackpid = getpid();
112 
113 #ifdef CHDIR			/* otherwise no chdir() */
114 	/*
115 	 * See if we must change directory to the playground.
116 	 * (Perhaps hack runs suid and playground is inaccessible
117 	 *  for the player.)
118 	 * The environment variable HACKDIR is overridden by a
119 	 *  -d command line option (must be the first option given)
120 	 */
121 
122 	dir = getenv("HACKDIR");
123 	if(argc > 1 && !strncmp(argv[1], "-d", 2)) {
124 		argc--;
125 		argv++;
126 		dir = argv[0]+2;
127 		if(*dir == '=' || *dir == ':') dir++;
128 		if(!*dir && argc > 1) {
129 			argc--;
130 			argv++;
131 			dir = argv[0];
132 		}
133 		if(!*dir)
134 		    error("Flag -d must be followed by a directory name.");
135 	}
136 #endif
137 
138 	/*
139 	 * Who am i? Algorithm: 1. Use name as specified in HACKOPTIONS
140 	 *			2. Use $LOGNAME or $USER	(if 1. fails)
141 	 *			3. Use getlogin()		(if 2. fails)
142 	 * The resulting name is overridden by command line options.
143 	 * If everything fails, or if the resulting name is some generic
144 	 * account like "games", "play", "player", "hack" then eventually
145 	 * we'll ask him.
146 	 * Note that we trust him here; it is possible to play under
147 	 * somebody else's name.
148 	 */
149 	{ char *s;
150 
151 	  initoptions();
152 	  if(!*plname && (s = getenv("LOGNAME")))
153 		(void) strlcpy(plname, s, sizeof(plname));
154 	  if(!*plname && (s = getenv("USER")))
155 		(void) strlcpy(plname, s, sizeof(plname));
156 	  if(!*plname && (s = getlogin()))
157 		(void) strlcpy(plname, s, sizeof(plname));
158 	}
159 
160 	/*
161 	 * Now we know the directory containing 'record' and
162 	 * may do a prscore().
163 	 */
164 	if(argc > 1 && !strncmp(argv[1], "-s", 2)) {
165 #ifdef CHDIR
166 		chdirx(dir,0);
167 #endif
168 		prscore(argc, argv);
169 		return 0;
170 	}
171 
172 	/*
173 	 * It seems he really wants to play.
174 	 * Remember tty modes, to be restored on exit.
175 	 */
176 	gettty();
177 	setvbuf(stdout, obuf, _IOFBF, sizeof obuf);
178 	umask(007);
179 	startup();
180 	cls();
181 	u.uhp = 1;	/* prevent RIP on early quits */
182 	u.ux = FAR;	/* prevent nscr() */
183 	(void) signal(SIGHUP, hackhangup);
184 
185 #ifdef CHDIR
186 	chdirx(dir,1);
187 #endif
188 
189 	/*
190 	 * Process options.
191 	 */
192 	while(argc > 1 && argv[1][0] == '-'){
193 		argv++;
194 		argc--;
195 		switch(argv[0][1]){
196 #ifdef WIZARD
197 		case 'D':
198 /*			if(!strcmp(getlogin(), WIZARD)) */
199 				wizard = TRUE;
200 /*			else
201 				printf("Sorry.\n"); */
202 			break;
203 #endif
204 #ifdef NEWS
205 		case 'n':
206 			flags.nonews = TRUE;
207 			break;
208 #endif
209 		case 'u':
210 			if(argv[0][2]) {
211 			  (void) strlcpy(plname, argv[0]+2, sizeof(plname));
212 			} else if(argc > 1) {
213 			  argc--;
214 			  argv++;
215 			  (void) strlcpy(plname, argv[0], sizeof(plname));
216 			} else
217 				printf("Player name expected after -u\n");
218 			break;
219 		default:
220 			/* allow -T for Tourist, etc. */
221 			(void) strlcpy(pl_character, argv[0]+1, sizeof(pl_character));
222 			/* printf("Unknown option: %s\n", *argv); */
223 		}
224 	}
225 
226 	if(argc > 1)
227 		locknum = atoi(argv[1]);
228 #ifdef MAX_NR_OF_PLAYERS
229 	if(!locknum || locknum > MAX_NR_OF_PLAYERS)
230 		locknum = MAX_NR_OF_PLAYERS;
231 #endif
232 #ifdef DEF_PAGER
233 	if(!(catmore = getenv("HACKPAGER")) && !(catmore = getenv("PAGER")))
234 		catmore = DEF_PAGER;
235 #endif
236 #ifdef MAIL
237 	getmailstatus();
238 #endif
239 #ifdef WIZARD
240 	if(wizard) (void) strlcpy(plname, "wizard", sizeof plname); else
241 #endif
242 	if(!*plname || !strncmp(plname, "player", 4)
243 		    || !strncmp(plname, "games", 4))
244 		askname();
245 	plnamesuffix();		/* strip suffix from name; calls askname() */
246 				/* again if suffix was whole name */
247 				/* accepts any suffix */
248 #ifdef WIZARD
249 	if(!wizard) {
250 #endif
251 		/*
252 		 * check for multiple games under the same name
253 		 * (if !locknum) or check max nr of players (otherwise)
254 		 */
255 		(void) signal(SIGQUIT,SIG_IGN);
256 		(void) signal(SIGINT,SIG_IGN);
257 		if(!locknum)
258 			(void) strlcpy(lock,plname,sizeof lock);
259 		getlock();	/* sets lock if locknum != 0 */
260 #ifdef WIZARD
261 	} else {
262 		char *sfoo;
263 		(void) strlcpy(lock,plname,sizeof lock);
264 		if ((sfoo = getenv("MAGIC")))
265 			while(*sfoo) {
266 				switch(*sfoo++) {
267 				case 'n': (void) srandom_deterministic(*sfoo++);
268 					break;
269 				}
270 			}
271 		if ((sfoo = getenv("GENOCIDED"))) {
272 			if(*sfoo == '!'){
273 				struct permonst *pm = mons;
274 				char *gp = genocided;
275 
276 				while(pm < mons+CMNUM+2){
277 					if(!strchr(sfoo, pm->mlet))
278 						*gp++ = pm->mlet;
279 					pm++;
280 				}
281 				*gp = 0;
282 			} else
283 				strlcpy(genocided, sfoo, sizeof genocided);
284 			strlcpy(fut_geno, genocided, sizeof fut_geno);
285 		}
286 	}
287 #endif
288 	setftty();
289 	(void) snprintf(SAVEF, sizeof SAVEF, "save/%u%s", getuid(), plname);
290 	regularize(SAVEF+5);		/* avoid . or / in name */
291 	if((fd = open(SAVEF, O_RDONLY)) >= 0) {
292 		(void) signal(SIGINT,done1);
293 		pline("Restoring old save file...");
294 		(void) fflush(stdout);
295 		if(!dorecover(fd))
296 			goto not_recovered;
297 		pline("Hello %s, welcome to %s!", plname, gamename);
298 		flags.move = 0;
299 	} else {
300 not_recovered:
301 		fobj = fcobj = invent = 0;
302 		fmon = fallen_down = 0;
303 		ftrap = 0;
304 		fgold = 0;
305 		flags.ident = 1;
306 		init_objects();
307 		u_init();
308 
309 		(void) signal(SIGINT,done1);
310 		mklev();
311 		u.ux = xupstair;
312 		u.uy = yupstair;
313 		(void) inshop();
314 		setsee();
315 		flags.botlx = 1;
316 		makedog();
317 		{ struct monst *mtmp;
318 		  if ((mtmp = m_at(u.ux, u.uy)))
319 			  mnexto(mtmp);	/* riv05!a3 */
320 		}
321 		seemons();
322 #ifdef NEWS
323 		if(flags.nonews || !readnews())
324 			/* after reading news we did docrt() already */
325 #endif
326 			docrt();
327 
328 		/* give welcome message before pickup messages */
329 		pline("Hello %s, welcome to %s!", plname, gamename);
330 
331 		pickup(1);
332 		read_engr_at(u.ux,u.uy);
333 		flags.move = 1;
334 	}
335 
336 	flags.moonphase = phase_of_the_moon();
337 	if(flags.moonphase == FULL_MOON) {
338 		pline("You are lucky! Full moon tonight.");
339 		u.uluck++;
340 	} else if(flags.moonphase == NEW_MOON) {
341 		pline("Be careful! New moon tonight.");
342 	}
343 
344 	initrack();
345 
346 	for(;;) {
347 		if(flags.move) {	/* actual time passed */
348 
349 			settrack();
350 
351 			if(moves%2 == 0 ||
352 			  (!(Fast & ~INTRINSIC) && (!Fast || rn2(3)))) {
353 				movemon();
354 				if(!rn2(70))
355 				    (void) makemon((struct permonst *)0, 0, 0);
356 			}
357 			if(Glib) glibr();
358 			hacktimeout();
359 			++moves;
360 			if(flags.time) flags.botl = 1;
361 			if(u.uhp < 1) {
362 				pline("You die...");
363 				done("died");
364 			}
365 			if(u.uhp*10 < u.uhpmax && moves-wailmsg > 50){
366 			    wailmsg = moves;
367 			    if(u.uhp == 1)
368 			    pline("You hear the wailing of the Banshee...");
369 			    else
370 			    pline("You hear the howling of the CwnAnnwn...");
371 			}
372 			if(u.uhp < u.uhpmax) {
373 				if(u.ulevel > 9) {
374 					if(Regeneration || !(moves%3)) {
375 					    flags.botl = 1;
376 					    u.uhp += rnd((int) u.ulevel-9);
377 					    if(u.uhp > u.uhpmax)
378 						u.uhp = u.uhpmax;
379 					}
380 				} else if(Regeneration ||
381 					(!(moves%(22-u.ulevel*2)))) {
382 					flags.botl = 1;
383 					u.uhp++;
384 				}
385 			}
386 			if(Teleportation && !rn2(85)) tele();
387 			if(Searching && multi >= 0) (void) dosearch();
388 			gethungry();
389 			invault();
390 			amulet();
391 		}
392 		if(multi < 0) {
393 			if(!++multi){
394 				pline("%s", nomovemsg ? nomovemsg :
395 					"You can move again.");
396 				nomovemsg = 0;
397 				if(afternmv) (*afternmv)();
398 				afternmv = 0;
399 			}
400 		}
401 
402 		find_ac();
403 #ifndef QUEST
404 		if(!flags.mv || Blind)
405 #endif
406 		{
407 			seeobjs();
408 			seemons();
409 			nscr();
410 		}
411 		if(flags.botl || flags.botlx) bot();
412 
413 		flags.move = 1;
414 
415 		if(multi >= 0 && occupation) {
416 			if(monster_nearby())
417 				stop_occupation();
418 			else if ((*occupation)() == 0)
419 				occupation = 0;
420 			continue;
421 		}
422 
423 		if(multi > 0) {
424 #ifdef QUEST
425 			if(flags.run >= 4) finddir();
426 #endif
427 			lookaround();
428 			if(!multi) {	/* lookaround may clear multi */
429 				flags.move = 0;
430 				continue;
431 			}
432 			if(flags.mv) {
433 				if(multi < COLNO && !--multi)
434 					flags.mv = flags.run = 0;
435 				domove();
436 			} else {
437 				--multi;
438 				rhack(save_cm);
439 			}
440 		} else if(multi == 0) {
441 #ifdef MAIL
442 			ckmailstatus();
443 #endif
444 			rhack(NULL);
445 		}
446 		if(multi && multi%7 == 0)
447 			(void) fflush(stdout);
448 	}
449 }
450 
451 void
glo(int foo)452 glo(int foo)
453 {
454 	/* construct the string  xlock.n  */
455 	char *tf;
456 
457 	tf = lock;
458 	while(*tf && *tf != '.') tf++;
459 	(void) snprintf(tf, lock + sizeof lock - tf, ".%d", foo);
460 }
461 
462 /*
463  * plname is filled either by an option (-u Player  or  -uPlayer) or
464  * explicitly (-w implies wizard) or by askname.
465  * It may still contain a suffix denoting pl_character.
466  */
467 void
askname(void)468 askname(void)
469 {
470 	int c,ct;
471 
472 	printf("\nWho are you? ");
473 	(void) fflush(stdout);
474 	ct = 0;
475 	while((c = getchar()) != '\n'){
476 		if(c == EOF) error("End of input\n");
477 		/* some people get confused when their erase char is not ^H */
478 		if(c == '\010') {
479 			if(ct) ct--;
480 			continue;
481 		}
482 		if(c != '-')
483 		if(c < 'A' || (c > 'Z' && c < 'a') || c > 'z') c = '_';
484 		if(ct < sizeof(plname)-1) plname[ct++] = c;
485 	}
486 	plname[ct] = 0;
487 	if(ct == 0) askname();
488 }
489 
490 void
impossible(const char * s,...)491 impossible(const char *s, ...)
492 {
493 	va_list ap;
494 
495 	va_start(ap, s);
496 	vpline(s, ap);
497 	va_end(ap);
498 	pline("Program in disorder - perhaps you'd better Quit.");
499 }
500 
501 #ifdef CHDIR
502 static void
chdirx(char * dir,boolean wr)503 chdirx(char *dir, boolean wr)
504 {
505 	gid_t gid;
506 
507 #ifdef SECURE
508 	if(dir					/* User specified directory? */
509 #ifdef HACKDIR
510 	       && strcmp(dir, HACKDIR)		/* and not the default? */
511 #endif
512 		) {
513 		/* revoke privs */
514 		gid = getgid();
515 		setresgid(gid, gid, gid);
516 	}
517 #endif
518 
519 #ifdef HACKDIR
520 	if(dir == NULL)
521 		dir = HACKDIR;
522 #endif
523 
524 	if(dir && chdir(dir) == -1) {
525 		perror(dir);
526 		error("Cannot chdir to %s.", dir);
527 	}
528 
529 	/* warn the player if he cannot write the record file */
530 	/* warn the player if he cannot read the permanent lock file */
531 	/* warn the player if he cannot create the save directory */
532 	/* perhaps we should also test whether . is writable */
533 	/* unfortunately the access systemcall is worthless */
534 	if(wr) {
535 	    int fd;
536 
537 	    if(dir == NULL)
538 		dir = ".";
539 	    if((fd = open(RECORD, O_RDWR | O_CREAT, FMASK)) == -1) {
540 		printf("Warning: cannot write %s/%s", dir, RECORD);
541 		getret();
542 	    } else
543 		(void) close(fd);
544 	    if((fd = open(HLOCK, O_RDONLY | O_CREAT, FMASK)) == -1) {
545 		printf("Warning: cannot read %s/%s", dir, HLOCK);
546 		getret();
547 	    } else
548 		(void) close(fd);
549 	    if(mkdir("save", 0770) && errno != EEXIST) {
550 		printf("Warning: cannot create %s/save", dir);
551 		getret();
552 	    }
553 	}
554 }
555 #endif
556 
557 void
stop_occupation(void)558 stop_occupation(void)
559 {
560 	if(occupation) {
561 		pline("You stop %s.", occtxt);
562 		occupation = 0;
563 	}
564 }
565