-/* NetHack 3.7 unixmain.c $NHDT-Date: 1644866265 2022/02/14 19:17:45 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.96 $ */
+/* NetHack 3.7 unixmain.c $NHDT-Date: 1645223897 2022/02/18 22:38:17 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.97 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2011. */
/* NetHack may be freely redistributed. See license for details. */
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 **);
+static void opt_terminate(void) NORETURN;
+static void opt_showpaths(const char *);
+static void scores_only(int, char **, const char *) NORETURN;
#ifdef _M_UNIX
extern void check_sco_console(void);
int
main(int argc, char *argv[])
{
-#ifdef CHDIR
- char *dir;
-#endif
+ char *dir = NULL;
NHFILE *nhfp;
boolean exact_username;
boolean resuming = FALSE; /* assume new game */
if (!dir)
dir = nh_getenv("HACKDIR");
#endif /* CHDIR */
+ /* handle -dalthackdir, -s <score stuff>, --version, --showpaths */
+ early_options(&argc, &argv, &dir);
- if (argc > 1) {
- if (argcheck(argc, argv, ARG_VERSION) == 2)
- exit(EXIT_SUCCESS);
-
-#ifndef NODUMPENUMS
- if (argcheck(argc, argv, ARG_DUMPENUMS) == 2)
- exit(EXIT_SUCCESS);
-#endif
- if (argcheck(argc, argv, ARG_SHOWPATHS) == 2) {
-#ifdef CHDIR
- chdirx((char *) 0, 0);
-#endif
- iflags.initoptions_noterminate = TRUE;
- initoptions();
- iflags.initoptions_noterminate = FALSE;
- reveal_paths();
- exit(EXIT_SUCCESS);
- }
- if (argcheck(argc, argv, ARG_DEBUG) == 1) {
- argc--;
- argv++;
- }
-#ifdef CHDIR
- if (argc > 1 && !strncmp(argv[1], "-d", 2) && argv[1][2] != 'e') {
- /* avoid matching "-dec" for DECgraphics; since the man page
- * says -d directory, hope nobody's using -desomething_else
- */
- argc--;
- argv++;
- dir = argv[0] + 2;
- if (*dir == '=' || *dir == ':')
- dir++;
- if (!*dir && argc > 1) {
- argc--;
- argv++;
- dir = argv[0];
- }
- if (!*dir)
- error("Flag -d must be followed by a directory name.");
- }
- }
-#endif /* CHDIR */
-
- if (argc > 1) {
- /*
- * Now we know the directory containing 'record' and
- * may do a prscore(). Exclude `-style' - it's a Qt option.
- */
- if (!strncmp(argv[1], "-s", 2) && strncmp(argv[1], "-style", 6)) {
#ifdef CHDIR
- chdirx(dir, 0);
-#endif
-#ifdef SYSCF
- initoptions();
-#endif
-#ifdef PANICTRACE
- ARGV0 = g.hname; /* save for possible stack trace */
-#ifndef NO_SIGNAL
- panictrace_setsignals(TRUE);
-#endif
-#endif
- prscore(argc, argv);
- /* FIXME: shouldn't this be using nh_terminate() to free
- up any memory allocated by initoptions() */
- exit(EXIT_SUCCESS);
- }
- } /* argc > 1 */
-
-/*
- * Change directories before we initialize the window system so
- * we can find the tile file.
- */
-#ifdef CHDIR
- chdirx(dir, 1);
+ /*
+ * 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 = g.hname; /* save for possible stack trace */
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 = index(arg, '=')) == 0)
+ p = index(arg, ':');
+ if (p && opttype == ArgValDisallowed)
+ goto loptnotallowed;
+
+ l = (int) (p ? (p - arg) : strlen(arg));
+ if (!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" */
+ } 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 arbitrary long */
static void
process_options(int argc, char *argv[])
{
- static char novalue[] = "[nothing]"; /* note: not 'const' */
- char *p, *arg, *origarg;
+ char *arg, *origarg;
int i, l;
config_error_init(FALSE, "command line", FALSE);
&& (arg[3] != '\0' && arg[3] != '=' && arg[3] != ':'))
++arg;
l = (int) strlen(arg);
- /* must supply at least 4 chars to match "-XXXgraphics" */
- if (l < 4)
- l = 4;
+ 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 'X':
discover = TRUE, wizard = FALSE;
break;
-#ifdef NEWS
case 'n':
- iflags.news = FALSE;
- break;
+#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(g.plname, arg + 2, sizeof g.plname - 1);
(void) strncpy(g.plname, argv[0], sizeof g.plname - 1);
g.plnamelen = 0;
} else {
- config_error_add("Player name expected after -u");
+ config_error_add("Character name expected after -u");
}
break;
case 'I':
break;
case 'w': /* windowtype: "-wfoo" or "-w[indowtype]=foo"
* or "-w[indowtype]:foo" or "-w[indowtype] foo" */
- if ((p = index(arg, '=')) == 0)
- p = index(arg, ':');
- l = (int) (p ? (p - arg) : strlen(arg));
- if (!strncmp(arg, "-windowtype", l)) {
- /* "-windowtype[=foo]" */
- if (p)
- ++p; /* past '=' or ':' */
- else
- p = eos(arg); /* we have "-w[indowtype]" w/o "=foo"
- * so we'll take foo from next element */
- } else {
- /* "-w..." but not "-w[indowtype[=foo]]" */
- if (!p) {
- p = &arg[2]; /* past 'w' of "-wfoo" */
- } else {
- /* "-w...=foo" but not "-w[indowtype]=foo" */
- config_error_add("Unknown option: %.60s", origarg);
- continue;
- }
- }
- if (!*p) {
- /* "-w[indowtype]" w/o '='/':' use next element for "foo" */
- if (argc > 1)
- --argc, ++argv, p = argv[0];
- else
- p = novalue; /* there is no next element */
- }
- choose_windows(p);
+ arg = lopt(arg,
+ (ArgValRequired | ArgNamOneLetter | ArgErrComplain),
+ "-windowtype", origarg, &argc, &argv);
+ if (arg)
+ choose_windows(arg);
break;
case '@':
flags.randomall = 1;
}
}
+ if (argc > 1) {
+ int mxplyrs = atoi(argv[1]);
+ boolean mx_ok = mxplyrs != 0;
#ifdef SYSCF
- if (argc > 1)
- config_error_add("MAXPLAYERS are set in sysconf file.\n");
+ 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 (argc > 1)
- g.locknum = atoi(argv[1]);
+ /* XXX This is deprecated in favor of SYSCF with MAXPLAYERS */
+ if (mx_ok)
+ g.locknum = mxplyrs;
+ else
+ config_error_add("Invalid MAXPLATERS \"%s\"", argv[1]);
#endif
+ }
#ifdef MAX_NR_OF_PLAYERS
/* limit to compile-time limit */
if (!g.locknum || g.locknum > MAX_NR_OF_PLAYERS)
#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);
+
+ /*
+ * 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 (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 '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 {
+#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 '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) {
+ g.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) {
+ opt_showpaths(*hackdir_p);
+ opt_terminate();
+ /*NOTREACHED*/
+ }
+ /* check for "-s" request to show scores */
+ if (lopt(arg,
+ (ArgValDisallowed | ArgNamOneLetter | ArgErrComplain),
+ "-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 'v':
+ if (argcheck(argc, argv, ARG_VERSION) == 2) {
+ opt_terminate();
+ /*NOTREACHED*/
+ }
+ 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 */
+static void
+opt_terminate(void)
+{
+ config_error_done(); /* free memory allocated by config_error_init() */
+
+ nh_terminate(EXIT_SUCCESS);
+ /*NOTREACHED*/
+}
+
+/* show the sysconf file name, playground directory, run-time configuration
+ file name, dumplog file name if applicable, and some other things */
+static void
+opt_showpaths(const char *dir)
+{
+#ifdef CHDIR
+ chdirx(dir, FALSE);
+#else
+ nhUse(dir);
+#endif
+ iflags.initoptions_noterminate = TRUE;
+ initoptions();
+ iflags.initoptions_noterminate = FALSE;
+ reveal_paths();
+}
+
+/* handle "-s <score options> [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 */
+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 = g.hname; /* save for possible stack trace */
+#ifndef NO_SIGNAL
+ panictrace_setsignals(TRUE);
+#endif
+#endif
+
+ prscore(argc, argv);
+
+ nh_terminate(EXIT_SUCCESS); /* bypass opt_terminate() */
+ /*NOTREACHED*/
}
#ifdef CHDIR
#ifdef VAR_PLAYGROUND
int len = strlen(VAR_PLAYGROUND);
+ /* FIXME: this allocation never gets freed.
+ */
g.fqn_prefix[SCOREPREFIX] = (char *) alloc(len + 2);
Strcpy(g.fqn_prefix[SCOREPREFIX], VAR_PLAYGROUND);
if (g.fqn_prefix[SCOREPREFIX][len - 1] != '/') {
g.fqn_prefix[SCOREPREFIX][len] = '/';
g.fqn_prefix[SCOREPREFIX][len + 1] = '\0';
}
-
#endif
}
#ifdef HACKDIR
- if (dir == (const char *) 0)
+ 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
*/
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.
+ */
g.fqn_prefix[LEVELPREFIX] = g.fqn_prefix[SCOREPREFIX];
g.fqn_prefix[SAVEPREFIX] = g.fqn_prefix[SCOREPREFIX];
g.fqn_prefix[BONESPREFIX] = g.fqn_prefix[SCOREPREFIX];
#endif
check_recordfile(dir);
}
+ return;
}
#endif /* CHDIR */