1 /* $OpenBSD: hack.main.c,v 1.25 2023/06/03 15:19:38 op 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 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 extern struct monst *makemon(); 354 movemon(); 355 if(!rn2(70)) 356 (void) makemon((struct permonst *)0, 0, 0); 357 } 358 if(Glib) glibr(); 359 hacktimeout(); 360 ++moves; 361 if(flags.time) flags.botl = 1; 362 if(u.uhp < 1) { 363 pline("You die..."); 364 done("died"); 365 } 366 if(u.uhp*10 < u.uhpmax && moves-wailmsg > 50){ 367 wailmsg = moves; 368 if(u.uhp == 1) 369 pline("You hear the wailing of the Banshee..."); 370 else 371 pline("You hear the howling of the CwnAnnwn..."); 372 } 373 if(u.uhp < u.uhpmax) { 374 if(u.ulevel > 9) { 375 if(Regeneration || !(moves%3)) { 376 flags.botl = 1; 377 u.uhp += rnd((int) u.ulevel-9); 378 if(u.uhp > u.uhpmax) 379 u.uhp = u.uhpmax; 380 } 381 } else if(Regeneration || 382 (!(moves%(22-u.ulevel*2)))) { 383 flags.botl = 1; 384 u.uhp++; 385 } 386 } 387 if(Teleportation && !rn2(85)) tele(); 388 if(Searching && multi >= 0) (void) dosearch(); 389 gethungry(); 390 invault(); 391 amulet(); 392 } 393 if(multi < 0) { 394 if(!++multi){ 395 pline("%s", nomovemsg ? nomovemsg : 396 "You can move again."); 397 nomovemsg = 0; 398 if(afternmv) (*afternmv)(); 399 afternmv = 0; 400 } 401 } 402 403 find_ac(); 404 #ifndef QUEST 405 if(!flags.mv || Blind) 406 #endif 407 { 408 seeobjs(); 409 seemons(); 410 nscr(); 411 } 412 if(flags.botl || flags.botlx) bot(); 413 414 flags.move = 1; 415 416 if(multi >= 0 && occupation) { 417 if(monster_nearby()) 418 stop_occupation(); 419 else if ((*occupation)() == 0) 420 occupation = 0; 421 continue; 422 } 423 424 if(multi > 0) { 425 #ifdef QUEST 426 if(flags.run >= 4) finddir(); 427 #endif 428 lookaround(); 429 if(!multi) { /* lookaround may clear multi */ 430 flags.move = 0; 431 continue; 432 } 433 if(flags.mv) { 434 if(multi < COLNO && !--multi) 435 flags.mv = flags.run = 0; 436 domove(); 437 } else { 438 --multi; 439 rhack(save_cm); 440 } 441 } else if(multi == 0) { 442 #ifdef MAIL 443 ckmailstatus(); 444 #endif 445 rhack(NULL); 446 } 447 if(multi && multi%7 == 0) 448 (void) fflush(stdout); 449 } 450 } 451 452 void 453 glo(int foo) 454 { 455 /* construct the string xlock.n */ 456 char *tf; 457 458 tf = lock; 459 while(*tf && *tf != '.') tf++; 460 (void) snprintf(tf, lock + sizeof lock - tf, ".%d", foo); 461 } 462 463 /* 464 * plname is filled either by an option (-u Player or -uPlayer) or 465 * explicitly (-w implies wizard) or by askname. 466 * It may still contain a suffix denoting pl_character. 467 */ 468 void 469 askname(void) 470 { 471 int c,ct; 472 473 printf("\nWho are you? "); 474 (void) fflush(stdout); 475 ct = 0; 476 while((c = getchar()) != '\n'){ 477 if(c == EOF) error("End of input\n"); 478 /* some people get confused when their erase char is not ^H */ 479 if(c == '\010') { 480 if(ct) ct--; 481 continue; 482 } 483 if(c != '-') 484 if(c < 'A' || (c > 'Z' && c < 'a') || c > 'z') c = '_'; 485 if(ct < sizeof(plname)-1) plname[ct++] = c; 486 } 487 plname[ct] = 0; 488 if(ct == 0) askname(); 489 } 490 491 void 492 impossible(const char *s, ...) 493 { 494 va_list ap; 495 496 va_start(ap, s); 497 vpline(s, ap); 498 va_end(ap); 499 pline("Program in disorder - perhaps you'd better Quit."); 500 } 501 502 #ifdef CHDIR 503 static void 504 chdirx(char *dir, boolean wr) 505 { 506 gid_t gid; 507 508 #ifdef SECURE 509 if(dir /* User specified directory? */ 510 #ifdef HACKDIR 511 && strcmp(dir, HACKDIR) /* and not the default? */ 512 #endif 513 ) { 514 /* revoke privs */ 515 gid = getgid(); 516 setresgid(gid, gid, gid); 517 } 518 #endif 519 520 #ifdef HACKDIR 521 if(dir == NULL) 522 dir = HACKDIR; 523 #endif 524 525 if(dir && chdir(dir) == -1) { 526 perror(dir); 527 error("Cannot chdir to %s.", dir); 528 } 529 530 /* warn the player if he cannot write the record file */ 531 /* warn the player if he cannot read the permanent lock file */ 532 /* warn the player if he cannot create the save directory */ 533 /* perhaps we should also test whether . is writable */ 534 /* unfortunately the access systemcall is worthless */ 535 if(wr) { 536 int fd; 537 538 if(dir == NULL) 539 dir = "."; 540 if((fd = open(RECORD, O_RDWR | O_CREAT, FMASK)) == -1) { 541 printf("Warning: cannot write %s/%s", dir, RECORD); 542 getret(); 543 } else 544 (void) close(fd); 545 if((fd = open(HLOCK, O_RDONLY | O_CREAT, FMASK)) == -1) { 546 printf("Warning: cannot read %s/%s", dir, HLOCK); 547 getret(); 548 } else 549 (void) close(fd); 550 if(mkdir("save", 0770) && errno != EEXIST) { 551 printf("Warning: cannot create %s/save", dir); 552 getret(); 553 } 554 } 555 } 556 #endif 557 558 void 559 stop_occupation(void) 560 { 561 if(occupation) { 562 pline("You stop %s.", occtxt); 563 occupation = 0; 564 } 565 } 566