Files
nethack/sys/libnh/libnhmain.c
2025-11-08 14:53:50 -05:00

1282 lines
39 KiB
C

/* NetHack 3.7 libnhmain.c $NHDT-Date: 1693359589 2023/08/30 01:39:49 $ $NHDT-Branch: keni-crashweb2 $:$NHDT-Revision: 1.106 $ */
/* 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 <sys/stat.h>
#include <signal.h>
#include <pwd.h>
#include <func_tab.h>
#ifndef O_RDONLY
#include <fcntl.h>
#endif
/* for cross-compiling to WebAssembly (WASM) */
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
void js_helpers_init();
void js_constants_init();
void js_globals_init();
#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 void process_options(int, char **);
#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);
ATTRNORETURN static void opt_terminate(void) NORETURN;
#ifdef __EMSCRIPTEN__
/* if WebAssembly, export this API and don't optimize it out */
EMSCRIPTEN_KEEPALIVE
int
main(int argc, char *argv[])
#else /* !__EMSCRIPTEN__ */
int nhmain(int argc, char *argv[]);
int
nhmain(int argc, char *argv[])
#endif /* __EMSCRIPTEN__ */
{
#ifdef CHDIR
char *dir;
#endif
NHFILE *nhfp;
boolean exact_username;
boolean resuming = FALSE; /* assume new game */
boolean plsel_once = FALSE;
// int i;
// for (i = 0; i < argc; i++) {
// printf ("argv[%d]: %s\n", i, argv[i]);
// }
early_init(argc, argv);
gh.hname = argv[0];
svh.hackpid = getpid();
(void) umask(0777 & ~FCMASK);
choose_windows(DEFAULT_WINDOW_SYS);
#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");
if (argc > 1) {
if (argcheck(argc, argv, ARG_VERSION) == 2)
exit(EXIT_SUCCESS);
#ifdef CRASHREPORT
if (argcheck(argc, argv, ARG_BIDSHOW))
exit(EXIT_SUCCESS);
#endif
if (argcheck(argc, argv, ARG_SHOWPATHS) == 2) {
gd.deferred_showpaths = TRUE;
return;
}
if (argcheck(argc, argv, ARG_DEBUG) == 1) {
argc--;
argv++;
}
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 = gh.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);
#endif
#ifdef __EMSCRIPTEN__
js_helpers_init();
js_constants_init();
js_globals_init();
#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 */
program_state.preserve_locks = 1;
#ifndef NO_SIGNAL
sethanguphandler((SIG_RET_TYPE) hangup);
#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("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 {
#ifndef NO_SIGNAL
/* suppress interrupts while processing lock file */
(void) signal(SIGQUIT, SIG_IGN);
(void) signal(SIGINT, SIG_IGN);
#endif
}
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 <lockname>.0 file to
* designate the current game.
* getlock() constructs <lockname> 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();
program_state.preserve_locks = 0; /* after getlock() */
}
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
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 (!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;
}
}
newgame();
wd_message();
}
/* moveloop() never returns but isn't flagged NORETURN */
moveloop(resuming);
exit(EXIT_SUCCESS);
/*NOTREACHED*/
return 0;
}
/* caveat: argv elements might be arbitrary long */
static void
process_options(int argc, char *argv[])
{
int i, l;
/*
* Process options.
*/
while (argc > 1 && argv[1][0] == '-') {
argv++;
argc--;
l = (int) strlen(*argv);
/* must supply at least 4 chars to match "-XXXgraphics" */
if (l < 4)
l = 4;
switch (argv[0][1]) {
case 'D':
case 'd':
if ((argv[0][1] == 'D' && !argv[0][2])
|| !strcmpi(*argv, "-debug")) {
wizard = TRUE, discover = FALSE;
} else if (!strncmpi(*argv, "-DECgraphics", l)) {
load_symset("DECGraphics", PRIMARYSET);
switch_symbols(TRUE);
} else {
raw_printf("Unknown option: %.60s", *argv);
}
break;
case 'X':
discover = TRUE, wizard = FALSE;
break;
#ifdef NEWS
case 'n':
iflags.news = FALSE;
break;
#endif
case 'u':
if (argv[0][2]) {
(void) strncpy(svp.plname, argv[0] + 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 {
raw_print("Player name expected after -u");
}
break;
case 'I':
case 'i':
if (!strncmpi(*argv, "-IBMgraphics", l)) {
load_symset("IBMGraphics", PRIMARYSET);
load_symset("RogueIBM", ROGUESET);
switch_symbols(TRUE);
} else {
raw_printf("Unknown option: %.60s", *argv);
}
break;
case 'p': /* profession (role) */
if (argv[0][2]) {
if ((i = str2role(&argv[0][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 (argv[0][2]) {
if ((i = str2race(&argv[0][2])) >= 0)
flags.initrace = i;
} else if (argc > 1) {
argc--;
argv++;
if ((i = str2race(argv[0])) >= 0)
flags.initrace = i;
}
break;
case 'w': /* windowtype */
config_error_init(FALSE, "command line", FALSE);
choose_windows(&argv[0][2]);
config_error_done();
break;
case '@':
flags.randomall = 1;
break;
default:
if ((i = str2role(&argv[0][1])) >= 0) {
flags.initrole = i;
break;
}
/* else raw_printf("Unknown option: %.60s", *argv); */
}
}
#ifdef SYSCF
if (argc > 1)
raw_printf("MAXPLAYERS are set in sysconf file.\n");
#else
/* XXX This is deprecated in favor of SYSCF with MAXPLAYERS */
if (argc > 1)
gl.locknum = atoi(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
}
#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);
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 == (const char *) 0)
dir = HACKDIR;
#endif
if (dir && chdir(dir) < 0) {
perror(dir);
error("Cannot chdir to %s.", dir);
}
/* 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
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);
}
}
#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) {
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;
}
#ifndef NO_SIGNAL
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 */
}
#endif /* !NO_SIGNAL */
#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);
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;
}
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;
}
/* 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*/
}
/* 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*/
}
#ifdef __EMSCRIPTEN__
/***
* Helpers
***/
EM_JS(void, js_helpers_init, (), {
globalThis.nethackGlobal = globalThis.nethackGlobal || {};
globalThis.nethackGlobal.helpers = globalThis.nethackGlobal.helpers || {};
installHelper(displayInventory, "displayInventory");
installHelper(getPointerValue, "getPointerValue");
installHelper(setPointerValue, "setPointerValue");
// used by update_inventory
function displayInventory() {
// Asyncify.handleAsync(async () => {
return _repopulate_perminvent();
// });
}
// convert 'ptr' to the type indicated by 'type'
function getPointerValue(name, ptr, type) {
// console.log("getPointerValue", name, "0x" + ptr.toString(16), type);
switch(type) {
case "s": // string
// var value = UTF8ToString(getValue(ptr, "*"));
return UTF8ToString(ptr);
case "p": // pointer
if(!ptr) return 0; // null pointer
return getValue(ptr, "*");
case "c": // char
return String.fromCharCode(getValue(ptr, "i8"));
case "b":
return getValue(ptr, "i8") == 1;
case "0": /* 2^0 = 1 byte */
return getValue(ptr, "i8");
case "1": /* 2^1 = 2 bytes */
return getValue(ptr, "i16");
case "2": /* 2^2 = 4 bytes */
case "i": // integer
case "n": // number
return getValue(ptr, "i32");
case "f": // float
return getValue(ptr, "float");
case "d": // double
return getValue(ptr, "double");
case "v": // void
return undefined;
default:
throw new TypeError ("unknown type:" + type);
}
}
// sets the return value of the function to the type expected
function setPointerValue(name, ptr, type, value = 0) {
// console.log("setPointerValue", name, "0x" + ptr.toString(16), type, value);
switch (type) {
case "p":
setValue(ptr, value, "*");
break;
case "s":
if(typeof value !== "string")
throw new TypeError(`expected ${name} return type to be string`);
// value=value?value:"(no value)";
// var strPtr = getValue(ptr, "i32");
stringToUTF8(value, ptr, 1024); // TODO: uhh... danger will robinson
break;
case "i":
if(typeof value !== "number" || !Number.isInteger(value))
throw new TypeError(`expected ${name} return type to be integer`);
setValue(ptr, value, "i32");
break;
case "1":
if(typeof value !== "number" || !Number.isInteger(value))
throw new TypeError(`expected ${name} return type to be integer`);
setValue(ptr, value, "i16");
break;
case "c":
if(typeof value !== "number" || value < 0 || value > 128)
throw new TypeError(`expected ${name} return type to be integer representing an ASCII character`);
setValue(ptr, value, "i8");
break;
case "f":
if(typeof value !== "number" || isFloat(value))
throw new TypeError(`expected ${name} return type to be float`);
// XXX: I'm not sure why 'double' works and 'float' doesn't
setValue(ptr, value, "double");
break;
case "d":
if(typeof value !== "number" || isFloat(value))
throw new TypeError(`expected ${name} return type to be double`);
setValue(ptr, value, "double");
break;
case "b":
if (typeof value !== "boolean")
throw new TypeError(`expected ${name} return type to be boolean`);
setValue(ptr, value ? 1 : 0, "i8");
case "v":
break;
default:
throw new Error("unknown type");
}
function isFloat(n){
return n === +n && n !== (n|0) && !Number.isInteger(n);
}
}
function installHelper(fn, name) {
name = name || fn.name;
globalThis.nethackGlobal.helpers[name] = fn;
}
})
/***
* Constants
***/
#define SET_CONSTANT(scope, name) set_const(scope, #name, name);
EM_JS(void, set_const, (char *scope_str, char *name_str, int num), {
let scope = UTF8ToString(scope_str);
let name = UTF8ToString(name_str);
globalThis.nethackGlobal.constants[scope] = globalThis.nethackGlobal.constants[scope] || {};
globalThis.nethackGlobal.constants[scope][name] = num;
globalThis.nethackGlobal.constants[scope][num] = name;
});
#define SET_CONSTANT_STRING(scope, name) set_const_str(scope, #name, name);
EM_JS(void, set_const_str, (char *scope_str, char *name_str, char *input_str), {
let scope = UTF8ToString(scope_str);
let name = UTF8ToString(name_str);
let str = UTF8ToString(input_str);
globalThis.nethackGlobal.constants[scope] = globalThis.nethackGlobal.constants[scope] || {};
globalThis.nethackGlobal.constants[scope][name] = str;
});
#define SET_POINTER(name) set_const_ptr(#name, (void *)&name);
EM_JS(void, set_const_ptr, (char *name_str, void* ptr), {
let name = UTF8ToString(name_str);
globalThis.nethackGlobal.pointers = globalThis.nethackGlobal.pointers || {};
globalThis.nethackGlobal.pointers[name] = ptr;
});
void js_constants_init() {
EM_ASM({
globalThis.nethackGlobal = globalThis.nethackGlobal || {};
globalThis.nethackGlobal.constants = globalThis.nethackGlobal.constants || {};
globalThis.nethackGlobal.pointers = globalThis.nethackGlobal.pointers || {};
});
// create_nhwindow
SET_CONSTANT("WIN_TYPE", NHW_MESSAGE)
SET_CONSTANT("WIN_TYPE", NHW_STATUS)
SET_CONSTANT("WIN_TYPE", NHW_MAP)
SET_CONSTANT("WIN_TYPE", NHW_MENU)
SET_CONSTANT("WIN_TYPE", NHW_TEXT)
// status_update
SET_CONSTANT("STATUS_FIELD", BL_CHARACTERISTICS)
SET_CONSTANT("STATUS_FIELD", BL_RESET)
SET_CONSTANT("STATUS_FIELD", BL_FLUSH)
SET_CONSTANT("STATUS_FIELD", BL_TITLE)
SET_CONSTANT("STATUS_FIELD", BL_STR)
SET_CONSTANT("STATUS_FIELD", BL_DX)
SET_CONSTANT("STATUS_FIELD", BL_CO)
SET_CONSTANT("STATUS_FIELD", BL_IN)
SET_CONSTANT("STATUS_FIELD", BL_WI)
SET_CONSTANT("STATUS_FIELD", BL_CH)
SET_CONSTANT("STATUS_FIELD", BL_ALIGN)
SET_CONSTANT("STATUS_FIELD", BL_SCORE)
SET_CONSTANT("STATUS_FIELD", BL_CAP)
SET_CONSTANT("STATUS_FIELD", BL_GOLD)
SET_CONSTANT("STATUS_FIELD", BL_ENE)
SET_CONSTANT("STATUS_FIELD", BL_ENEMAX)
SET_CONSTANT("STATUS_FIELD", BL_XP)
SET_CONSTANT("STATUS_FIELD", BL_AC)
SET_CONSTANT("STATUS_FIELD", BL_HD)
SET_CONSTANT("STATUS_FIELD", BL_TIME)
SET_CONSTANT("STATUS_FIELD", BL_HUNGER)
SET_CONSTANT("STATUS_FIELD", BL_HP)
SET_CONSTANT("STATUS_FIELD", BL_HPMAX)
SET_CONSTANT("STATUS_FIELD", BL_LEVELDESC)
SET_CONSTANT("STATUS_FIELD", BL_EXP)
SET_CONSTANT("STATUS_FIELD", BL_CONDITION)
SET_CONSTANT("STATUS_FIELD", MAXBLSTATS)
// text attributes
SET_CONSTANT("ATTR", ATR_NONE);
SET_CONSTANT("ATTR", ATR_BOLD);
SET_CONSTANT("ATTR", ATR_DIM);
SET_CONSTANT("ATTR", ATR_ULINE);
SET_CONSTANT("ATTR", ATR_BLINK);
SET_CONSTANT("ATTR", ATR_INVERSE);
SET_CONSTANT("ATTR", ATR_URGENT);
SET_CONSTANT("ATTR", ATR_NOHISTORY);
// conditions
SET_CONSTANT("CONDITION", BL_MASK_BAREH);
SET_CONSTANT("CONDITION", BL_MASK_BLIND);
SET_CONSTANT("CONDITION", BL_MASK_BUSY);
SET_CONSTANT("CONDITION", BL_MASK_CONF);
SET_CONSTANT("CONDITION", BL_MASK_DEAF);
SET_CONSTANT("CONDITION", BL_MASK_ELF_IRON);
SET_CONSTANT("CONDITION", BL_MASK_FLY);
SET_CONSTANT("CONDITION", BL_MASK_FOODPOIS);
SET_CONSTANT("CONDITION", BL_MASK_GLOWHANDS);
SET_CONSTANT("CONDITION", BL_MASK_GRAB);
SET_CONSTANT("CONDITION", BL_MASK_HALLU);
SET_CONSTANT("CONDITION", BL_MASK_HELD);
SET_CONSTANT("CONDITION", BL_MASK_ICY);
SET_CONSTANT("CONDITION", BL_MASK_INLAVA);
SET_CONSTANT("CONDITION", BL_MASK_LEV);
SET_CONSTANT("CONDITION", BL_MASK_PARLYZ);
SET_CONSTANT("CONDITION", BL_MASK_RIDE);
SET_CONSTANT("CONDITION", BL_MASK_SLEEPING);
SET_CONSTANT("CONDITION", BL_MASK_SLIME);
SET_CONSTANT("CONDITION", BL_MASK_SLIPPERY);
SET_CONSTANT("CONDITION", BL_MASK_STONE);
SET_CONSTANT("CONDITION", BL_MASK_STRNGL);
SET_CONSTANT("CONDITION", BL_MASK_STUN);
SET_CONSTANT("CONDITION", BL_MASK_SUBMERGED);
SET_CONSTANT("CONDITION", BL_MASK_TERMILL);
SET_CONSTANT("CONDITION", BL_MASK_TETHERED);
SET_CONSTANT("CONDITION", BL_MASK_TRAPPED);
SET_CONSTANT("CONDITION", BL_MASK_UNCONSC);
SET_CONSTANT("CONDITION", BL_MASK_WOUNDEDL);
SET_CONSTANT("CONDITION", BL_MASK_HOLDING);
// menu
SET_CONSTANT("MENU_SELECT", PICK_NONE);
SET_CONSTANT("MENU_SELECT", PICK_ONE);
SET_CONSTANT("MENU_SELECT", PICK_ANY);
// copyright
SET_CONSTANT_STRING("COPYRIGHT", COPYRIGHT_BANNER_A);
SET_CONSTANT_STRING("COPYRIGHT", COPYRIGHT_BANNER_B);
set_const_str("COPYRIGHT", "COPYRIGHT_BANNER_C", (char*) COPYRIGHT_BANNER_C);
SET_CONSTANT_STRING("COPYRIGHT", COPYRIGHT_BANNER_D);
// glyphs
SET_CONSTANT("GLYPH", GLYPH_MON_OFF);
SET_CONSTANT("GLYPH", GLYPH_PET_OFF);
SET_CONSTANT("GLYPH", GLYPH_INVIS_OFF);
SET_CONSTANT("GLYPH", GLYPH_DETECT_OFF);
SET_CONSTANT("GLYPH", GLYPH_BODY_OFF);
SET_CONSTANT("GLYPH", GLYPH_RIDDEN_OFF);
SET_CONSTANT("GLYPH", GLYPH_OBJ_OFF);
SET_CONSTANT("GLYPH", GLYPH_CMAP_OFF);
SET_CONSTANT("GLYPH", GLYPH_EXPLODE_OFF);
SET_CONSTANT("GLYPH", GLYPH_ZAP_OFF);
SET_CONSTANT("GLYPH", GLYPH_SWALLOW_OFF);
SET_CONSTANT("GLYPH", GLYPH_WARNING_OFF);
SET_CONSTANT("GLYPH", GLYPH_STATUE_OFF);
SET_CONSTANT("GLYPH", GLYPH_UNEXPLORED_OFF);
SET_CONSTANT("GLYPH", GLYPH_NOTHING_OFF);
SET_CONSTANT("GLYPH", MAX_GLYPH);
SET_CONSTANT("GLYPH", NO_GLYPH);
SET_CONSTANT("GLYPH", GLYPH_INVISIBLE);
SET_CONSTANT("GLYPH", GLYPH_UNEXPLORED);
SET_CONSTANT("GLYPH", GLYPH_NOTHING);
// colors
SET_CONSTANT("COLORS", CLR_BLACK);
SET_CONSTANT("COLORS", CLR_RED);
SET_CONSTANT("COLORS", CLR_GREEN);
SET_CONSTANT("COLORS", CLR_BROWN);
SET_CONSTANT("COLORS", CLR_BLUE);
SET_CONSTANT("COLORS", CLR_MAGENTA);
SET_CONSTANT("COLORS", CLR_CYAN);
SET_CONSTANT("COLORS", CLR_GRAY);
SET_CONSTANT("COLORS", NO_COLOR);
SET_CONSTANT("COLORS", CLR_ORANGE);
SET_CONSTANT("COLORS", CLR_BRIGHT_GREEN);
SET_CONSTANT("COLORS", CLR_YELLOW);
SET_CONSTANT("COLORS", CLR_BRIGHT_BLUE);
SET_CONSTANT("COLORS", CLR_BRIGHT_MAGENTA);
SET_CONSTANT("COLORS", CLR_BRIGHT_CYAN);
SET_CONSTANT("COLORS", CLR_WHITE);
SET_CONSTANT("COLORS", CLR_MAX);
// color attributes (?)
SET_CONSTANT("COLOR_ATTR", HL_ATTCLR_BOLD);
SET_CONSTANT("COLOR_ATTR", HL_ATTCLR_DIM);
SET_CONSTANT("COLOR_ATTR", HL_ATTCLR_ITALIC);
SET_CONSTANT("COLOR_ATTR", HL_ATTCLR_ULINE);
SET_CONSTANT("COLOR_ATTR", HL_ATTCLR_BLINK);
SET_CONSTANT("COLOR_ATTR", HL_ATTCLR_INVERSE);
SET_CONSTANT("COLOR_ATTR", BL_ATTCLR_MAX);
SET_CONSTANT("BL_MASK", BL_MASK_BAREH);
SET_CONSTANT("BL_MASK", BL_MASK_BLIND);
SET_CONSTANT("BL_MASK", BL_MASK_BUSY);
SET_CONSTANT("BL_MASK", BL_MASK_CONF);
SET_CONSTANT("BL_MASK", BL_MASK_DEAF);
SET_CONSTANT("BL_MASK", BL_MASK_ELF_IRON);
SET_CONSTANT("BL_MASK", BL_MASK_FLY);
SET_CONSTANT("BL_MASK", BL_MASK_FOODPOIS);
SET_CONSTANT("BL_MASK", BL_MASK_GLOWHANDS);
SET_CONSTANT("BL_MASK", BL_MASK_GRAB);
SET_CONSTANT("BL_MASK", BL_MASK_HALLU);
SET_CONSTANT("BL_MASK", BL_MASK_HELD);
SET_CONSTANT("BL_MASK", BL_MASK_ICY);
SET_CONSTANT("BL_MASK", BL_MASK_INLAVA);
SET_CONSTANT("BL_MASK", BL_MASK_LEV);
SET_CONSTANT("BL_MASK", BL_MASK_PARLYZ);
SET_CONSTANT("BL_MASK", BL_MASK_RIDE);
SET_CONSTANT("BL_MASK", BL_MASK_SLEEPING);
SET_CONSTANT("BL_MASK", BL_MASK_SLIME);
SET_CONSTANT("BL_MASK", BL_MASK_SLIPPERY);
SET_CONSTANT("BL_MASK", BL_MASK_STONE);
SET_CONSTANT("BL_MASK", BL_MASK_STRNGL);
SET_CONSTANT("BL_MASK", BL_MASK_STUN);
SET_CONSTANT("BL_MASK", BL_MASK_SUBMERGED);
SET_CONSTANT("BL_MASK", BL_MASK_TERMILL);
SET_CONSTANT("BL_MASK", BL_MASK_TETHERED);
SET_CONSTANT("BL_MASK", BL_MASK_TRAPPED);
SET_CONSTANT("BL_MASK", BL_MASK_UNCONSC);
SET_CONSTANT("BL_MASK", BL_MASK_WOUNDEDL);
SET_CONSTANT("BL_MASK", BL_MASK_HOLDING);
SET_CONSTANT("ROLE_RACEMASK", MH_HUMAN);
SET_CONSTANT("ROLE_RACEMASK", MH_ELF);
SET_CONSTANT("ROLE_RACEMASK", MH_DWARF);
SET_CONSTANT("ROLE_RACEMASK", MH_GNOME);
SET_CONSTANT("ROLE_RACEMASK", MH_ORC);
SET_CONSTANT("ROLE_GENDMASK", ROLE_MALE);
SET_CONSTANT("ROLE_GENDMASK", ROLE_FEMALE);
SET_CONSTANT("ROLE_GENDMASK", ROLE_NEUTER);
SET_CONSTANT("ROLE_ALIGNMASK", ROLE_LAWFUL);
SET_CONSTANT("ROLE_ALIGNMASK", ROLE_NEUTRAL);
SET_CONSTANT("ROLE_ALIGNMASK", ROLE_CHAOTIC);
SET_CONSTANT("blconditions", bl_bareh);
SET_CONSTANT("blconditions", bl_blind);
SET_CONSTANT("blconditions", bl_busy);
SET_CONSTANT("blconditions", bl_conf);
SET_CONSTANT("blconditions", bl_deaf);
SET_CONSTANT("blconditions", bl_elf_iron);
SET_CONSTANT("blconditions", bl_fly);
SET_CONSTANT("blconditions", bl_foodpois);
SET_CONSTANT("blconditions", bl_glowhands);
SET_CONSTANT("blconditions", bl_grab);
SET_CONSTANT("blconditions", bl_hallu);
SET_CONSTANT("blconditions", bl_held);
SET_CONSTANT("blconditions", bl_icy);
SET_CONSTANT("blconditions", bl_inlava);
SET_CONSTANT("blconditions", bl_lev);
SET_CONSTANT("blconditions", bl_parlyz);
SET_CONSTANT("blconditions", bl_ride);
SET_CONSTANT("blconditions", bl_sleeping);
SET_CONSTANT("blconditions", bl_slime);
SET_CONSTANT("blconditions", bl_slippery);
SET_CONSTANT("blconditions", bl_stone);
SET_CONSTANT("blconditions", bl_strngl);
SET_CONSTANT("blconditions", bl_stun);
SET_CONSTANT("blconditions", bl_submerged);
SET_CONSTANT("blconditions", bl_termill);
SET_CONSTANT("blconditions", bl_tethered);
SET_CONSTANT("blconditions", bl_trapped);
SET_CONSTANT("blconditions", bl_unconsc);
SET_CONSTANT("blconditions", bl_woundedl);
SET_CONSTANT("blconditions", bl_holding);
SET_CONSTANT("blconditions", CONDITION_COUNT);
SET_CONSTANT("HL", HL_UNDEF);
SET_CONSTANT("HL", HL_NONE);
SET_CONSTANT("HL", HL_BOLD);
SET_CONSTANT("HL", HL_DIM);
SET_CONSTANT("HL", HL_ITALIC);
SET_CONSTANT("HL", HL_ULINE);
SET_CONSTANT("HL", HL_BLINK);
SET_CONSTANT("HL", HL_INVERSE);
SET_CONSTANT("MG", MG_HERO);
SET_CONSTANT("MG", MG_CORPSE);
SET_CONSTANT("MG", MG_INVIS);
SET_CONSTANT("MG", MG_DETECT);
SET_CONSTANT("MG", MG_PET);
SET_CONSTANT("MG", MG_RIDDEN);
SET_CONSTANT("MG", MG_STATUE);
SET_CONSTANT("MG", MG_OBJPILE);
SET_CONSTANT("MG", MG_BW_LAVA);
SET_CONSTANT("MG", MG_BW_ICE);
SET_CONSTANT("MG", MG_BW_SINK);
SET_CONSTANT("MG", MG_BW_ENGR);
SET_CONSTANT("MG", MG_NOTHING);
SET_CONSTANT("MG", MG_UNEXPL);
SET_CONSTANT("MG", MG_MALE);
SET_CONSTANT("MG", MG_FEMALE);
SET_POINTER(extcmdlist);
SET_POINTER(conditions);
SET_POINTER(condtests);
/* roles/races/genders/alignments */
SET_POINTER(roles);
SET_POINTER(races);
SET_POINTER(genders);
SET_POINTER(aligns);
}
/***
* Globals
***/
#define CREATE_GLOBAL(var, type) create_global(#var, (void *)&var, type);
#define CREATE_GLOBAL_FROM_ARRAY(base, iter, path, end_expr, type) \
for(iter = 0; end_expr; iter++) { \
snprintf(buf, BUFSZ, #base ".%d." #path, iter); \
create_global(buf, (void *)(&(base[iter].path)), type); \
}
void create_global (char *name, void *ptr, char *type);
void js_globals_init() {
// int i;
// char buf[BUFSZ];
EM_ASM({
globalThis.nethackGlobal = globalThis.nethackGlobal || {};
globalThis.nethackGlobal.globals = globalThis.nethackGlobal.globals || {};
});
/* globals */
CREATE_GLOBAL(svp.plname, "s");
/* window globals */
CREATE_GLOBAL(WIN_MAP, "i");
CREATE_GLOBAL(WIN_MESSAGE, "i");
CREATE_GLOBAL(WIN_INVEN, "i");
CREATE_GLOBAL(WIN_STATUS, "i");
/* instance flags */
CREATE_GLOBAL(iflags.window_inited, "b");
CREATE_GLOBAL(iflags.wc2_hitpointbar, "b");
CREATE_GLOBAL(iflags.wc_hilite_pet, "b");
CREATE_GLOBAL(iflags.hilite_pile, "b");
/* flags */
CREATE_GLOBAL(flags.initrole, "i");
CREATE_GLOBAL(flags.initrace, "i");
CREATE_GLOBAL(flags.initgend, "i");
CREATE_GLOBAL(flags.initalign, "i");
CREATE_GLOBAL(flags.showexp, "b");
CREATE_GLOBAL(flags.time, "b");
}
EM_JS(void, create_global, (char *name_str, void *ptr, char *type_str), {
let name = UTF8ToString(name_str);
let type = UTF8ToString(type_str);
// get helpers
let getPointerValue = globalThis.nethackGlobal.helpers.getPointerValue;
let setPointerValue = globalThis.nethackGlobal.helpers.setPointerValue;
let { obj, prop } = createPath(globalThis.nethackGlobal.globals, name);
// setters / getters with bound pointers
Object.defineProperty(obj, prop, {
get: getPointerValue.bind(null, name, ptr, type),
set: setPointerValue.bind(null, name, ptr, type),
configurable: true,
enumerable: true
});
function createPath(obj, path) {
path = path.split(".");
let i;
for (i = 0; i < path.length - 1; i++) {
// obj[path[i]] = obj[path[i]] || {};
if (obj[path[i]] === undefined) {
obj[path[i]] = {};
}
obj = obj[path[i]];
}
return { obj, prop: path[i] };
}
})
#endif
/*libnhmain.c*/