/* NetHack 3.7 unixmain.c $NHDT-Date: 1711213891 2024/03/23 17:11:31 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.127 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ /* main.c - Unix NetHack */ #include "hack.h" #include "dlb.h" #include #include #include #ifndef O_RDONLY #include #endif #if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX) #if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__)) #if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX) extern struct passwd *getpwuid(uid_t); #else extern struct passwd *getpwuid(int); #endif #endif #endif extern struct passwd *getpwnam(const char *); #ifdef CHDIR static void chdirx(const char *, boolean); #endif /* CHDIR */ static boolean whoami(void); static char *lopt(char *, int, const char *, const char *, int *, char ***); static void process_options(int, char **); static void consume_arg(int, int *, char ***); static void consume_two_args(int, int *, char ***); static void early_options(int *, char ***, char **); ATTRNORETURN static void opt_terminate(void) NORETURN; ATTRNORETURN static void opt_usage(const char *) NORETURN; ATTRNORETURN static void scores_only(int, char **, const char *) NORETURN; #ifdef SND_LIB_INTEGRATED uint32_t soundlibchoice = soundlib_nosound; #endif #ifdef _M_UNIX extern void check_sco_console(void); extern void init_sco_cons(void); #endif #ifdef __linux__ extern void check_linux_console(void); extern void init_linux_cons(void); #endif static void wd_message(void); static struct passwd *get_unix_pw(void); int main(int argc, char *argv[]) { char *dir = NULL; NHFILE *nhfp; boolean exact_username; boolean resuming = FALSE; /* assume new game */ boolean plsel_once = FALSE; early_init(argc, argv); #if defined(__APPLE__) { /* special hack to change working directory to a resource fork when running from finder --sam */ #define MAC_PATH_VALUE ".app/Contents/MacOS/" char mac_cwd[1024], *mac_exe = argv[0], *mac_tmp; int arg0_len = strlen(mac_exe), mac_tmp_len, mac_lhs_len = 0; getcwd(mac_cwd, 1024); if (mac_exe[0] == '/' && !strcmp(mac_cwd, "/")) { if ((mac_exe = strrchr(mac_exe, '/'))) mac_exe++; else mac_exe = argv[0]; mac_tmp_len = (strlen(mac_exe) * 2) + strlen(MAC_PATH_VALUE); if (mac_tmp_len <= arg0_len) { mac_tmp = malloc(mac_tmp_len + 1); sprintf(mac_tmp, "%s%s%s", mac_exe, MAC_PATH_VALUE, mac_exe); if (!strcmp(argv[0] + (arg0_len - mac_tmp_len), mac_tmp)) { mac_lhs_len = (arg0_len - mac_tmp_len) + strlen(mac_exe) + 5; if (mac_lhs_len > mac_tmp_len - 1) mac_tmp = realloc(mac_tmp, mac_lhs_len); strncpy(mac_tmp, argv[0], mac_lhs_len); mac_tmp[mac_lhs_len] = '\0'; chdir(mac_tmp); } free(mac_tmp); } } } #endif gh.hname = argv[0]; svh.hackpid = getpid(); (void) umask(0777 & ~FCMASK); choose_windows(DEFAULT_WINDOW_SYS); #ifdef SND_LIB_INTEGRATED /* One of the soundlib interfaces was integrated on build. * We can leave a hint here for activate_chosen_soundlib later. * assign_soundlib() just sets an indicator, it doesn't initialize * any soundlib, and the indicator could be overturned before * activate_chosen_soundlib() gets called. Qt will place its own * hint if qt_init_nhwindow() is invoked. */ #if defined(SND_LIB_MACSOUND) soundlibchoice = soundlib_macsound; assign_soundlib(soundlibchoice); #endif #endif #ifdef CHDIR /* otherwise no chdir() */ /* * See if we must change directory to the playground. * (Perhaps hack runs suid and playground is inaccessible * for the player.) * The environment variable HACKDIR is overridden by a * -d command line option (must be the first option given). */ dir = nh_getenv("NETHACKDIR"); if (!dir) dir = nh_getenv("HACKDIR"); #endif /* CHDIR */ #ifdef ENHANCED_SYMBOLS if (argcheck(argc, argv, ARG_DUMPGLYPHIDS) == 2) exit(EXIT_SUCCESS); #endif /* handle -dalthackdir, -s , --version, --showpaths */ early_options(&argc, &argv, &dir); #ifdef CHDIR /* * Change directories before we initialize the window system so * we can find the tile file. */ chdirx(dir, TRUE); #endif #ifdef _M_UNIX check_sco_console(); #endif #ifdef __linux__ check_linux_console(); #endif initoptions(); #ifdef PANICTRACE ARGV0 = gh.hname; /* save for possible stack trace */ #ifndef NO_SIGNAL panictrace_setsignals(TRUE); #endif #endif exact_username = whoami(); /* * It seems you really want to play. */ u.uhp = 1; /* prevent RIP on early quits */ #if defined(HANGUPHANDLING) program_state.preserve_locks = 1; #ifndef NO_SIGNAL sethanguphandler((SIG_RET_TYPE) hangup); #endif #endif process_options(argc, argv); /* command line options */ #ifdef WINCHAIN commit_windowchain(); #endif init_nhwindows(&argc, argv); /* now we can set up window system */ #ifdef _M_UNIX init_sco_cons(); #endif #ifdef __linux__ init_linux_cons(); #endif #ifdef DEF_PAGER if (!(gc.catmore = nh_getenv("NETHACKPAGER")) && !(gc.catmore = nh_getenv("HACKPAGER")) && !(gc.catmore = nh_getenv("PAGER"))) gc.catmore = DEF_PAGER; #endif #ifdef MAIL getmailstatus(); #endif /* wizard mode access is deferred until here */ set_playmode(); /* sets plname to "wizard" for wizard mode */ /* hide any hyphens from plnamesuffix() */ gp.plnamelen = exact_username ? (int) strlen(svp.plname) : 0; /* strip role,race,&c suffix; calls askname() if plname[] is empty or holds a generic user name like "player" or "games" */ plnamesuffix(); if (wizard) { /* use character name rather than lock letter for file names */ gl.locknum = 0; } else { /* suppress interrupts while processing lock file */ (void) signal(SIGQUIT, SIG_IGN); (void) signal(SIGINT, SIG_IGN); } dlb_init(); /* must be before newgame() */ /* * Initialize the vision system. This must be before mklev() on a * new game or before a level restore on a saved game. */ vision_init(); init_sound_disp_gamewindows(); /* * First, try to find and restore a save file for specified character. * We'll return here if new game player_selection() renames the hero. */ attempt_restore: /* * getlock() complains and quits if there is already a game * in progress for current character name (when gl.locknum == 0) * or if there are too many active games (when gl.locknum > 0). * When proceeding, it creates an empty .0 file to * designate the current game. * getlock() constructs based on the character * name (for !gl.locknum) or on first available of alock, block, * clock, &c not currently in use in the playground directory * (for gl.locknum > 0). */ if (*svp.plname) { getlock(); #if defined(HANGUPHANDLING) program_state.preserve_locks = 0; /* after getlock() */ #endif } if (*svp.plname && (nhfp = restore_saved_game()) != 0) { const char *fq_save = fqname(gs.SAVEF, SAVEPREFIX, 1); (void) chmod(fq_save, 0); /* disallow parallel restores */ #ifndef NO_SIGNAL (void) signal(SIGINT, (SIG_RET_TYPE) done1); #endif #ifdef NEWS if (iflags.news) { display_file(NEWS, FALSE); iflags.news = FALSE; /* in case dorecover() fails */ } #endif /* if there are early trouble-messages issued, let's * not go overtop of them with a pline just yet */ if (ge.early_raw_messages) raw_print("Restoring save file..."); else pline("Restoring save file..."); mark_synch(); /* flush output */ if (dorecover(nhfp)) { resuming = TRUE; /* not starting new game */ wd_message(); if (discover || wizard) { /* this seems like a candidate for paranoid_confirmation... */ if (y_n("Do you want to keep the save file?") == 'n') { (void) delete_savefile(); } else { (void) chmod(fq_save, FCMASK); /* back to readable */ nh_compress(fq_save); } } } if (program_state.in_self_recover) { program_state.in_self_recover = FALSE; } } if (!resuming) { boolean neednewlock = (!*svp.plname); /* new game: start by choosing role, race, etc; player might change the hero's name while doing that, in which case we try to restore under the new name and skip selection this time if that didn't succeed */ if (!iflags.renameinprogress || iflags.defer_plname || neednewlock) { if (!plsel_once) player_selection(); plsel_once = TRUE; if (neednewlock && *svp.plname) goto attempt_restore; if (iflags.renameinprogress) { /* player has renamed the hero while selecting role; if locking alphabetically, the existing lock file can still be used; otherwise, discard current one and create another for the new character name */ if (!gl.locknum) { delete_levelfile(0); /* remove empty lock file */ getlock(); } goto attempt_restore; } } #ifdef CHECK_PANIC_SAVE /* no save file; check for a panic save; if the check finds one, ask the player whether to proceed with a new game; it will quit instead of returning if the answer isn't yes */ if (check_panic_save()) ask_about_panic_save(); #endif /* no save file; start a new game */ newgame(); wd_message(); } /* moveloop() never returns but isn't flagged NORETURN */ moveloop(resuming); exit(EXIT_SUCCESS); /*NOTREACHED*/ return 0; } static char ArgVal_novalue[] = "[nothing]"; /* note: not 'const' */ enum cmdlinearg { ArgValRequired = 0, ArgValOptional = 1, ArgValDisallowed = 2, ArgVal_mask = (1 | 2), ArgNamOneLetter = 4, ArgNam_mask = 4, ArgErrSilent = 0, ArgErrComplain = 8, ArgErr_mask = 8 }; /* approximate 'getopt_long()' for one option; all the comments refer to "-windowtype" but the code isn't specific to that */ static char * lopt( char *arg, /* command line token; beginning matches 'optname' */ int lflags, /* cmdlinearg | errorhandling */ const char *optname, /* option's name; "-windowtype" in examples below */ const char *origarg, /* 'arg' might have had a dash prefix removed */ int *argc_p, /* argc that can have changes passed to caller */ char ***argv_p) /* argv[] ditto */ { int argc = *argc_p; char **argv = *argv_p; char *p, *nextarg = (argc > 1 && argv[1][0] != '-') ? argv[1] : 0; int l, opttype = (lflags & ArgVal_mask); boolean oneletterok = ((lflags & ArgNam_mask) == ArgNamOneLetter), complain = ((lflags & ArgErr_mask) == ArgErrComplain); /* first letter must match */ if (arg[1] != optname[1]) { loptbail: if (complain) config_error_add("Unknown option: %.60s", origarg); return (char *) 0; loptnotallowed: if (complain) config_error_add("Value not allowed: %.60s", origarg); return (char *) 0; loptrequired: if (complain) config_error_add("Missing required value: %.60s", origarg); return (char *) 0; } if ((p = strchr(arg, '=')) == 0) p = strchr(arg, ':'); if (p && opttype == ArgValDisallowed) goto loptnotallowed; l = (int) (p ? (long) (p - arg) : (long) strlen(arg)); if ((l > 2 || oneletterok) && !strncmp(arg, optname, l)) { /* "-windowtype[=foo]" */ if (p) ++p; /* past '=' or ':' */ else if (opttype == ArgValRequired) p = eos(arg); /* we have "-w[indowtype]" w/o "=foo" * so we'll take foo from next element */ else return ArgVal_novalue; } else if (oneletterok) { /* "-w..." but not "-w[indowtype[=foo]]" */ if (!p) { p = &arg[2]; /* past 'w' of "-wfoo" */ #if 0 /* -x:value could work but is not supported (callers don't expect it) */ } else if (p == arg + 2) { ++p; /* past ':' of "-w:foo" */ #endif } else { /* "-w...=foo" but not "-w[indowtype]=foo" */ goto loptbail; } } else { goto loptbail; } if (!p || !*p) { /* "-w[indowtype]" w/o '='/':' if there is a next element, use it for "foo"; if not, supply a non-Null bogus value */ if (nextarg && (opttype == ArgValRequired || opttype == ArgValOptional)) p = nextarg, --(*argc_p), ++(*argv_p); else if (opttype == ArgValRequired) goto loptrequired; else p = ArgVal_novalue; /* there is no next element */ } return p; } /* caveat: argv elements might be arbitrarily long */ static void process_options(int argc, char *argv[]) { char *arg, *origarg; int i, l; config_error_init(FALSE, "command line", FALSE); /* * Process options. * * We don't support "-xyz" as shortcut for "-x -y -z" and we only * simulate longopts by allowing "--foo" for "-foo" when the user * specifies at least 2 characters of leading substring for "foo". * If "foo" takes a value, both "--foo=value" and "--foo value" work. */ while (argc > 1 && argv[1][0] == '-') { argv++; argc--; arg = origarg = argv[0]; /* allow second dash if arg is longer than one character */ if (arg[0] == '-' && arg[1] == '-' && arg[2] != '\0' /* "--a=b" violates the "--" ok when at least 2 chars long rule */ && (arg[3] != '\0' && arg[3] != '=' && arg[3] != ':')) ++arg; l = (int) strlen(arg); if (l < 6 && !strncmp(arg, "-no-", 4)) l = 6; else if (l < 4) l = 4; /* must supply at least 4 chars to match "-XXXgraphics" */ switch (arg[1]) { case 'D': case 'd': if ((arg[1] == 'D' && !arg[2]) || !strcmpi(arg, "-debug")) { wizard = TRUE, discover = FALSE; } else if (!strncmpi(arg, "-DECgraphics", l)) { load_symset("DECGraphics", PRIMARYSET); switch_symbols(TRUE); } else { config_error_add("Unknown option: %.60s", origarg); } break; case 'X': discover = TRUE, wizard = FALSE; break; case 'n': #ifdef NEWS if (!arg[2] || !strcmp(arg, "-no-news")) { iflags.news = FALSE; break; } else if (!strcmp(arg, "-news")) { /* in case RC has !news, allow 'nethack -news' to override */ iflags.news = TRUE; break; } #endif break; case 'u': if (arg[2]) { (void) strncpy(svp.plname, arg + 2, sizeof svp.plname - 1); gp.plnamelen = 0; /* plname[] might have -role-race attached */ } else if (argc > 1) { argc--; argv++; (void) strncpy(svp.plname, argv[0], sizeof svp.plname - 1); gp.plnamelen = 0; } else { config_error_add("Character name expected after -u"); } break; case 'I': case 'i': if (!strncmpi(arg, "-IBMgraphics", l)) { load_symset("IBMGraphics", PRIMARYSET); load_symset("RogueIBM", ROGUESET); switch_symbols(TRUE); } else { config_error_add("Unknown option: %.60s", origarg); } break; case 'l': #ifdef LIVELOG if(!strncmp(arg, "-loglua", 7)){ gl.loglua = 1; } else #endif config_error_add("Unknown option: %.60s", origarg); break; case 'p': /* profession (role) */ if (arg[2]) { if ((i = str2role(&arg[2])) >= 0) flags.initrole = i; } else if (argc > 1) { argc--; argv++; if ((i = str2role(argv[0])) >= 0) flags.initrole = i; } break; case 'r': /* race */ if (arg[2]) { if ((i = str2race(&arg[2])) >= 0) flags.initrace = i; } else if (argc > 1) { argc--; argv++; if ((i = str2race(argv[0])) >= 0) flags.initrace = i; } break; case '@': flags.randomall = 1; break; case '-': /* "--" or "--x" or "--x=y"; need at least 2 chars after the dashes in order to accept "--x" as an alternative to "-x"; don't just silently ignore it */ config_error_add("Unknown option: %.60s", origarg); break; default: /* default for "-x" is to play as the role that starts with "x" */ if ((i = str2role(&argv[0][1])) >= 0) { flags.initrole = i; break; } /* else config_error_add("Unknown option: %.60s", origarg); */ } } if (argc > 1) { int mxplyrs = atoi(argv[1]); boolean mx_ok = (mxplyrs > 0); #ifdef SYSCF config_error_add("%s%s%s", mx_ok ? "MAXPLAYERS are set in sysconf file" : "Expected MAXPLAYERS, found \"", mx_ok ? "" : argv[1], mx_ok ? "" : "\""); #else /* XXX This is deprecated in favor of SYSCF with MAXPLAYERS */ if (mx_ok) gl.locknum = mxplyrs; else config_error_add("Invalid MAXPLAYERS \"%s\"", argv[1]); #endif } #ifdef MAX_NR_OF_PLAYERS /* limit to compile-time limit */ if (!gl.locknum || gl.locknum > MAX_NR_OF_PLAYERS) gl.locknum = MAX_NR_OF_PLAYERS; #endif #ifdef SYSCF /* let syscf override compile-time limit */ if (!gl.locknum || (sysopt.maxplayers && gl.locknum > sysopt.maxplayers)) gl.locknum = sysopt.maxplayers; #endif /* empty or "N errors on command line" */ config_error_done(); return; } /* move argv[ndx] to end of argv[] array, then reduce argc to hide it; prevents process_options() from encountering it after early_options() has processed it; elements get reordered but all remain intact */ static void consume_arg(int ndx, int *ac_p, char ***av_p) { char *gone, **av = *av_p; int i, ac = *ac_p; /* "-one -two -three -four" -> "-two -three -four -one" */ if (ac > 2) { gone = av[ndx]; for (i = ndx + 1; i < ac; ++i) av[i - 1] = av[i]; av[ac - 1] = gone; } --(*ac_p); } /* consume two tokens for '-argname value' w/o '=' or ':' */ static void consume_two_args(int ndx, int *ac_p, char ***av_p) { /* when consuming "-two arg" from "-two arg -three -four", the *ac_p manipulation results in "-three -four -two arg" rather than the "-three -four arg -two" that would happen with just two ordinary consume_arg() calls */ consume_arg(ndx, ac_p, av_p); ++(*ac_p); /* bring the final slot back into view */ consume_arg(ndx, ac_p, av_p); --(*ac_p); /* take away restored slot */ } /* process some command line arguments before loading options */ static void early_options(int *argc_p, char ***argv_p, char **hackdir_p) { char **argv, *arg, *origarg; int argc, oldargc, ndx = 0, consumed = 0; config_error_init(FALSE, "command line", FALSE); /* treat "nethack ?" as a request for usage info; due to shell processing, player likely has to use "nethack \?" or "nethack '?'" [won't work if used as "nethack -dpath ?" or "nethack -d path ?"] */ if (*argc_p > 1 && !strcmp((*argv_p)[1], "?")) opt_usage(*hackdir_p); /* doesn't return */ /* * Both *argc_p and *argv_p account for the program name as (*argv_p)[0]; * local argc and argv impicitly discard that (by starting 'ndx' at 1). * argcheck() doesn't mind, prscore() (via scores_only()) does (for the * number of args it gets passed, not for the value of argv[0]). */ for (ndx = 1; ndx < *argc_p; ndx += (consumed ? 0 : 1)) { consumed = 0; argc = *argc_p - ndx; argv = *argv_p + ndx; arg = origarg = argv[0]; /* skip any args intended for deferred options */ if (*arg != '-') continue; /* allow second dash if arg name is longer than one character */ if (arg[0] == '-' && arg[1] == '-' && arg[2] != '\0' && (arg[3] != '\0' && arg[3] != '=' && arg[3] != ':')) ++arg; switch (arg[1]) { /* char after leading dash */ case 'b': #ifdef CRASHREPORT // --bidshow if (argcheck(argc, argv, ARG_BIDSHOW) == 2){ opt_terminate(); /*NOTREACHED*/ } #endif break; case 'd': if (argcheck(argc, argv, ARG_DEBUG) == 1) { consume_arg(ndx, argc_p, argv_p), consumed = 1; #ifndef NODUMPENUMS } else if (argcheck(argc, argv, ARG_DUMPENUMS) == 2) { opt_terminate(); /*NOTREACHED*/ #endif } else if (argcheck(argc, argv, ARG_DUMPMONGEN) == 2) { opt_terminate(); /*NOTREACHED*/ } else if (argcheck(argc, argv, ARG_DUMPWEIGHTS) == 2) { opt_terminate(); /*NOTREACHED*/ } else { #ifdef CHDIR oldargc = argc; arg = lopt(arg, (ArgValRequired | ArgNamOneLetter | ArgErrSilent), "-directory", origarg, &argc, &argv); if (!arg) error("Flag -d must be followed by a directory name."); if (*arg != 'e') { /* avoid matching -decgraphics or -debug */ *hackdir_p = arg; if (oldargc == argc) consume_arg(ndx, argc_p, argv_p), consumed = 1; else consume_two_args(ndx, argc_p, argv_p), consumed = 2; } #endif /* CHDIR */ } break; case 'h': case '?': if (lopt(arg, ArgValDisallowed, "-help", origarg, &argc, &argv) || lopt(arg, ArgValDisallowed | ArgNamOneLetter, "-?", origarg, &argc, &argv)) opt_usage(*hackdir_p); /* doesn't return */ break; case 'n': oldargc = argc; if (!strcmp(arg, "-no-nethackrc")) /* no abbreviation allowed */ arg = nhStr("/dev/null"); else arg = lopt(arg, (ArgValRequired | ArgErrComplain), "-nethackrc", origarg, &argc, &argv); if (arg) { gc.cmdline_rcfile = dupstr(arg); if (oldargc == argc) consume_arg(ndx, argc_p, argv_p), consumed = 1; else consume_two_args(ndx, argc_p, argv_p), consumed = 2; } break; case 's': if (argcheck(argc, argv, ARG_SHOWPATHS) == 2) { gd.deferred_showpaths = TRUE; gd.deferred_showpaths_dir = *hackdir_p; config_error_done(); return; } /* check for "-s" request to show scores */ if (lopt(arg, ((ArgValDisallowed | ArgErrComplain) /* only accept one-letter if there is just one dash; reject "--s" because prscore() via scores_only() doesn't understand it */ | ((origarg[1] != '-') ? ArgNamOneLetter : 0)), /* [ought to omit val-disallowed and accept --scores=foo since -s foo and -sfoo are allowed, but -s form can take more than one space-separated argument and --scores=foo isn't suited for that] */ "-scores", origarg, &argc, &argv)) { /* at this point, argv[0] contains "-scores" or a leading substring of it; prscore() (via scores_only()) expects that to be in argv[1] so we adjust the pointer to make that be the case; if there are any non-early args waiting to be passed along to process_options(), the resulting argv[0] will be one of those rather than the program name but prscore() doesn't care */ scores_only(argc + 1, argv - 1, *hackdir_p); /*NOTREACHED*/ } break; case 'u': if (lopt(arg, ArgValDisallowed, "-usage", origarg, &argc, &argv)) opt_usage(*hackdir_p); break; case 'v': if (argcheck(argc, argv, ARG_VERSION) == 2) { opt_terminate(); /*NOTREACHED*/ } break; case 'w': /* windowtype: "-wfoo" or "-w[indowtype]=foo" * or "-w[indowtype]:foo" or "-w[indowtype] foo" */ arg = lopt(arg, (ArgValRequired | ArgNamOneLetter | ArgErrComplain), "-windowtype", origarg, &argc, &argv); if (gc.cmdline_windowsys) free((genericptr_t) gc.cmdline_windowsys); gc.cmdline_windowsys = arg ? dupstr(arg) : NULL; break; default: break; } } /* empty or "N errors on command line" */ config_error_done(); return; } /* for command-line options that perform some immediate action and then terminate the program without starting play, like 'nethack --version' or 'nethack -s Zelda'; do some cleanup before that termination */ ATTRNORETURN static void opt_terminate(void) { config_error_done(); /* free memory allocated by config_error_init() */ nh_terminate(EXIT_SUCCESS); /*NOTREACHED*/ } ATTRNORETURN static void opt_usage(const char *hackdir) { #ifdef CHDIR chdirx(hackdir, TRUE); #else nhUse(hackdir); #endif dlb_init(); genl_display_file(USAGEHELP, TRUE); opt_terminate(); } /* show the sysconf file name, playground directory, run-time configuration file name, dumplog file name if applicable, and some other things */ ATTRNORETURN void after_opt_showpaths(const char *dir) { #ifdef CHDIR chdirx(dir, FALSE); #else nhUse(dir); #endif opt_terminate(); /*NOTREACHED*/ } /* handle "-s [character-names]" to show all the entries in the high scores file ('record') belonging to particular characters; nethack will end after doing so without starting play */ ATTRNORETURN static void scores_only(int argc, char **argv, const char *dir) { /* do this now rather than waiting for final termination, in case there is an error summary coming */ config_error_done(); #ifdef CHDIR chdirx(dir, FALSE); #else nhUse(dir); #endif #ifdef SYSCF iflags.initoptions_noterminate = TRUE; initoptions(); /* sysconf options affect whether panictrace is enabled */ iflags.initoptions_noterminate = FALSE; #endif #ifdef PANICTRACE ARGV0 = gh.hname; /* save for possible stack trace */ #ifndef NO_SIGNAL panictrace_setsignals(TRUE); #endif #endif (void) whoami(); /* set up default plname[] */ prscore(argc, argv); nh_terminate(EXIT_SUCCESS); /* bypass opt_terminate() */ /*NOTREACHED*/ } #ifdef CHDIR static void chdirx(const char *dir, boolean wr) { if (dir /* User specified directory? */ #ifdef HACKDIR && strcmp(dir, HACKDIR) /* and not the default? */ #endif ) { #ifdef SECURE (void) setgid(getgid()); (void) setuid(getuid()); /* Ron Wessels */ #endif } else { /* non-default data files is a sign that scores may not be * compatible, or perhaps that a binary not fitting this * system's layout is being used. */ #ifdef VAR_PLAYGROUND int len = strlen(VAR_PLAYGROUND); /* FIXME: this allocation never gets freed. */ gf.fqn_prefix[SCOREPREFIX] = (char *) alloc(len + 2); Strcpy(gf.fqn_prefix[SCOREPREFIX], VAR_PLAYGROUND); if (gf.fqn_prefix[SCOREPREFIX][len - 1] != '/') { gf.fqn_prefix[SCOREPREFIX][len] = '/'; gf.fqn_prefix[SCOREPREFIX][len + 1] = '\0'; } #endif } #ifdef HACKDIR if (!dir) dir = HACKDIR; #endif if (dir && chdir(dir) < 0) { perror(dir); error("Cannot chdir to %s.", dir); /*NOTREACHED*/ } /* warn the player if we can't write the record file * perhaps we should also test whether . is writable * unfortunately the access system-call is worthless. */ if (wr) { #ifdef VAR_PLAYGROUND /* FIXME: if termination cleanup ever frees fqn_prefix[0..N-1], * these will need to use dupstr() so that they have distinct * values that can be freed separately. Or perhaps freeing * fqn_prefix[j] can check [j+1] through [N-1] for duplicated * pointer and just set the value to Null. */ gf.fqn_prefix[LEVELPREFIX] = gf.fqn_prefix[SCOREPREFIX]; gf.fqn_prefix[SAVEPREFIX] = gf.fqn_prefix[SCOREPREFIX]; gf.fqn_prefix[BONESPREFIX] = gf.fqn_prefix[SCOREPREFIX]; gf.fqn_prefix[LOCKPREFIX] = gf.fqn_prefix[SCOREPREFIX]; gf.fqn_prefix[TROUBLEPREFIX] = gf.fqn_prefix[SCOREPREFIX]; #endif check_recordfile(dir); } return; } #endif /* CHDIR */ /* returns True iff we set plname[] to username which contains a hyphen */ static boolean whoami(void) { /* * Who am i? Algorithm: 1. Use name as specified in NETHACKOPTIONS * 2. Use $USER or $LOGNAME (if 1. fails) * 3. Use getlogin() (if 2. fails) * The resulting name is overridden by command line options. * If everything fails, or if the resulting name is some generic * account like "games", "play", "player", "hack" then eventually * we'll ask him. * Note that we trust the user here; it is possible to play under * somebody else's name. */ if (!*svp.plname) { register const char *s; s = nh_getenv("USER"); if (!s || !*s) s = nh_getenv("LOGNAME"); if (!s || !*s) s = getlogin(); if (s && *s) { (void) strncpy(svp.plname, s, sizeof svp.plname - 1); if (strchr(svp.plname, '-')) return TRUE; } } return FALSE; } void sethanguphandler(void (*handler)(int)) { #ifdef SA_RESTART /* don't want reads to restart. If SA_RESTART is defined, we know * sigaction exists and can be used to ensure reads won't restart. * If it's not defined, assume reads do not restart. If reads restart * and a signal occurs, the game won't do anything until the read * succeeds (or the stream returns EOF, which might not happen if * reading from, say, a window manager). */ struct sigaction sact; (void) memset((genericptr_t) &sact, 0, sizeof sact); sact.sa_handler = (SIG_RET_TYPE) handler; (void) sigaction(SIGHUP, &sact, (struct sigaction *) 0); #ifdef SIGXCPU (void) sigaction(SIGXCPU, &sact, (struct sigaction *) 0); #endif #else /* !SA_RESTART */ (void) signal(SIGHUP, (SIG_RET_TYPE) handler); #ifdef SIGXCPU (void) signal(SIGXCPU, (SIG_RET_TYPE) handler); #endif #endif /* ?SA_RESTART */ } #ifdef PORT_HELP void port_help(void) { /* * Display unix-specific help. Just show contents of the helpfile * named by PORT_HELP. */ display_file(PORT_HELP, TRUE); } #endif /* validate wizard mode if player has requested access to it */ boolean authorize_wizard_mode(void) { if (sysopt.wizards && sysopt.wizards[0]) { if (check_user_string(sysopt.wizards)) return TRUE; } iflags.wiz_error_flag = TRUE; /* not being allowed into wizard mode */ return FALSE; } /* similar to above, validate explore mode access */ boolean authorize_explore_mode(void) { #ifdef SYSCF if (sysopt.explorers && sysopt.explorers[0]) { if (check_user_string(sysopt.explorers)) return TRUE; } iflags.explore_error_flag = TRUE; /* not allowed into explore mode */ return FALSE; #else return TRUE; /* if sysconf disabled, no restrictions on explore mode */ #endif } static void wd_message(void) { if (iflags.wiz_error_flag) { if (sysopt.wizards && sysopt.wizards[0]) { char *tmp = build_english_list(sysopt.wizards); pline("Only user%s %s may access debug (wizard) mode.", strchr(sysopt.wizards, ' ') ? "s" : "", tmp); free(tmp); } else { You("cannot access debug (wizard) mode."); } wizard = FALSE; /* (paranoia) */ if (!iflags.explore_error_flag) pline("Entering explore/discovery mode instead."); } else if (iflags.explore_error_flag) { You("cannot access explore mode."); /* same as enter_explore_mode */ discover = iflags.deferred_X = FALSE; /* (more paranoia) */ } else if (discover) You("are in non-scoring explore/discovery mode."); } /* * Add a slash to any name not ending in /. There must * be room for the / */ void append_slash(char *name) { char *ptr; if (!*name) return; ptr = name + (strlen(name) - 1); if (*ptr != '/') { *++ptr = '/'; *++ptr = '\0'; } return; } boolean check_user_string(const char *optstr) { struct passwd *pw; int pwlen; const char *eop, *w; char *pwname = 0; if (optstr[0] == '*') return TRUE; /* allow any user */ if (sysopt.check_plname) pwname = svp.plname; else if ((pw = get_unix_pw()) != 0) pwname = pw->pw_name; if (!pwname || !*pwname) return FALSE; pwlen = (int) strlen(pwname); eop = eos((char *) optstr); /* temporarily cast away 'const' */ w = optstr; while (w + pwlen <= eop) { if (!*w) break; if (isspace(*w)) { w++; continue; } if (!strncmp(w, pwname, pwlen)) { if (!w[pwlen] || isspace(w[pwlen])) return TRUE; } while (*w && !isspace(*w)) w++; } return FALSE; } static struct passwd * get_unix_pw(void) { char *user; unsigned uid; static struct passwd *pw = (struct passwd *) 0; if (pw) return pw; /* cache answer */ uid = (unsigned) getuid(); user = getlogin(); if (user) { pw = getpwnam(user); if (pw && ((unsigned) pw->pw_uid != uid)) pw = 0; } if (pw == 0) { user = nh_getenv("USER"); if (user) { pw = getpwnam(user); if (pw && ((unsigned) pw->pw_uid != uid)) pw = 0; } if (pw == 0) { pw = getpwuid(uid); } } return pw; } char * get_login_name(void) { static char buf[BUFSZ]; struct passwd *pw = get_unix_pw(); buf[0] = '\0'; if (pw) (void)strcpy(buf, pw->pw_name); return buf; } #ifdef __APPLE__ extern int errno; void port_insert_pastebuf(char *buf) { /* This should be replaced when there is a Cocoa port. */ const char *errarg; size_t len; FILE *PB = popen("/usr/bin/pbcopy", "w"); if (!PB) { errarg = "Unable to start pbcopy"; goto error; } len = strlen(buf); /* Remove the trailing \n, carefully. */ if (len > 0 && buf[len - 1] == '\n') len--; /* XXX Sorry, I'm too lazy to write a loop for output this short. */ if (len != fwrite(buf, 1, len, PB)) { errarg = "Error sending data to pbcopy"; goto error; } if (pclose(PB) != -1) { return; } errarg = "Error finishing pbcopy"; error: raw_printf("%s: %s (%d)\n", errarg, strerror(errno), errno); } #endif /* __APPLE__ */ unsigned long sys_random_seed(void) { unsigned long seed = 0L; unsigned long pid = (unsigned long) getpid(); boolean no_seed = TRUE; #ifdef DEV_RANDOM FILE *fptr; fptr = fopen(DEV_RANDOM, "r"); if (fptr) { fread(&seed, sizeof (long), 1, fptr); has_strong_rngseed = TRUE; /* decl.c */ no_seed = FALSE; (void) fclose(fptr); } else { /* leaves clue, doesn't exit */ paniclog("sys_random_seed", "falling back to weak seed"); } #endif if (no_seed) { seed = (unsigned long) getnow(); /* time((TIME_type) 0) */ /* Quick dirty band-aid to prevent PRNG prediction */ if (pid) { if (!(pid & 3L)) pid -= 1L; seed *= pid; } } return seed; } /*unixmain.c*/