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