diff --git a/include/unixconf.h b/include/unixconf.h index 4a43e88..8293dda 100644 --- a/include/unixconf.h +++ b/include/unixconf.h @@ -316,7 +316,12 @@ #define HLOCK "perm" /* an empty file used for locking purposes */ +#ifdef __EMSCRIPTEN__ +extern int fredy_getch(void); +#define tgetch fredy_getch +#else #define tgetch getchar +#endif #ifndef NOSHELL #define SHELL /* do not delete the '!' command */ diff --git a/src/date.c b/src/date.c index 3f5a3a8..b6ae824 100644 --- a/src/date.c +++ b/src/date.c @@ -79,7 +79,12 @@ populate_nomakedefs(struct version_info *version) * */ +#ifdef __EMSCRIPTEN__ + /* reproducible build: the time of the source commit, not of the compilation */ + Snprintf(tmpbuf1, sizeof tmpbuf1, "%s %s", "May 26 2026", "20:15:32"); +#else Snprintf(tmpbuf1, sizeof tmpbuf1, "%s %s", __DATE__, __TIME__); +#endif /* "Feb 12 1996 23:59:01" 01234567890123456789 */ if ((int) strlen(tmpbuf1) == 20) { diff --git a/sys/libnh/sysconf b/sys/libnh/sysconf index bc3e69e..ed9525a 100644 --- a/sys/libnh/sysconf +++ b/sys/libnh/sysconf @@ -36,7 +36,7 @@ EXPLORERS=* # Uses the same syntax as the WIZARDS option above. # A public server should probably disable this. # ["ec2-user" is the default user name on Amazon Linux] -GENERICUSERS=play player game games nethack nethacker ec2-user +GENERICUSERS=play player game games nethack nethacker ec2-user web_user # Use the player name for matching WIZARDS, EXPLORERS and SHELLERS, # instead of the user's login name. @@ -159,6 +159,5 @@ PANICTRACE_LIBC=0 # option settings via XNETHACKOPTIONS in their environment or via # ~/.xnethackrc run-time configuration file. #OPTIONS=!autopickup,fruit:tomato,symset:DECgraphics -OPTIONS=perm_invent #eof diff --git a/sys/unix/fredytc.c b/sys/unix/fredytc.c new file mode 100644 index 0000000..92f1d64 --- /dev/null +++ b/sys/unix/fredytc.c @@ -0,0 +1,99 @@ +/* fredytc.c: a tiny termcap for the fredygames.com browser build of xNetHack (Emscripten). + * + * NetHack's tty window port talks to the terminal through termcap/terminfo calls. In the browser there is + * no terminal database, so this file answers those calls for an xterm-like ANSI terminal (16 colors) that + * the page (play.html) draws on a canvas, and reads keys from the page. Nothing here changes how the game + * plays. + * + * Copyright (c) 2026 FredyGames. NetHack General Public License, like the rest of the program. + */ +#include +#include +#include +#include +#include + +#define FREDY_COLS 80 +#define FREDY_ROWS 26 + +short ospeed; + +int tgetent(char *buf, const char *name) { (void) buf; (void) name; return 1; } + +int tgetflag(const char *id) +{ + return !strcmp(id, "bs") ? 1 : 0; +} + +int tgetnum(const char *id) +{ + if (!strcmp(id, "co")) return FREDY_COLS; + if (!strcmp(id, "li")) return FREDY_ROWS; + if (!strcmp(id, "Co")) return 16; + return -1; +} + +static const struct { const char *id; const char *val; } caps[] = { + { "le", "\b" }, { "bc", "\b" }, + { "ho", "\033[H" }, { "nd", "\033[C" }, { "up", "\033[A" }, + { "ce", "\033[K" }, { "cd", "\033[J" }, { "cl", "\033[H\033[2J" }, + { "cm", "\033[%i%d;%dH" }, + { "so", "\033[7m" }, { "se", "\033[27m" }, + { "us", "\033[4m" }, { "ue", "\033[24m" }, + { "mr", "\033[7m" }, { "mb", "\033[5m" }, { "md", "\033[1m" }, + { "mh", "\033[2m" }, { "me", "\033[0m" }, + { "vi", "\033[?25l" }, { "ve", "\033[?25h" }, + { "AF", "\033[%dm" }, + { 0, 0 } +}; + +char *tgetstr(const char *id, char **area) +{ + int i; + (void) area; + for (i = 0; caps[i].id; i++) + if (!strcmp(id, caps[i].id)) + return (char *) caps[i].val; + return NULL; +} + +/* tgoto() for the "cm" string above: rows and columns are 1-based on the wire. */ +char *tgoto(const char *cm, int col, int row) +{ + static char out[32]; + (void) cm; + snprintf(out, sizeof out, "\033[%d;%dH", row + 1, col + 1); + return out; +} + +void tputs(const char *s, int n, int (*putc_fn)(int)) +{ + (void) n; + while (*s) + putc_fn((unsigned char) *s++); +} + +/* tparm(AF, color): the SGR code for ANSI color 0-7 (normal) or 8-15 (bright). */ +char *tparm(const char *fmt, ...) +{ + static char out[16]; + va_list ap; + int c; + (void) fmt; + va_start(ap, fmt); + c = va_arg(ap, int); + va_end(ap); + snprintf(out, sizeof out, "\033[%dm", c < 8 ? 30 + c : 90 + (c - 8)); + return out; +} + +/* A key typed on the page; the build uses Asyncify so the game waits here without blocking the browser. */ +EM_ASYNC_JS(int, fredy_wait_key, (void), { + return await Module.fredyWaitKey(); +}); + +int fredy_getch(void) +{ + fflush(stdout); + return fredy_wait_key(); +} diff --git a/sys/unix/fredyterm/curses.h b/sys/unix/fredyterm/curses.h new file mode 100644 index 0000000..69da165 --- /dev/null +++ b/sys/unix/fredyterm/curses.h @@ -0,0 +1,19 @@ +/* Stand-in for in the fredygames.com browser build (see fredytc.c). */ +#ifndef FREDY_CURSES_H +#define FREDY_CURSES_H +#define COLOR_BLACK 0 +#define COLOR_RED 1 +#define COLOR_GREEN 2 +#define COLOR_YELLOW 3 +#define COLOR_BLUE 4 +#define COLOR_MAGENTA 5 +#define COLOR_CYAN 6 +#define COLOR_WHITE 7 +extern int tgetent(char *, const char *); +extern void tputs(const char *, int, int (*)(int)); +extern int tgetnum(const char *); +extern int tgetflag(const char *); +extern char *tgetstr(const char *, char **); +extern char *tgoto(const char *, int, int); +extern char *tparm(const char *, ...); +#endif diff --git a/sys/unix/fredyterm/term.h b/sys/unix/fredyterm/term.h new file mode 100644 index 0000000..2d690ee --- /dev/null +++ b/sys/unix/fredyterm/term.h @@ -0,0 +1 @@ +/* Empty stand-in for in the fredygames.com browser build (see fredytc.c). */ diff --git a/sys/unix/hints/include/cross-pre2.500 b/sys/unix/hints/include/cross-pre2.500 index ef6f1c0..30ab526 100644 --- a/sys/unix/hints/include/cross-pre2.500 +++ b/sys/unix/hints/include/cross-pre2.500 @@ -281,29 +281,24 @@ WASM_DATA_DIR = $(TARGETPFX)wasm-data WASM_TARGET = $(TARGETPFX)nethack.js EMCC_LFLAGS = #EMCC_LFLAGS += -s SINGLE_FILE=1 -EMCC_LFLAGS += -DHACKDIR=\"$(HACKDIR)\" EMCC_LFLAGS += -s WASM=1 EMCC_LFLAGS += -s ALLOW_TABLE_GROWTH -EMCC_LFLAGS += -s ASYNCIFY -s ASYNCIFY_IMPORTS='["local_callback"]' +EMCC_LFLAGS += -s ASYNCIFY -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=64MB -s STACK_SIZE=1MB +EMCC_LFLAGS += -s ENVIRONMENT=web -s EXIT_RUNTIME=1 EMCC_LFLAGS += -O3 -EMCC_LFLAGS += -s MODULARIZE -EMCC_LFLAGS += -s EXPORTED_FUNCTIONS='["_main", "_shim_graphics_set_callback", "_repopulate_perminvent", "_malloc"]' -EMCC_LFLAGS += -s EXPORTED_RUNTIME_METHODS='["cwrap", "ccall", "addFunction", \ - "removeFunction", "UTF8ToString", "stringToUTF8", "getValue", \ - "setValue", "ENV", "FS", "IDBFS"]' +EMCC_LFLAGS += -s EXPORTED_FUNCTIONS='["_main"]' +EMCC_LFLAGS += -s EXPORTED_RUNTIME_METHODS='["FS", "ENV", "addRunDependency", "removeRunDependency"]' EMCC_LFLAGS += -s ERROR_ON_UNDEFINED_SYMBOLS=0 -EMCC_LFLAGS += -s EXPORT_ES6=1 EMCC_LFLAGS += -lidbfs.js # XXX: the "@/" at the end of "--embed-file" tells emscripten to embed the files # in the root directory, otherwise they will end up in the $(WASM_DATA_DIR) path -EMCC_LFLAGS += --embed-file $(WASM_DATA_DIR)@/ +EMCC_LFLAGS += --preload-file $(WASM_DATA_DIR)@/nhdata # For a list of EMCC settings: # https://github.com/emscripten-core/emscripten/blob/master/src/settings.js # # WASM C flags EMCC_CFLAGS = EMCC_CFLAGS += -Wall -EMCC_CFLAGS += -Werror EMCC_CFLAGS += -DNO_SIGNAL #EMCC_CFLAGS += -s DISABLE_EXCEPTION_CATCHING=0 EMCC_DEBUG_LFLAGS += -s ASSERTIONS=1 @@ -342,12 +337,11 @@ WASM_CFLAGS += -DSYSCF -DSYSCF_FILE=\"/sysconf\" -DSECURE -DNHUUID WASM_CFLAGS += -I../include -DNOTPARMDECL # NetHack sources control WASM_CFLAGS += -DDLB -WASM_CFLAGS += -DHACKDIR=\"$(HACKDIR)\" -WASM_CFLAGS += -DDEFAULT_WINDOW_SYS=\"shim\" \ +WASM_CFLAGS += -DHACKDIR=\"/nh\" -I../sys/unix/fredyterm +WASM_CFLAGS += -DDEFAULT_WINDOW_SYS=\"tty\" #override TARGET_CFLAGS += -DGREPPATH=\"/usr/bin/grep\" WASM_CFLAGS += -DNOMAIL WASM_CFLAGS += $(LUAINCL) -WASM_CFLAGS += -DNOTTYGRAPHICS -DSHIM_GRAPHICS -DLIBNH WASM_CFLAGS += -DCROSSCOMPILE WASM_TARGET_CFLAGS = -DCROSSCOMPILE_TARGET -DCROSS_TO_WASM # For src Makefile @@ -358,24 +352,24 @@ LUA_TARGET_CFLAGS = $(TARGET_CFLAGS) override TARGET_CXXFLAGS = $(TARGET_CFLAGS) override TARGET_LINK = $(TARGET_CC) override TARGET_LFLAGS= $(EMCC_LFLAGS) -override SYSSRC = ../sys/libnh/libnhmain.c \ +override SYSSRC = ../sys/unix/unixmain.c \ ../sys/share/ioctl.c ../sys/share/unixtty.c \ ../sys/unix/unixunix.c ../sys/unix/unixres.c \ - ../win/shim/winshim.c -override SYSOBJ= $(TARGETPFX)libnhmain.o \ + ../sys/unix/fredytc.c +override SYSOBJ= $(TARGETPFX)unixmain.o \ $(TARGETPFX)ioctl.o $(TARGETPFX)unixtty.o \ $(TARGETPFX)unixunix.o $(TARGETPFX)unixres.o \ - $(TARGETPFX)winshim.o + $(TARGETPFX)fredytc.o override WINLIB = emranlib override LUALIB= override LUATOPLIB= override REGEXOBJ = $(TARGETPFX)posixregex.o -override WINOBJ= +override WINOBJ= $(WINTTYOBJ) override UUIDOBJ= # don't bother Making regular NetHack executable override GAME= # the real VARDAT hasn't been defined yet for use in ALLDEP override -WASM_DAT = bogusmon data engrave epitaph oracles quest.lua rumors +WASM_DAT = bogusmon data engrave epitaph oracles quest.lua rumors shirts WASMDEP = include/nhlua.h $(WASM_DAT) spec_levs check-dlb override ALLDEP = $(WASMDEP) wasm override PREGAME += mkdir -p $(TARGETDIR)/wasm-data ; diff --git a/sys/unix/unixunix.c b/sys/unix/unixunix.c index a7fb3f9..74a775f 100644 --- a/sys/unix/unixunix.c +++ b/sys/unix/unixunix.c @@ -6,6 +6,11 @@ /* This file collects some Unix dependencies */ #include "hack.h" /* mainly for strchr() which depends on BSD */ +#ifdef __EMSCRIPTEN__ +#define fredy_browser() 1 +#else +#define fredy_browser() 0 +#endif #include #include @@ -117,8 +122,9 @@ getlock(void) * also incidentally prevents development of any hack-o-matic programs */ /* added check for window-system type -dlc */ + /* the browser build has no tty on fd 0: the page feeds the keys (fredytc.c) */ if (!strcmp(windowprocs.name, "tty")) - if (!isatty(0)) + if (!isatty(0) && !fredy_browser()) error("You must play from a terminal."); #endif diff --git a/win/tty/wintty.c b/win/tty/wintty.c index 95034e2..755a482 100644 --- a/win/tty/wintty.c +++ b/win/tty/wintty.c @@ -765,8 +765,12 @@ getret(void) { #if defined(MICRO) || defined(WIN32CON) getreturn("to continue"); +#else +#ifdef __EMSCRIPTEN__ + if (program_state.early_options) #else if (!isatty(STDIN_FILENO) || program_state.early_options) +#endif return; HUPSKIP(); xputs("\n");