Files
nethack/src/nhlua.c
nhkeni bacfa6ef40 More lua setup cleanup
Stop (poorly) tracking the amount of memory in use and get
 it from Lua instead.
Simplify nhl_alloc.
2024-01-17 15:04:40 -05:00

2899 lines
78 KiB
C

/* NetHack 3.7 nhlua.c $NHDT-Date: 1705087450 2024/01/12 19:24:10 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.129 $ */
/* Copyright (c) 2018 by Pasi Kallinen */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#include "dlb.h"
#ifndef LUA_VERSION_RELEASE_NUM
#ifdef NHL_SANDBOX
#undef NHL_SANDBOX
#endif
#endif
#ifdef NHL_SANDBOX
#include <setjmp.h>
#endif
/*
#- include <lua5.3/lua.h>
#- include <lua5.3/lualib.h>
#- include <lua5.3/lauxlib.h>
*/
/* */
/* lua_CFunction prototypes */
#ifdef DUMPLOG
static int nhl_dump_fmtstr(lua_State *);
#endif /* DUMPLOG */
static int nhl_dnum_name(lua_State *);
static int nhl_stairways(lua_State *);
static int nhl_pushkey(lua_State *);
static int nhl_doturn(lua_State *);
static int nhl_debug_flags(lua_State *);
static int nhl_timer_has_at(lua_State *);
static int nhl_timer_peek_at(lua_State *);
static int nhl_timer_stop_at(lua_State *);
static int nhl_timer_start_at(lua_State *);
static int nhl_get_cmd_key(lua_State *);
static int nhl_callback(lua_State *);
static int nhl_gamestate(lua_State *);
static int nhl_test(lua_State *);
static int nhl_getmap(lua_State *);
static char splev_typ2chr(schar);
static int nhl_gettrap(lua_State *);
static int nhl_deltrap(lua_State *);
#if 0
static int nhl_setmap(lua_State *);
#endif
static int nhl_impossible(lua_State *);
static int nhl_pline(lua_State *);
static int nhl_verbalize(lua_State *);
static int nhl_parse_config(lua_State *);
static int nhl_menu(lua_State *);
static int nhl_text(lua_State *);
static int nhl_getlin(lua_State *);
static int nhl_makeplural(lua_State *);
static int nhl_makesingular(lua_State *);
static int nhl_s_suffix(lua_State *);
static int nhl_ing_suffix(lua_State *);
static int nhl_an(lua_State *);
static int nhl_rn2(lua_State *);
static int nhl_random(lua_State *);
static int nhl_level_difficulty(lua_State *);
static void init_nhc_data(lua_State *);
static int nhl_push_anything(lua_State *, int, void *);
static int nhl_meta_u_index(lua_State *);
static int nhl_meta_u_newindex(lua_State *);
static int nhl_u_clear_inventory(lua_State *);
static int nhl_u_giveobj(lua_State *);
static void init_u_data(lua_State *);
#ifdef notyet
static int nhl_set_package_path(lua_State *, const char *);
#endif
static int traceback_handler(lua_State *);
#ifdef NHL_SANDBOX
static void nhlL_openlibs(lua_State *, uint32_t);
#endif
static void *nhl_alloc(void *, void *, size_t, size_t);
static lua_State *nhlL_newstate (nhl_sandbox_info *, const char *);
static void end_luapat(void);
static const char *const nhcore_call_names[NUM_NHCORE_CALLS] = {
"start_new_game",
"restore_old_game",
"moveloop_turn",
"game_exit",
"getpos_tip",
"enter_tutorial",
"leave_tutorial",
};
static boolean nhcore_call_available[NUM_NHCORE_CALLS];
/* internal structure that hangs off L->ud (but use lua_getallocf() )
* Note that we use it for both memory use tracking and instruction counting.
*/
typedef struct nhl_user_data {
lua_State *L; /* because the allocator needs it */
uint32_t flags; /* from nhl_sandbox_info */
uint32_t memlimit;
uint32_t steps; /* current counter */
uint32_t osteps; /* original steps value */
uint32_t perpcall; /* per pcall steps value */
/* stats */
uint32_t statctr; /* stats step reload count */
int sid; /* id number (per state) */
const char *name; /* for stats logging (per pcall) */
#ifdef NHL_SANDBOX
jmp_buf jb;
#endif
} nhl_user_data;
static lua_State *luapat; /* instance for file pattern matching */
void
l_nhcore_init(void)
{
nhl_sandbox_info sbi = { NHL_SB_SAFE, 1 * 1024 * 1024, 0,
1 * 1024 * 1024 };
if ((gl.luacore = nhl_init(&sbi)) != 0) {
if (!nhl_loadlua(gl.luacore, "nhcore.lua")) {
gl.luacore = (lua_State *) 0;
} else {
int i;
for (i = 0; i < NUM_NHCORE_CALLS; i++)
nhcore_call_available[i] = TRUE;
}
} else
impossible("l_nhcore_init failed");
}
void
l_nhcore_done(void)
{
if (gl.luacore) {
nhl_done(gl.luacore);
gl.luacore = 0;
}
end_luapat();
}
void
l_nhcore_call(int callidx)
{
int ltyp;
if (callidx < 0 || callidx >= NUM_NHCORE_CALLS || !gl.luacore
|| !nhcore_call_available[callidx])
return;
lua_getglobal(gl.luacore, "nhcore");
if (!lua_istable(gl.luacore, -1)) {
/*impossible("nhcore is not a lua table");*/
nhl_done(gl.luacore);
gl.luacore = 0;
return;
}
lua_getfield(gl.luacore, -1, nhcore_call_names[callidx]);
ltyp = lua_type(gl.luacore, -1);
if (ltyp == LUA_TFUNCTION) {
lua_remove(gl.luacore, -2); /* nhcore_call_names[callidx] */
lua_remove(gl.luacore, -2); /* nhcore */
nhl_pcall_handle(gl.luacore, 0, 1, "l_nhcore_call", NHLpa_panic);
} else {
/*impossible("nhcore.%s is not a lua function",
nhcore_call_names[callidx]);*/
nhcore_call_available[callidx] = FALSE;
}
}
DISABLE_WARNING_UNREACHABLE_CODE
ATTRNORETURN void
nhl_error(lua_State *L, const char *msg)
{
lua_Debug ar;
char buf[BUFSZ * 2];
lua_getstack(L, 1, &ar);
lua_getinfo(L, "lS", &ar);
Sprintf(buf, "%s (line %d ", msg, ar.currentline);
Sprintf(eos(buf), "%.*s)",
/* (max length of ar.short_src is actually LUA_IDSIZE
so this is overkill for it, but crucial for ar.source) */
(int) (sizeof buf - (strlen(buf) + sizeof ")")),
ar.short_src); /* (used to be 'ar.source' here) */
lua_pushstring(L, buf);
#if 0 /* defined(PANICTRACE) && !defined(NO_SIGNALS) */
panictrace_setsignals(FALSE);
#endif
(void) lua_error(L);
/*NOTREACHED*/
}
RESTORE_WARNING_UNREACHABLE_CODE
/* Check that parameters are nothing but single table,
or if no parameters given, put empty table there */
void
lcheck_param_table(lua_State *L)
{
int argc = lua_gettop(L);
if (argc < 1)
lua_createtable(L, 0, 0);
/* discard any extra arguments passed in */
lua_settop(L, 1);
luaL_checktype(L, 1, LUA_TTABLE);
}
DISABLE_WARNING_UNREACHABLE_CODE
schar
get_table_mapchr(lua_State *L, const char *name)
{
char *ter;
xint8 typ;
ter = get_table_str(L, name);
typ = check_mapchr(ter);
if (typ == INVALID_TYPE)
nhl_error(L, "Erroneous map char");
if (ter)
free(ter);
return typ;
}
schar
get_table_mapchr_opt(lua_State *L, const char *name, schar defval)
{
char *ter;
xint8 typ;
ter = get_table_str_opt(L, name, emptystr);
if (ter && *ter) {
typ = (xint8) check_mapchr(ter);
if (typ == INVALID_TYPE)
nhl_error(L, "Erroneous map char");
} else
typ = (xint8) defval;
if (ter)
free(ter);
return typ;
}
short
nhl_get_timertype(lua_State *L, int idx)
{
/* these are in the same order as enum timeout_types in timeout.h and
ttable timeout_funcs[] in timeout.c, although not spelled the same */
static const char *const timerstr[NUM_TIME_FUNCS + 1] = {
"rot-organic", "rot-corpse", "revive-mon", "zombify-mon",
"burn-obj", "hatch-egg", "fig-transform", "shrink-glob",
"melt-ice", NULL
};
short ret = luaL_checkoption(L, idx, NULL, timerstr);
if (ret < 0 || ret >= NUM_TIME_FUNCS)
nhl_error(L, "Unknown timer type");
return ret;
}
RESTORE_WARNING_UNREACHABLE_CODE
void
nhl_add_table_entry_int(lua_State *L, const char *name, lua_Integer value)
{
lua_pushstring(L, name);
lua_pushinteger(L, value);
lua_rawset(L, -3);
}
void
nhl_add_table_entry_char(lua_State *L, const char *name, char value)
{
char buf[2];
Sprintf(buf, "%c", value);
lua_pushstring(L, name);
lua_pushstring(L, buf);
lua_rawset(L, -3);
}
void
nhl_add_table_entry_str(lua_State *L, const char *name, const char *value)
{
lua_pushstring(L, name);
lua_pushstring(L, value);
lua_rawset(L, -3);
}
void
nhl_add_table_entry_bool(lua_State *L, const char *name, boolean value)
{
lua_pushstring(L, name);
lua_pushboolean(L, value);
lua_rawset(L, -3);
}
void
nhl_add_table_entry_region(lua_State *L, const char *name, coordxy x1,
coordxy y1, coordxy x2, coordxy y2)
{
lua_pushstring(L, name);
lua_newtable(L);
nhl_add_table_entry_int(L, "x1", x1);
nhl_add_table_entry_int(L, "y1", y1);
nhl_add_table_entry_int(L, "x2", x2);
nhl_add_table_entry_int(L, "y2", y2);
lua_rawset(L, -3);
}
/* converting from special level "map character" to levl location type
and back. order here is important. */
const struct {
char ch;
schar typ;
} char2typ[] = {
{ ' ', STONE },
{ '#', CORR },
{ '.', ROOM },
{ '-', HWALL },
{ '-', TLCORNER },
{ '-', TRCORNER },
{ '-', BLCORNER },
{ '-', BRCORNER },
{ '-', CROSSWALL },
{ '-', TUWALL },
{ '-', TDWALL },
{ '-', TLWALL },
{ '-', TRWALL },
{ '-', DBWALL },
{ '|', VWALL },
{ '+', DOOR },
{ 'A', AIR },
{ 'C', CLOUD },
{ 'S', SDOOR },
{ 'H', SCORR },
{ '{', FOUNTAIN },
{ '\\', THRONE },
{ 'K', SINK },
{ '}', MOAT },
{ 'P', POOL },
{ 'L', LAVAPOOL },
{ 'Z', LAVAWALL },
{ 'I', ICE },
{ 'W', WATER },
{ 'T', TREE },
{ 'F', IRONBARS }, /* Fe = iron */
{ 'x', MAX_TYPE }, /* "see-through" */
{ 'B', CROSSWALL }, /* hack: boundary location */
{ 'w', MATCH_WALL }, /* IS_STWALL() */
{ '\0', STONE },
};
schar
splev_chr2typ(char c)
{
int i;
for (i = 0; char2typ[i].ch; i++)
if (c == char2typ[i].ch)
return char2typ[i].typ;
return (INVALID_TYPE);
}
schar
check_mapchr(const char *s)
{
if (s && strlen(s) == 1)
return splev_chr2typ(s[0]);
return INVALID_TYPE;
}
static char
splev_typ2chr(schar typ)
{
int i;
for (i = 0; char2typ[i].typ < MAX_TYPE; i++)
if (typ == char2typ[i].typ)
return char2typ[i].ch;
return 'x';
}
DISABLE_WARNING_UNREACHABLE_CODE
/* local t = nh.gettrap(x,y); */
/* local t = nh.gettrap({ x = 10, y = 10 }); */
static int
nhl_gettrap(lua_State *L)
{
lua_Integer lx, ly;
coordxy x, y;
if (!nhl_get_xy_params(L, &lx, &ly)) {
nhl_error(L, "Incorrect arguments");
/*NOTREACHED*/
return 0;
}
x = (coordxy) lx;
y = (coordxy) ly;
cvt_to_abscoord(&x, &y);
if (isok(x, y)) {
struct trap *ttmp = t_at(x, y);
if (ttmp) {
lua_newtable(L);
nhl_add_table_entry_int(L, "tx", ttmp->tx);
nhl_add_table_entry_int(L, "ty", ttmp->ty);
nhl_add_table_entry_int(L, "ttyp", ttmp->ttyp);
nhl_add_table_entry_str(L, "ttyp_name",
get_trapname_bytype(ttmp->ttyp));
nhl_add_table_entry_bool(L, "tseen", ttmp->tseen);
nhl_add_table_entry_bool(L, "madeby_u", ttmp->madeby_u);
switch (ttmp->ttyp) {
case SQKY_BOARD:
nhl_add_table_entry_int(L, "tnote", ttmp->tnote);
break;
case ROLLING_BOULDER_TRAP:
nhl_add_table_entry_int(L, "launchx", ttmp->launch.x);
nhl_add_table_entry_int(L, "launchy", ttmp->launch.y);
nhl_add_table_entry_int(L, "launch2x", ttmp->launch2.x);
nhl_add_table_entry_int(L, "launch2y", ttmp->launch2.y);
break;
case PIT:
case SPIKED_PIT:
nhl_add_table_entry_int(L, "conjoined", ttmp->conjoined);
break;
}
return 1;
} else {
nhl_error(L, "No trap at location");
}
} else {
nhl_error(L, "Coordinates out of range");
}
return 0;
}
/* nh.deltrap(x,y); nh.deltrap({ x = 10, y = 15 }); */
static int
nhl_deltrap(lua_State *L)
{
lua_Integer lx, ly;
coordxy x, y;
if (!nhl_get_xy_params(L, &lx, &ly)) {
nhl_error(L, "Incorrect arguments");
/*NOTREACHED*/
return 0;
}
x = (coordxy) lx;
y = (coordxy) ly;
cvt_to_abscoord(&x, &y);
if (isok(x, y)) {
struct trap *ttmp = t_at(x, y);
if (ttmp)
deltrap(ttmp);
}
return 0;
}
RESTORE_WARNING_UNREACHABLE_CODE
/* get parameters (XX,YY) or ({ x = XX, y = YY }) or ({ XX, YY }),
and set the x and y values.
return TRUE if there are such params in the stack.
Note that this does not adjust the values of x and y at all from what is
specified in the level file; so, it returns absolute coordinates rather than
map-relative coordinates. Callers of this function must decide if they want
to interpret the values as absolute or as map-relative, and adjust
accordingly.
*/
boolean
nhl_get_xy_params(lua_State *L, lua_Integer *x, lua_Integer *y)
{
int argc = lua_gettop(L);
boolean ret = FALSE;
if (argc == 2) {
*x = lua_tointeger(L, 1);
*y = lua_tointeger(L, 2);
ret = TRUE;
} else if (argc == 1 && lua_type(L, 1) == LUA_TTABLE) {
lua_Integer ax, ay;
ret = get_coord(L, 1, &ax, &ay);
*x = ax;
*y = ay;
}
return ret;
}
DISABLE_WARNING_UNREACHABLE_CODE
/* local loc = nh.getmap(x,y); */
/* local loc = nh.getmap({ x = 10, y = 35 }); */
static int
nhl_getmap(lua_State *L)
{
lua_Integer lx, ly;
coordxy x, y;
if (!nhl_get_xy_params(L, &lx, &ly)) {
nhl_error(L, "Incorrect arguments");
return 0;
}
x = (coordxy) lx;
y = (coordxy) ly;
cvt_to_abscoord(&x, &y);
if (isok(x, y)) {
char buf[BUFSZ];
lua_newtable(L);
/* FIXME: some should be boolean values */
nhl_add_table_entry_int(L, "glyph", levl[x][y].glyph);
nhl_add_table_entry_int(L, "typ", levl[x][y].typ);
nhl_add_table_entry_str(L, "typ_name",
levltyp_to_name(levl[x][y].typ));
Sprintf(buf, "%c", splev_typ2chr(levl[x][y].typ));
nhl_add_table_entry_str(L, "mapchr", buf);
nhl_add_table_entry_int(L, "seenv", levl[x][y].seenv);
nhl_add_table_entry_bool(L, "horizontal", levl[x][y].horizontal);
nhl_add_table_entry_bool(L, "lit", levl[x][y].lit);
nhl_add_table_entry_bool(L, "waslit", levl[x][y].waslit);
nhl_add_table_entry_int(L, "roomno", levl[x][y].roomno);
nhl_add_table_entry_bool(L, "edge", levl[x][y].edge);
nhl_add_table_entry_bool(L, "candig", levl[x][y].candig);
nhl_add_table_entry_bool(L, "has_trap", t_at(x, y) ? 1 : 0);
/* TODO: FIXME: levl[x][y].flags */
lua_pushliteral(L, "flags");
lua_newtable(L);
if (IS_DOOR(levl[x][y].typ)) {
nhl_add_table_entry_bool(L, "nodoor",
(levl[x][y].flags == D_NODOOR));
nhl_add_table_entry_bool(L, "broken",
(levl[x][y].flags & D_BROKEN));
nhl_add_table_entry_bool(L, "isopen",
(levl[x][y].flags & D_ISOPEN));
nhl_add_table_entry_bool(L, "closed",
(levl[x][y].flags & D_CLOSED));
nhl_add_table_entry_bool(L, "locked",
(levl[x][y].flags & D_LOCKED));
nhl_add_table_entry_bool(L, "trapped",
(levl[x][y].flags & D_TRAPPED));
} else if (IS_ALTAR(levl[x][y].typ)) {
/* TODO: bits 0, 1, 2 */
nhl_add_table_entry_bool(L, "shrine",
(levl[x][y].flags & AM_SHRINE));
} else if (IS_THRONE(levl[x][y].typ)) {
nhl_add_table_entry_bool(L, "looted",
(levl[x][y].flags & T_LOOTED));
} else if (levl[x][y].typ == TREE) {
nhl_add_table_entry_bool(L, "looted",
(levl[x][y].flags & TREE_LOOTED));
nhl_add_table_entry_bool(L, "swarm",
(levl[x][y].flags & TREE_SWARM));
} else if (IS_FOUNTAIN(levl[x][y].typ)) {
nhl_add_table_entry_bool(L, "looted",
(levl[x][y].flags & F_LOOTED));
nhl_add_table_entry_bool(L, "warned",
(levl[x][y].flags & F_WARNED));
} else if (IS_SINK(levl[x][y].typ)) {
nhl_add_table_entry_bool(L, "pudding",
(levl[x][y].flags & S_LPUDDING));
nhl_add_table_entry_bool(L, "dishwasher",
(levl[x][y].flags & S_LDWASHER));
nhl_add_table_entry_bool(L, "ring", (levl[x][y].flags & S_LRING));
}
/* TODO: drawbridges, walls, ladders, room=>ICED_xxx */
lua_settable(L, -3);
return 1;
} else {
/* TODO: return zerorm instead? */
nhl_error(L, "Coordinates out of range");
return 0;
}
}
/* impossible("Error!") */
static int
nhl_impossible(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
impossible("%s", luaL_checkstring(L, 1));
else
nhl_error(L, "Wrong args");
return 0;
}
/* pline("It hits!") */
/* pline("It hits!", true) */
static int
nhl_pline(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1 || argc == 2) {
pline("%s", luaL_checkstring(L, 1));
if (lua_toboolean(L, 2))
display_nhwindow(WIN_MESSAGE, TRUE); /* --more-- */
} else
nhl_error(L, "Wrong args");
return 0;
}
/* verbalize("Fool!") */
static int
nhl_verbalize(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
verbalize("%s", luaL_checkstring(L, 1));
else
nhl_error(L, "Wrong args");
return 0;
}
/* parse_config("OPTIONS=!color") */
static int
nhl_parse_config(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
parse_conf_str(luaL_checkstring(L, 1), parse_config_line);
else
nhl_error(L, "Wrong args");
return 0;
}
/* local windowtype = get_config("windowtype"); */
static int
nhl_get_config(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
lua_pushstring(L, get_option_value(luaL_checkstring(L, 1), TRUE));
return 1;
} else
nhl_error(L, "Wrong args");
return 0;
}
/*
str = getlin("What do you want to call this dungeon level?");
*/
static int
nhl_getlin(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
const char *prompt = luaL_checkstring(L, 1);
char buf[BUFSZ];
getlin(prompt, buf);
lua_pushstring(L, buf);
return 1;
}
nhl_error(L, "Wrong args");
/*NOTREACHED*/
return 0;
}
/*
selected = menu("prompt", default, pickX, { "a" = "option a", "b" = "option b", ...})
pickX = 0,1,2, or "none", "one", "any" (PICK_X in code)
selected = menu("prompt", default, pickX,
{ {key:"a", text:"option a"}, {key:"b", text:"option b"}, ... } ) */
static int
nhl_menu(lua_State *L)
{
static const char *const pickX[] = { "none", "one", "any" }; /* PICK_x */
int argc = lua_gettop(L);
const char *prompt;
const char *defval = "";
int pick = PICK_ONE, pick_cnt;
winid tmpwin;
anything any;
menu_item *picks = (menu_item *) 0;
int clr = NO_COLOR;
if (argc < 2 || argc > 4) {
nhl_error(L, "Wrong args");
/*NOTREACHED*/
return 0;
}
prompt = luaL_checkstring(L, 1);
if (lua_isstring(L, 2))
defval = luaL_checkstring(L, 2);
if (lua_isstring(L, 3))
pick = luaL_checkoption(L, 3, "one", pickX);
luaL_checktype(L, argc, LUA_TTABLE);
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin, MENU_BEHAVE_STANDARD);
lua_pushnil(L); /* first key */
while (lua_next(L, argc) != 0) {
const char *str = "";
const char *key = "";
/* key @ index -2, value @ index -1 */
if (lua_istable(L, -1)) {
lua_pushliteral(L, "key");
lua_gettable(L, -2);
key = lua_tostring(L, -1);
lua_pop(L, 1);
lua_pushliteral(L, "text");
lua_gettable(L, -2);
str = lua_tostring(L, -1);
lua_pop(L, 1);
/* TODO: glyph, attr, accel, group accel (all optional) */
} else if (lua_isstring(L, -1)) {
str = luaL_checkstring(L, -1);
key = luaL_checkstring(L, -2);
}
any = cg.zeroany;
if (*key)
any.a_char = key[0];
add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, str,
(*defval && *key && defval[0] == key[0])
? MENU_ITEMFLAGS_SELECTED
: MENU_ITEMFLAGS_NONE);
lua_pop(L, 1); /* removes 'value'; keeps 'key' for next iteration */
}
end_menu(tmpwin, prompt);
pick_cnt = select_menu(tmpwin, pick, &picks);
destroy_nhwindow(tmpwin);
if (pick_cnt > 0) {
char buf[2];
buf[0] = picks[0].item.a_char;
if (pick == PICK_ONE && pick_cnt > 1 && *defval
&& defval[0] == picks[0].item.a_char)
buf[0] = picks[1].item.a_char;
buf[1] = '\0';
lua_pushstring(L, buf);
/* TODO: pick any */
} else {
char buf[2];
buf[0] = defval[0];
buf[1] = '\0';
lua_pushstring(L, buf);
}
return 1;
}
/* text("foo\nbar\nbaz") */
static int
nhl_text(lua_State *L)
{
int argc = lua_gettop(L);
if (argc > 0) {
menu_item *picks = (menu_item *) 0;
winid tmpwin;
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin, MENU_BEHAVE_STANDARD);
while (lua_gettop(L) > 0) {
char *ostr = dupstr(luaL_checkstring(L, 1));
char *ptr, *str = ostr;
char *lstr = str + strlen(str) - 1;
do {
char *nlp = strchr(str, '\n');
if (nlp && (nlp - str) <= 76) {
ptr = nlp;
} else {
ptr = str + 76;
if (ptr > lstr)
ptr = lstr;
}
while ((ptr > str) && !(*ptr == ' ' || *ptr == '\n'))
ptr--;
*ptr = '\0';
add_menu_str(tmpwin, str);
str = ptr + 1;
} while (*str && str <= lstr);
lua_pop(L, 1);
free(ostr);
}
end_menu(tmpwin, (char *) 0);
(void) select_menu(tmpwin, PICK_NONE, &picks);
destroy_nhwindow(tmpwin);
}
return 0;
}
/* makeplural("zorkmid") */
static int
nhl_makeplural(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushstring(L, makeplural(luaL_checkstring(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* makesingular("zorkmids") */
static int
nhl_makesingular(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushstring(L, makesingular(luaL_checkstring(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* s_suffix("foo") */
static int
nhl_s_suffix(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushstring(L, s_suffix(luaL_checkstring(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* ing_suffix("foo") */
static int
nhl_ing_suffix(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushstring(L, ing_suffix(luaL_checkstring(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* an("foo") */
static int
nhl_an(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushstring(L, an(luaL_checkstring(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* rn2(10) */
static int
nhl_rn2(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushinteger(L, rn2((int) luaL_checkinteger(L, 1)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* random(10); -- is the same as rn2(10); */
/* random(5,8); -- same as 5 + rn2(8); */
static int
nhl_random(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1)
lua_pushinteger(L, rn2((int) luaL_checkinteger(L, 1)));
else if (argc == 2)
lua_pushinteger(L, luaL_checkinteger(L, 1)
+ rn2((int) luaL_checkinteger(L, 2)));
else
nhl_error(L, "Wrong args");
return 1;
}
/* level_difficulty() */
static int
nhl_level_difficulty(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 0) {
lua_pushinteger(L, level_difficulty());
} else {
nhl_error(L, "level_difficulty should not have any args");
}
return 1;
}
RESTORE_WARNING_UNREACHABLE_CODE
/* get mandatory integer value from table */
int
get_table_int(lua_State *L, const char *name)
{
int ret;
lua_getfield(L, -1, name);
ret = (int) luaL_checkinteger(L, -1);
lua_pop(L, 1);
return ret;
}
/* get optional integer value from table */
int
get_table_int_opt(lua_State *L, const char *name, int defval)
{
int ret = defval;
lua_getfield(L, -1, name);
if (!lua_isnil(L, -1)) {
ret = (int) luaL_checkinteger(L, -1);
}
lua_pop(L, 1);
return ret;
}
char *
get_table_str(lua_State *L, const char *name)
{
char *ret;
lua_getfield(L, -1, name);
ret = dupstr(luaL_checkstring(L, -1));
lua_pop(L, 1);
return ret;
}
/* get optional string value from table.
return value must be freed by caller. */
char *
get_table_str_opt(lua_State *L, const char *name, char *defval)
{
const char *ret;
lua_getfield(L, -1, name);
ret = luaL_optstring(L, -1, defval);
if (ret) {
lua_pop(L, 1);
return dupstr(ret);
}
lua_pop(L, 1);
return NULL;
}
int
get_table_boolean(lua_State *L, const char *name)
{
static const char *const boolstr[] = {
"true", "false", "yes", "no", NULL
};
/* static const int boolstr2i[] = { TRUE, FALSE, TRUE, FALSE, -1 }; */
int ltyp;
int ret = -1;
lua_getfield(L, -1, name);
ltyp = lua_type(L, -1);
if (ltyp == LUA_TSTRING) {
ret = luaL_checkoption(L, -1, NULL, boolstr);
/* nhUse(boolstr2i[0]); */
} else if (ltyp == LUA_TBOOLEAN) {
ret = lua_toboolean(L, -1);
} else if (ltyp == LUA_TNUMBER) {
ret = (int) luaL_checkinteger(L, -1);
if (ret < 0 || ret > 1)
ret = -1;
}
lua_pop(L, 1);
if (ret == -1)
nhl_error(L, "Expected a boolean");
return ret;
}
int
get_table_boolean_opt(lua_State *L, const char *name, int defval)
{
int ret = defval;
lua_getfield(L, -1, name);
if (lua_type(L, -1) != LUA_TNIL) {
lua_pop(L, 1);
return get_table_boolean(L, name);
}
lua_pop(L, 1);
return ret;
}
/* opts[] is a null-terminated list */
int
get_table_option(lua_State *L,
const char *name,
const char *defval,
const char *const opts[])
{
int ret;
lua_getfield(L, -1, name);
ret = luaL_checkoption(L, -1, defval, opts);
lua_pop(L, 1);
return ret;
}
#ifdef DUMPLOG
/* local fname = dump_fmtstr("/tmp/nethack.%n.%d.log"); */
static int
nhl_dump_fmtstr(lua_State *L)
{
int argc = lua_gettop(L);
char buf[512];
if (argc == 1)
lua_pushstring(L, dump_fmtstr(luaL_checkstring(L, 1), buf, TRUE));
else
nhl_error(L, "Expected a string parameter");
return 1;
}
#endif /* DUMPLOG */
/* local dungeon_name = dnum_name(u.dnum); */
static int
nhl_dnum_name(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
lua_Integer dnum = luaL_checkinteger(L, 1);
if (dnum >= 0 && dnum < gn.n_dgns)
lua_pushstring(L, gd.dungeons[dnum].dname);
else
lua_pushstring(L, "");
} else
nhl_error(L, "Expected an integer parameter");
return 1;
}
DISABLE_WARNING_UNREACHABLE_CODE
/* because nhl_error() does not return */
/* set or get variables which are saved and restored along with the game.
nh.variable("test", 10);
local ten = nh.variable("test"); */
static int
nhl_variable(lua_State *L)
{
int argc = lua_gettop(L);
int typ;
const char *key;
if (!gl.luacore) {
nhl_error(L, "nh luacore not inited");
/*NOTREACHED*/
return 0;
}
lua_getglobal(gl.luacore, "nh_lua_variables");
if (!lua_istable(gl.luacore, -1)) {
impossible("nh_lua_variables is not a lua table");
return 0;
}
if (argc == 1) {
key = luaL_checkstring(L, 1);
lua_getfield(gl.luacore, -1, key);
typ = lua_type(gl.luacore, -1);
if (typ == LUA_TSTRING)
lua_pushstring(L, lua_tostring(gl.luacore, -1));
else if (typ == LUA_TNIL)
lua_pushnil(L);
else if (typ == LUA_TBOOLEAN)
lua_pushboolean(L, lua_toboolean(gl.luacore, -1));
else if (typ == LUA_TNUMBER)
lua_pushinteger(L, lua_tointeger(gl.luacore, -1));
else if (typ == LUA_TTABLE) {
lua_getglobal(gl.luacore, "nh_get_variables_string");
lua_pushvalue(gl.luacore, -2);
nhl_pcall_handle(gl.luacore, 1, 1, "nhl_variable", NHLpa_panic);
luaL_loadstring(L, lua_tostring(gl.luacore, -1));
nhl_pcall_handle(L, 0, 1, "nhl_variable-1", NHLpa_panic);
} else
nhl_error(L, "Cannot get variable of that type");
return 1;
} else if (argc == 2) {
/* set nh_lua_variables[key] = value;
nh.variable("key", value); */
key = luaL_checkstring(L, 1);
//pline("SETVAR:%s", key);
typ = lua_type(L, -1);
if (typ == LUA_TSTRING) {
lua_pushstring(gl.luacore, lua_tostring(L, -1));
lua_setfield(gl.luacore, -2, key);
} else if (typ == LUA_TNIL) {
lua_pushnil(gl.luacore);
lua_setfield(gl.luacore, -2, key);
} else if (typ == LUA_TBOOLEAN) {
lua_pushboolean(gl.luacore, lua_toboolean(L, -1));
lua_setfield(gl.luacore, -2, key);
} else if (typ == LUA_TNUMBER) {
lua_pushinteger(gl.luacore, lua_tointeger(L, -1));
lua_setfield(gl.luacore, -2, key);
} else if (typ == LUA_TTABLE) {
lua_getglobal(L, "nh_set_variables_string");
lua_pushstring(L, key);
lua_pushvalue(L, -3); /* copy value to top */
nhl_pcall_handle(L, 2, 1, "nhl_variable-2", NHLpa_panic);
luaL_loadstring(gl.luacore, lua_tostring(L, -1));
nhl_pcall_handle(gl.luacore, 0, 0, "nhl_variable-3", NHLpa_panic);
} else
nhl_error(L, "Cannot set variable of that type");
return 0;
} else
nhl_error(L, "Wrong number of arguments");
return 1;
}
/* return nh_lua_variable lua table as a string */
char *
get_nh_lua_variables(void)
{
char *key = NULL;
if (!gl.luacore) {
nhl_error(gl.luacore, "nh luacore not inited");
/*NOTREACHED*/
return key;
}
lua_getglobal(gl.luacore, "nh_lua_variables");
if (!lua_istable(gl.luacore, -1)) {
impossible("nh_lua_variables is not a lua table");
return key;
}
lua_getglobal(gl.luacore, "get_variables_string");
if (lua_type(gl.luacore, -1) == LUA_TFUNCTION) {
if (nhl_pcall_handle(gl.luacore, 0, 1, "get_nh_lua_variables",
NHLpa_impossible)) {
return key;
}
key = dupstr(lua_tostring(gl.luacore, -1));
}
return key;
}
RESTORE_WARNING_UNREACHABLE_CODE
/* save nh_lua_variables table to file */
void
save_luadata(NHFILE *nhfp)
{
unsigned lua_data_len;
char *lua_data = get_nh_lua_variables(); /* note: '\0' terminated */
if (!lua_data)
lua_data = dupstr(emptystr);
lua_data_len = Strlen(lua_data) + 1; /* +1: include the terminator */
bwrite(nhfp->fd, (genericptr_t) &lua_data_len,
(unsigned) sizeof lua_data_len);
bwrite(nhfp->fd, (genericptr_t) lua_data, lua_data_len);
free(lua_data);
}
/* restore nh_lua_variables table from file */
void
restore_luadata(NHFILE *nhfp)
{
unsigned lua_data_len;
char *lua_data;
mread(nhfp->fd, (genericptr_t) &lua_data_len,
(unsigned) sizeof lua_data_len);
lua_data = (char *) alloc(lua_data_len);
mread(nhfp->fd, (genericptr_t) lua_data, lua_data_len);
if (!gl.luacore)
l_nhcore_init();
luaL_loadstring(gl.luacore, lua_data);
free(lua_data);
nhl_pcall_handle(gl.luacore, 0, 0, "restore_luadata", NHLpa_panic);
}
/* local stairs = stairways(); */
static int
nhl_stairways(lua_State *L)
{
stairway *tmp = gs.stairs;
int i = 1; /* lua arrays should start at 1 */
lua_newtable(L);
while (tmp) {
lua_pushinteger(L, i);
lua_newtable(L);
nhl_add_table_entry_bool(L, "up", tmp->up);
nhl_add_table_entry_bool(L, "ladder", tmp->isladder);
nhl_add_table_entry_int(L, "x", tmp->sx);
nhl_add_table_entry_int(L, "y", tmp->sy);
nhl_add_table_entry_int(L, "dnum", tmp->tolev.dnum);
nhl_add_table_entry_int(L, "dlevel", tmp->tolev.dlevel);
lua_settable(L, -3);
tmp = tmp->next;
i++;
}
return 1;
}
/*
test( { x = 123, y = 456 } );
*/
static int
nhl_test(lua_State *L)
{
coordxy x, y;
char *name, Player[] = "Player";
/* discard any extra arguments passed in */
lua_settop(L, 1);
luaL_checktype(L, 1, LUA_TTABLE);
x = (coordxy) get_table_int(L, "x");
y = (coordxy) get_table_int(L, "y");
name = get_table_str_opt(L, "name", Player);
pline("TEST:{ x=%i, y=%i, name=\"%s\" }", (int) x, (int) y, name);
free(name);
return 1;
}
/* push a key into command queue */
/* nh.pushkey("i"); */
static int
nhl_pushkey(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
const char *key = luaL_checkstring(L, 1);
cmdq_add_key(CQ_CANNED, key[0]);
}
return 0;
}
/* do a turn of moveloop, or until gm.multi is done if param is true. */
/* nh.doturn(); nh.doturn(true); */
static int
nhl_doturn(lua_State *L)
{
int argc = lua_gettop(L);
boolean domulti = FALSE;
if (argc == 1)
domulti = lua_toboolean(L, 1);
do {
moveloop_core();
} while (domulti && gm.multi);
return 0;
}
/* set debugging flags. debugging use only, of course. */
/* nh.debug_flags({ mongen = false,
hunger = false,
overwrite_stairs = true }); */
static int
nhl_debug_flags(lua_State *L)
{
int val;
lcheck_param_table(L);
/* disable monster generation */
val = get_table_boolean_opt(L, "mongen", -1);
if (val != -1) {
iflags.debug_mongen = !(boolean) val; /* value in lua is negated */
if (iflags.debug_mongen) {
register struct monst *mtmp, *mtmp2;
for (mtmp = fmon; mtmp; mtmp = mtmp2) {
mtmp2 = mtmp->nmon;
if (DEADMONSTER(mtmp))
continue;
mongone(mtmp);
}
}
}
/* prevent hunger */
val = get_table_boolean_opt(L, "hunger", -1);
if (val != -1) {
iflags.debug_hunger = !(boolean) val; /* value in lua is negated */
}
/* allow overwriting stairs */
val = get_table_boolean_opt(L, "overwrite_stairs", -1);
if (val != -1) {
iflags.debug_overwrite_stairs = (boolean) val;
}
return 0;
}
DISABLE_WARNING_UNREACHABLE_CODE
/* does location at x,y have timer? */
/* local has_melttimer = nh.has_timer_at(x,y, "melt-ice"); */
/* local has_melttimer = nh.has_timer_at({x=4,y=7}, "melt-ice"); */
static int
nhl_timer_has_at(lua_State *L)
{
boolean ret = FALSE;
short timertype = nhl_get_timertype(L, -1);
lua_Integer lx, ly;
coordxy x, y;
long when;
lua_pop(L, 1); /* remove timertype */
if (!nhl_get_xy_params(L, &lx, &ly)) {
nhl_error(L, "nhl_timer_has_at: Wrong args");
/*NOTREACHED*/
return 0;
}
x = (coordxy) lx;
y = (coordxy) ly;
cvt_to_abscoord(&x, &y);
if (isok(x, y)) {
when = spot_time_expires(x, y, timertype);
ret = (when > 0L);
}
lua_pushboolean(L, ret);
return 1;
}
/* when does location at x,y timer trigger? */
/* local melttime = nh.peek_timer_at(x,y, "melt-ice"); */
/* local melttime = nh.peek_timer_at({x=5,y=6}, "melt-ice"); */
static int
nhl_timer_peek_at(lua_State *L)
{
long when = 0L;
short timertype = nhl_get_timertype(L, -1);
lua_Integer lx, ly;
coordxy x, y;
lua_pop(L, 1); /* remove timertype */
if (!nhl_get_xy_params(L, &lx, &ly)) {
nhl_error(L, "nhl_timer_peek_at: Wrong args");
/*NOTREACHED*/
return 0;
}
x = (coordxy) lx;
y = (coordxy) ly;
cvt_to_abscoord(&x, &y);
if (timer_is_pos(timertype) && isok(x, y))
when = spot_time_expires(x, y, timertype);
lua_pushinteger(L, when);
return 1;
}
/* stop timer at location x,y */
/* nh.stop_timer_at(x,y, "melt-ice"); */
/* nh.stop_timer_at({x=6,y=8}, "melt-ice"); */
static int
nhl_timer_stop_at(lua_State *L)
{
short timertype = nhl_get_timertype(L, -1);
lua_Integer lx, ly;
coordxy x, y;
lua_pop(L, 1); /* remove timertype */
if (!nhl_get_xy_params(L, &lx, &ly)) {
nhl_error(L, "nhl_timer_stop_at: Wrong args");
/*NOTREACHED*/
return 0;
}
x = (coordxy) lx;
y = (coordxy) ly;
cvt_to_abscoord(&x, &y);
if (timer_is_pos(timertype) && isok(x, y))
spot_stop_timers(x, y, timertype);
return 0;
}
/* start timer at location x,y */
/* nh.start_timer_at(x,y, "melt-ice", 10); */
static int
nhl_timer_start_at(lua_State *L)
{
short timertype = nhl_get_timertype(L, -2);
long when = lua_tointeger(L, -1);
lua_Integer lx, ly;
coordxy x, y;
lua_pop(L, 2); /* remove when and timertype */
if (!nhl_get_xy_params(L, &lx, &ly)) {
nhl_error(L, "nhl_timer_start_at: Wrong args");
/*NOTREACHED*/
return 0;
}
x = (coordxy) lx;
y = (coordxy) ly;
cvt_to_abscoord(&x, &y);
if (timer_is_pos(timertype) && isok(x, y)) {
long where = ((long) x << 16) | (long) y;
spot_stop_timers(x, y, timertype);
(void) start_timer((long) when, TIMER_LEVEL, MELT_ICE_AWAY,
long_to_any(where));
}
return 0;
}
/* returns the visual interpretation of the key bound to an extended command,
or the ext cmd name if not bound to any key */
/* local helpkey = eckey("help"); */
static int
nhl_get_cmd_key(lua_State *L)
{
int argc = lua_gettop(L);
if (argc == 1) {
const char *cmd = luaL_checkstring(L, 1);
char *key = cmd_from_ecname(cmd);
lua_pushstring(L, key);
return 1;
}
return 0;
}
/* add or remove a lua function callback */
/* callback("level_enter", "function_name"); */
/* callback("level_enter", "function_name", true); */
/* level_enter, level_leave, cmd_before */
static int
nhl_callback(lua_State *L)
{
int argc = lua_gettop(L);
int i;
boolean rm;
const char *fn, *cb;
if (!gl.luacore) {
nhl_error(L, "nh luacore not inited");
/*NOTREACHED*/
return 0;
}
if (argc == 2 || argc == 3) {
if (argc == 2) {
rm = FALSE;
fn = luaL_checkstring(L, -1);
cb = luaL_checkstring(L, -2);
} else {
rm = lua_toboolean(L, -1);
fn = luaL_checkstring(L, -2);
cb = luaL_checkstring(L, -3);
}
for (i = 0; i < NUM_NHCB; i++)
if (!strcmp(cb, nhcb_name[i]))
break;
if (i >= NUM_NHCB)
return 0;
if (rm) {
nhcb_counts[i]--;
if (nhcb_counts[i] < 0)
impossible("nh.callback counts are wrong");
} else {
nhcb_counts[i]++;
}
lua_getglobal(gl.luacore, rm ? "nh_callback_rm" : "nh_callback_set");
lua_pushstring(gl.luacore, cb);
lua_pushstring(gl.luacore, fn);
nhl_pcall_handle(gl.luacore, 2, 0, "nhl_callback", NHLpa_panic);
}
return 0;
}
/* store or restore game state */
/* NOTE: doesn't work when saving/restoring the game */
/* currently handles inventory and turns. */
/* gamestate(); -- save state */
/* gamestate(true); -- restore state */
static int
nhl_gamestate(lua_State *L)
{
static struct obj *gmst_invent = NULL;
static long gmst_moves = 0;
static boolean gmst_stored = FALSE;
static struct you gmst_ubak;
long wornmask;
struct obj *otmp;
int argc = lua_gettop(L);
boolean reststate = (argc > 0) ? lua_toboolean(L, -1) : FALSE;
debugpline4("gamestate: %d:%d (%c vs %c)", u.uz.dnum, u.uz.dlevel,
reststate ? 'T' : 'F', gmst_stored ? 't' : 'f');
if (reststate && gmst_stored) {
d_level cur_uz = u.uz, cur_uz0 = u.uz0;
/* restore game state */
gm.moves = gmst_moves;
gl.lastinvnr = 51;
while (gi.invent)
useupall(gi.invent);
while ((otmp = gmst_invent) != NULL) {
wornmask = otmp->owornmask;
otmp->owornmask = 0L;
extract_nobj(otmp, &gmst_invent);
addinv_nomerge(otmp);
if (wornmask)
setworn(otmp, wornmask);
}
u = gmst_ubak;
/* some restored state would confuse the level change in progress */
u.uz = cur_uz, u.uz0 = cur_uz0;
init_uhunger();
gmst_stored = FALSE;
} else if (!reststate && !gmst_stored) {
/* store game state */
while ((otmp = gi.invent) != NULL) {
wornmask = otmp->owornmask;
setnotworn(otmp);
freeinv(otmp);
otmp->nobj = gmst_invent;
otmp->owornmask = wornmask;
gmst_invent = otmp;
}
gl.lastinvnr = 51; /* next inv letter to try to use will be 'a' */
gmst_moves = gm.moves;
gmst_ubak = u;
gmst_stored = TRUE;
} else {
impossible("nhl_gamestate: inconsistent state (%s vs %s)",
reststate ? "restore" : "save",
gmst_stored ? "already stored" : "not stored");
}
update_inventory();
return 0;
}
RESTORE_WARNING_UNREACHABLE_CODE
/* called from gotolevel(do.c) */
void
tutorial(boolean entering)
{
l_nhcore_call(entering ? NHCORE_ENTER_TUTORIAL : NHCORE_LEAVE_TUTORIAL);
if (!entering) { /* after leaving, can't go back */
nhcore_call_available[NHCORE_ENTER_TUTORIAL]
= nhcore_call_available[NHCORE_LEAVE_TUTORIAL]
= FALSE;
}
}
static const struct luaL_Reg nhl_functions[] = {
{ "test", nhl_test },
{ "getmap", nhl_getmap },
#if 0
{ "setmap", nhl_setmap },
#endif
{ "gettrap", nhl_gettrap },
{ "deltrap", nhl_deltrap },
{ "has_timer_at", nhl_timer_has_at },
{ "peek_timer_at", nhl_timer_peek_at },
{ "stop_timer_at", nhl_timer_stop_at },
{ "start_timer_at", nhl_timer_start_at },
{ "abscoord", nhl_abs_coord },
{ "impossible", nhl_impossible },
{ "pline", nhl_pline },
{ "verbalize", nhl_verbalize },
{ "menu", nhl_menu },
{ "text", nhl_text },
{ "getlin", nhl_getlin },
{ "eckey", nhl_get_cmd_key },
{ "callback", nhl_callback },
{ "gamestate", nhl_gamestate },
{ "makeplural", nhl_makeplural },
{ "makesingular", nhl_makesingular },
{ "s_suffix", nhl_s_suffix },
{ "ing_suffix", nhl_ing_suffix },
{ "an", nhl_an },
{ "rn2", nhl_rn2 },
{ "random", nhl_random },
{ "level_difficulty", nhl_level_difficulty },
{ "parse_config", nhl_parse_config },
{ "get_config", nhl_get_config },
{ "get_config_errors", l_get_config_errors },
#ifdef DUMPLOG
{ "dump_fmtstr", nhl_dump_fmtstr },
#endif /* DUMPLOG */
{ "dnum_name", nhl_dnum_name },
{ "variable", nhl_variable },
{ "stairways", nhl_stairways },
{ "pushkey", nhl_pushkey },
{ "doturn", nhl_doturn },
{ "debug_flags", nhl_debug_flags },
{ NULL, NULL }
};
static const struct {
const char *name;
long value;
} nhl_consts[] = {
{ "COLNO", COLNO },
{ "ROWNO", ROWNO },
#ifdef DLB
{ "DLB", 1 },
#else
{ "DLB", 0 },
#endif /* DLB */
{ NULL, 0 },
};
/* register and init the constants table */
static void
init_nhc_data(lua_State *L)
{
int i;
lua_newtable(L);
for (i = 0; nhl_consts[i].name; i++) {
lua_pushstring(L, nhl_consts[i].name);
lua_pushinteger(L, nhl_consts[i].value);
lua_rawset(L, -3);
}
lua_setglobal(L, "nhc");
}
static int
nhl_push_anything(lua_State *L, int anytype, void *src)
{
anything any = cg.zeroany;
switch (anytype) {
case ANY_INT:
any.a_int = *(int *) src;
lua_pushinteger(L, any.a_int);
break;
case ANY_UCHAR:
any.a_uchar = *(uchar *) src;
lua_pushinteger(L, any.a_uchar);
break;
case ANY_SCHAR:
any.a_schar = *(schar *) src;
lua_pushinteger(L, any.a_schar);
break;
}
return 1;
}
DISABLE_WARNING_UNREACHABLE_CODE
static int
nhl_meta_u_index(lua_State *L)
{
static const struct {
const char *name;
void *ptr;
int type;
} ustruct[] = {
{ "ux", &(u.ux), ANY_UCHAR },
{ "uy", &(u.uy), ANY_UCHAR },
{ "dx", &(u.dx), ANY_SCHAR },
{ "dy", &(u.dy), ANY_SCHAR },
{ "dz", &(u.dz), ANY_SCHAR },
{ "tx", &(u.tx), ANY_UCHAR },
{ "ty", &(u.ty), ANY_UCHAR },
{ "ulevel", &(u.ulevel), ANY_INT },
{ "ulevelmax", &(u.ulevelmax), ANY_INT },
{ "uhunger", &(u.uhunger), ANY_INT },
{ "nv_range", &(u.nv_range), ANY_INT },
{ "xray_range", &(u.xray_range), ANY_INT },
{ "umonster", &(u.umonster), ANY_INT },
{ "umonnum", &(u.umonnum), ANY_INT },
{ "mh", &(u.mh), ANY_INT },
{ "mhmax", &(u.mhmax), ANY_INT },
{ "mtimedone", &(u.mtimedone), ANY_INT },
{ "dlevel", &(u.uz.dlevel), ANY_SCHAR }, /* actually coordxy */
{ "dnum", &(u.uz.dnum), ANY_SCHAR }, /* actually coordxy */
{ "uluck", &(u.uluck), ANY_SCHAR },
{ "uhp", &(u.uhp), ANY_INT },
{ "uhpmax", &(u.uhpmax), ANY_INT },
{ "uen", &(u.uen), ANY_INT },
{ "uenmax", &(u.uenmax), ANY_INT },
};
const char *tkey = luaL_checkstring(L, 2);
int i;
/* FIXME: doesn't really work, eg. negative values for u.dx */
for (i = 0; i < SIZE(ustruct); i++)
if (!strcmp(tkey, ustruct[i].name)) {
return nhl_push_anything(L, ustruct[i].type, ustruct[i].ptr);
}
if (!strcmp(tkey, "inventory")) {
nhl_push_obj(L, gi.invent);
return 1;
} else if (!strcmp(tkey, "role")) {
lua_pushstring(L, gu.urole.name.m);
return 1;
} else if (!strcmp(tkey, "moves")) {
lua_pushinteger(L, gm.moves);
return 1;
} else if (!strcmp(tkey, "uhave_amulet")) {
lua_pushinteger(L, u.uhave.amulet);
return 1;
} else if (!strcmp(tkey, "depth")) {
lua_pushinteger(L, depth(&u.uz));
return 1;
} else if (!strcmp(tkey, "invocation_level")) {
lua_pushboolean(L, Invocation_lev(&u.uz));
return 1;
}
nhl_error(L, "Unknown u table index");
/*NOTREACHED*/
return 0;
}
static int
nhl_meta_u_newindex(lua_State *L)
{
nhl_error(L, "Cannot set u table values");
/*NOTREACHED*/
return 0;
}
RESTORE_WARNING_UNREACHABLE_CODE
static int
nhl_u_clear_inventory(lua_State *L UNUSED)
{
while (gi.invent)
useupall(gi.invent);
return 0;
}
/* Put object into player's inventory */
/* u.giveobj(obj.new("rock")); */
static int
nhl_u_giveobj(lua_State *L)
{
return nhl_obj_u_giveobj(L);
}
static const struct luaL_Reg nhl_u_functions[] = {
{ "clear_inventory", nhl_u_clear_inventory },
{ "giveobj", nhl_u_giveobj },
{ NULL, NULL }
};
static void
init_u_data(lua_State *L)
{
lua_newtable(L);
luaL_setfuncs(L, nhl_u_functions, 0);
lua_newtable(L);
lua_pushcfunction(L, nhl_meta_u_index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, nhl_meta_u_newindex);
lua_setfield(L, -2, "__newindex");
lua_setmetatable(L, -2);
lua_setglobal(L, "u");
}
#ifdef notyet
static int
nhl_set_package_path(lua_State *L, const char *path)
{
if (LUA_TTABLE != lua_getglobal(L, "package")) {
impossible("package not a table in nhl_set_package_path");
return 1;
};
lua_pushstring(L, path);
lua_setfield(L, -2, "path");
lua_pop(L, 1);
return 0;
}
#endif
static int
traceback_handler(lua_State *L)
{
luaL_traceback(L, L, lua_tostring(L, 1), 0);
/* TODO: call impossible() if fuzzing? */
return 1;
}
static uint32_t
nhl_getmeminuse(lua_State *L){
return lua_gc(L, LUA_GCCOUNT) * 1024 + lua_gc(L, LUA_GCCOUNTB);
}
/* lua_pcall with our traceback handler and memory and instruction step
* limiting.
* On error, traceback will be on top of stack */
int
nhl_pcall(lua_State *L, int nargs, int nresults, const char *name)
{
struct nhl_user_data *nud;
int rv;
lua_pushcfunction(L, traceback_handler);
lua_insert(L, 1);
(void) lua_getallocf(L, (void **) &nud);
#ifdef NHL_SANDBOX
if (nud && name) {
nud->name = name;
}
/* NB: We don't need to deal with nud->memlimit - Lua handles that. */
if (nud && (nud->steps || nud->perpcall)) {
if (nud->perpcall) {
nud->steps = nud->perpcall;
nud->statctr = 0;
}
if (setjmp(nud->jb)) {
/* panic, because we don't know if the game state is corrupt */
/* XXX can we get a lua stack trace as well? */
panic("Lua time exceeded %d:%s", nud->sid,
nud->name ? nud->name : "(unknown)");
}
}
#endif
rv = lua_pcall(L, nargs, nresults, 1);
lua_remove(L, 1); /* remove handler */
#ifdef NHL_SANDBOX
if (nud && nud->perpcall && gl.loglua) {
long ic = nud->statctr * NHL_SB_STEPSIZE; // an approximation
livelog_printf(LL_DEBUG, "LUASTATS PCAL %d:%s %ld", nud->sid,
nud->name, ic);
}
if (nud && nud->memlimit && gl.loglua) {
lua_gc(L, LUA_GCCOLLECT);
livelog_printf(LL_DEBUG, "LUASTATS PMEM %d:%s %lu", nud->sid,
nud->name, (long unsigned) nhl_getmeminuse(L));
}
#endif
return rv;
}
int
nhl_pcall_handle(lua_State *L, int nargs, int nresults, const char *name,
NHL_pcall_action npa)
{
int rv = nhl_pcall(L, nargs, nresults, name);
if (rv) {
nhl_user_data *nud;
(void) lua_getallocf(L, (void **) &nud);
/* XXX can we get a lua stack trace as well? */
switch (npa) {
case NHLpa_panic:
panic("Lua error %d:%s %s", nud->sid,
nud->name ? nud->name : "(unknown)", lua_tostring(L, -1));
case NHLpa_impossible:
impossible("Lua error: %d:%s %s", nud->sid,
nud->name ? nud->name : "(unknown)",
lua_tostring(L, -1));
}
}
return rv;
}
/* XXX impossible() should be swappable with pline/nothing/panic via flag */
/* read lua code/data from a dlb module or an external file
into a string buffer and feed that to lua */
boolean
nhl_loadlua(lua_State *L, const char *fname)
{
#define LOADCHUNKSIZE (1L << 13) /* 8K */
boolean ret = TRUE;
dlb *fh;
char *buf = (char *) 0, *bufin, *bufout, *p, *nl, *altfname;
long buflen, ct, cnt;
int llret;
altfname = (char *) alloc(Strlen(fname) + 3); /* 3: '('...')\0' */
/* don't know whether 'fname' is inside a dlb container;
if we did, we could choose between "nhdat(<fname>)" and "<fname>"
but since we don't, compromise */
Sprintf(altfname, "(%s)", fname);
fh = dlb_fopen(fname, RDBMODE);
if (!fh) {
impossible("nhl_loadlua: Error opening %s", altfname);
ret = FALSE;
goto give_up;
}
dlb_fseek(fh, 0L, SEEK_END);
buflen = dlb_ftell(fh);
dlb_fseek(fh, 0L, SEEK_SET);
/* extra +1: room to add final '\n' if missing */
buf = bufout = (char *) alloc(FITSint(buflen + 1 + 1));
buf[0] = '\0';
bufin = bufout = buf;
ct = 0L;
while (buflen > 0 || ct) {
/*
* Semi-arbitrarily limit reads to 8K at a time. That's big
* enough to cover the majority of our Lua files in one bite
* but small enough to fully exercise the partial record
* handling (when processing the castle's level description).
*
* [For an external file (non-DLB), VMS may only be able to
* read at most 32K-1 at a time depending on the file format
* in use, and fseek(SEEK_END) only yields an upper bound on
* the actual amount of data in that situation.]
*/
if ((cnt = dlb_fread(bufin, 1, min((int) buflen, LOADCHUNKSIZE), fh)) < 0L)
break;
buflen -= cnt; /* set up for next iteration, if any */
if (cnt == 0L) {
*bufin = '\n'; /* very last line is unterminated? */
cnt = 1;
}
bufin[cnt] = '\0'; /* fread() doesn't do this */
/* in case partial line was leftover from previous fread */
bufin -= ct, cnt += ct, ct = 0;
while (cnt > 0) {
if ((nl = strchr(bufin, '\n')) != 0) {
/* normal case, newline is present */
ct = (long) (nl - bufin + 1L); /* +1: keep the newline */
for (p = bufin; p <= nl; ++p)
*bufout++ = *bufin++;
if (*bufin == '\r')
++bufin, ++ct;
/* update for next loop iteration */
cnt -= ct;
ct = 0;
} else if (strlen(bufin) < LOADCHUNKSIZE) {
/* no newline => partial record; move unprocessed chars
to front of input buffer (bufin portion of buf[]) */
ct = cnt = (long) (eos(bufin) - bufin);
for (p = bufout; cnt > 0; --cnt)
*p++ = *bufin++;
*p = '\0';
bufin = p; /* next fread() populates buf[] starting here */
/* cnt==0 so inner loop will terminate */
} else {
/* LOADCHUNKSIZE portion of buffer already completely full */
impossible("(%s) line too long", altfname);
goto give_up;
}
}
}
*bufout = '\0';
(void) dlb_fclose(fh);
llret = luaL_loadbuffer(L, buf, strlen(buf), altfname);
if (llret != LUA_OK) {
impossible("luaL_loadbuffer: Error loading %s: %s", altfname,
lua_tostring(L, -1));
ret = FALSE;
goto give_up;
} else {
if (nhl_pcall_handle(L, 0, LUA_MULTRET, fname, NHLpa_impossible)) {
ret = FALSE;
goto give_up;
}
}
give_up:
if (altfname)
free((genericptr_t) altfname);
if (buf)
free((genericptr_t) buf);
return ret;
#undef LOADCHUNKSIZE
}
DISABLE_WARNING_CONDEXPR_IS_CONSTANT
lua_State *
nhl_init(nhl_sandbox_info *sbi)
{
/* It would be nice to import EXPECTED from each build system. XXX */
/* And it would be nice to do it only once, but it's cheap. */
#ifndef NHL_VERSION_EXPECTED
#define NHL_VERSION_EXPECTED 50406
#endif
#ifdef NHL_SANDBOX
if (NHL_VERSION_EXPECTED != LUA_VERSION_RELEASE_NUM) {
panic(
"sandbox doesn't know this Lua version: this=%d != expected=%d ",
LUA_VERSION_RELEASE_NUM, NHL_VERSION_EXPECTED);
}
#endif
lua_State *L = nhlL_newstate(sbi, "nhl_init");
iflags.in_lua = TRUE;
/* Temporary for development XXX */
/* Turn this off in config.h to disable the sandbox. */
#ifdef NHL_SANDBOX
nhlL_openlibs(L, sbi->flags);
#else
luaL_openlibs(L);
#endif
#ifdef notyet
if (sbi->flags & NHL_SB_PACKAGE) {
/* XXX Is this still needed? */
if (nhl_set_package_path(L, "./?.lua"))
return 0;
}
#endif
/* register nh -table, and functions for it */
lua_newtable(L);
luaL_setfuncs(L, nhl_functions, 0);
lua_setglobal(L, "nh");
/* init nhc -table */
init_nhc_data(L);
/* init u -table */
init_u_data(L);
l_selection_register(L);
l_register_des(L);
l_obj_register(L);
/* nhlib.lua assumes the math table exists. */
if (LUA_TTABLE != lua_getglobal(L, "math")) {
lua_newtable(L);
lua_setglobal(L, "math");
}
if (!nhl_loadlua(L, "nhlib.lua")) {
nhl_done(L);
return (lua_State *) 0;
}
return L;
}
RESTORE_WARNING_CONDEXPR_IS_CONSTANT
void
nhl_done(lua_State *L)
{
if (L) {
nhl_user_data *nud = 0;
(void) lua_getallocf(L, (void **) &nud);
if (gl.loglua) {
if (nud && nud->osteps) {
long ic = nud->statctr * NHL_SB_STEPSIZE; // an approximation
livelog_printf(LL_DEBUG, "LUASTATS DONE %d:%s %ld", nud->sid,
nud->name, ic);
}
if (nud && nud->memlimit && !nud->perpcall) {
lua_gc(L, LUA_GCCOLLECT);
livelog_printf(LL_DEBUG, "LUASTATS DMEM %d:%s %lu", nud->sid,
nud->name, (long unsigned) nhl_getmeminuse(L));
}
}
lua_close(L);
if (nud)
nhl_alloc(NULL, nud, 0, 0); // free nud
}
iflags.in_lua = FALSE;
}
boolean
load_lua(const char *name, nhl_sandbox_info *sbi)
{
boolean ret = TRUE;
lua_State *L = nhl_init(sbi);
if (!L) {
ret = FALSE;
goto give_up;
}
if (!nhl_loadlua(L, name)) {
ret = FALSE;
goto give_up;
}
give_up:
nhl_done(L);
return ret;
}
DISABLE_WARNING_CONDEXPR_IS_CONSTANT
const char *
get_lua_version(void)
{
nhl_sandbox_info sbi = { NHL_SB_VERSION, 1 * 1024 * 1024, 0,
1 * 1024 * 1024 };
if (gl.lua_ver[0] == 0) {
lua_State *L = nhl_init(&sbi);
if (L) {
size_t len = 0;
const char *vs = (const char *) 0;
/* LUA_VERSION yields "<major>.<minor>" although we check to see
whether it is "Lua-<major>.<minor>" and strip prefix if so;
LUA_RELEASE is <LUA_VERSION>.<LUA_VERSION_RELEASE> but doesn't
get set up as a lua global */
lua_getglobal(L, "_RELEASE");
if (lua_isstring(L, -1))
vs = lua_tolstring(L, -1, &len);
#ifdef LUA_RELEASE
else
vs = LUA_RELEASE, len = strlen(vs);
#endif
if (!vs) {
lua_getglobal(L, "_VERSION");
if (lua_isstring(L, -1))
vs = lua_tolstring(L, -1, &len);
#ifdef LUA_VERSION
else
vs = LUA_VERSION, len = strlen(vs);
#endif
}
if (vs && len < sizeof gl.lua_ver) {
if (!strncmpi(vs, "Lua", 3)) {
vs += 3;
if (*vs == '-' || *vs == ' ')
vs += 1;
}
Strcpy(gl.lua_ver, vs);
}
}
nhl_done(L);
#ifdef LUA_COPYRIGHT
if (sizeof LUA_COPYRIGHT <= sizeof gl.lua_copyright)
Strcpy(gl.lua_copyright, LUA_COPYRIGHT);
#endif
}
return (const char *) gl.lua_ver;
}
RESTORE_WARNING_CONDEXPR_IS_CONSTANT
/***
*** SANDBOX / HARDENING CODE
***/
/*
* Tracing the sandbox:
* 1. Make sure CHRONICLE and LIVELOG are defined in config.h.
* 2. Rebuild the game.
* 3. Run the game and do whatever you want to check with lua.
* 4. Find the logged information in livelog.
*
* Logging format in livelog:
* <livelog leader>message=LUASTATS <TYPE> <SID>:<TAG> <DATA>
* where:
* TYPE
* DONE rough number of step taken during the life of the VM
* DMEM memory in use at destruction of VM
* PCAL rough number of steps during lua_pcall
* PMEM memory in use after lua_pcall returns
* SID a small integer identifying the Lua VM instance
* TAG a string from the nhl_luapcall call
* DATA memory: rough number of bytes in use by the VM
* steps: rough number of steps by the VM(see NHL_SB_STEPSIZE)
*/
#ifdef NHL_SANDBOX
enum ewhen {NEVER, IFFLAG, EOT};
struct e {
enum ewhen when;
const char *fnname;
};
/* NHL_BASE_BASE - safe things */
static struct e ct_base_base[] = {
{ IFFLAG, "ipairs" },
{ IFFLAG, "next" },
{ IFFLAG, "pairs" },
{ IFFLAG, "pcall" },
{ IFFLAG, "select" },
{ IFFLAG, "tonumber" },
{ IFFLAG, "tostring" },
{ IFFLAG, "type" },
{ IFFLAG, "xpcall" },
{ EOT, NULL }
};
/* NHL_BASE_ERROR - not really safe - might not want Lua to kill the process */
static struct e ct_base_error[] = {
{ IFFLAG, "assert" }, /* ok, calls error */
{ IFFLAG, "error" }, /* ok, calls G->panic */
{ NEVER, "print" }, /*not ok - calls lua_writestring/lua_writeline -> stdout*/
{ NEVER, "warn" }, /*not ok - calls lua_writestringerror -> stderr*/
{ EOT, NULL }
};
/* NHL_BASE_META - metatable access */
static struct e ct_base_meta[] = {
{ IFFLAG, "getmetatable" },
{ IFFLAG, "rawequal" },
{ IFFLAG, "rawget" },
{ IFFLAG, "rawlen" },
{ IFFLAG, "rawset" },
{ IFFLAG, "setmetatable" },
{ EOT, NULL }
};
/* NHL_BASE_GC - questionable safety */
static struct e ct_base_iffy[] = {
{ IFFLAG, "collectgarbage" },
{ EOT, NULL }
};
/* NHL_BASE_UNSAFE - include only if required */
/* TODO: if NHL_BASE_UNSAFE is ever used, we need to wrap lua_load with
* something to forbid mode=="b" */
static struct e ct_base_unsafe[] = {
{ IFFLAG, "dofile" },
{ IFFLAG, "loadfile" },
{ IFFLAG, "load" },
{ EOT, NULL }
};
/* no ct_co_ tables; all fns at same level of concern */
/* no ct_string_ tables; all fns at same level of concern */
/* no ct_table_ tables; all fns at same level of concern (but
sort can take a lot of time and can't be caught by the step limit */
/* no ct_utf8_ tables; all fns at same level of concern */
/* possible ct_debug tables - likely to need changes */
static struct e ct_debug_debug[] = {
{ NEVER, "debug" }, /* uses normal I/O so needs re-write */
{ IFFLAG, "getuservalue" },
{ NEVER, "gethook" }, /* see sethook */
{ IFFLAG, "getinfo" },
{ IFFLAG, "getlocal" },
{ IFFLAG, "getregistry" },
{ IFFLAG, "getmetatable" },
{ IFFLAG, "getupvalue" },
{ IFFLAG, "upvaluejoin" },
{ IFFLAG, "upvalueid" },
{ IFFLAG, "setuservalue" },
{ NEVER, "sethook" }, /* used for memory and step limits */
{ IFFLAG, "setlocal" },
{ IFFLAG, "setmetatable" },
{ IFFLAG, "setupvalue" },
{ IFFLAG, "setcstacklimit" },
{ EOT, NULL }
};
static struct e ct_debug_safe[] = {
{ IFFLAG, "traceback" },
{ EOT, NULL }
};
/* possible ct_os_ tables */
static struct e ct_os_time[] = {
{ IFFLAG, "clock" }, /* is this portable? XXX */
{ IFFLAG, "date" },
{ IFFLAG, "difftime" },
{ IFFLAG, "time" },
{ EOT, NULL }
};
static struct e ct_os_files[] = {
{ NEVER, "execute" }, /* not portable */
{ NEVER, "exit" },
{ NEVER, "getenv" },
{ IFFLAG, "remove" },
{ IFFLAG, "rename" },
{ NEVER, "setlocale" },
{ NEVER, "tmpname" },
{ EOT, NULL }
};
#define DROPIF(flag, lib, ct) \
nhl_clearfromtable(L, !!(lflags & flag), lib, ct)
static void
nhl_clearfromtable(lua_State *L, int flag, int tndx, struct e *todo)
{
while (todo->when != EOT) {
lua_pushnil(L);
/* if we load the library at all, NEVER items must be erased
* and IFFLAG items should be erased if !flag */
if (todo->when == NEVER || !flag) {
lua_setfield(L, tndx, todo->fnname);
}
todo++;
}
}
#endif
/*
XXX
registry["org.nethack.nethack.sb.fs"][N]=
CODEOBJECT
{
modepat: PATTERN,
filepat: PATTERN
}
CODEOBJECT
if string then if pcall(string,mode, dir, file)
if table then if mode matches pattern and filepat ma....
or do we use a real regex engine? (which we don't have and I just
argued against adding)
return values from "call it":
accept - file access granted
reject - file access denied
continue - try next element
fail - error. deny and call impossible/panic
*/
/* stack indexes:
* -1 table to index with ename
* params file
* params+1 [mode]
*/
/*
* Problem: NetHack doesn't have a regex engine and Lua doesn't give
* C access to pattern matching. There are 3 poor solutions:
* 1. Import ~5K lines of code in a dozen files from FreeBSD. (Upside - we
* could use it in other places in NetHack.)
* 2. Hack up lstrlib.c to give C direct access to the pattern matching code.
* 3. Create a Lua state just to do pattern matching.
* We're going to do #3.
*/
#ifdef notyet
static boolean
start_luapat(void)
{
int rv;
/* XXX set memory and step limits */
nhl_sandbox_info sbi = { NHL_SB_STRING, 0, 0, 0 };
if ((luapat = nhl_init(&sbi)) == NULL)
return FALSE;
/* load a pattern matching function */
rv = luaL_loadstring(
luapat,
"function matches(s,p) return not not stringm.match(s,p) end");
if (rv != LUA_OK) {
panic("start_luapat: %d", rv);
}
return TRUE;
}
#endif
static void
end_luapat(void)
{
if (luapat) {
lua_close(luapat);
luapat = NULL;
}
}
#ifdef notyet
static int
opencheckpat(lua_State *L, const char *ename, int param)
{
/* careful - we're using 2 different and unrelated Lua states */
const char *string;
int rv;
lua_pushliteral(luapat, "matches"); /* function -0,+1 */
string = lua_tolstring(L, param, NULL); /* mode or filename -0,+0 */
lua_pushstring(luapat, string); /* -0,+1 */
(void) lua_getfield(L, -1, ename); /* pattern -0,+1 */
lua_pop(L, 1); /* -1,+0 */
string = lua_tolstring(L, -1, NULL); /* -0,+0 */
lua_pushstring(luapat, string); /* -0,+1 */
if (nhl_pcall(luapat, 2, 1)) { /* -3,+1 */
/* impossible("access check internal error"); */
return NHL_SBRV_FAIL;
}
rv = lua_toboolean(luapat, -1); /* -0,+0 */
#if 0
if (lua_resetthread(luapat) != LUA_OK)
return NHL_SBRV_FAIL;
is pop sufficient? XXX or wrong - look at the balance
#else
lua_pop(luapat, 1); /* -1,+0 */
#endif
return rv ? NHL_SBRV_ACCEPT : NHL_SBRV_DENY;
}
#endif
/* put the table open uses to check its arguments on the top of the stack,
* creating it if needed
*/
#define HOOKTBLNAME "org.nethack.nethack.sb.fs"
#ifdef notyet
static int (*io_open)(lua_State *) = NULL; /* XXX this may have to be in g TBD */
#endif
void
nhl_pushhooked_open_table(lua_State *L)
{
int hot = lua_getfield(L, LUA_REGISTRYINDEX, HOOKTBLNAME);
if (hot == LUA_TNONE) {
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setfield(L, LUA_REGISTRYINDEX, HOOKTBLNAME);
}
}
#ifdef notyet
static int
hooked_open(lua_State *L)
{
const char *mode;
static boolean never = TRUE;
const char *filename;
int params;
int hot;
if (never) {
if (!start_luapat())
return NHL_SBRV_FAIL;
never = FALSE;
}
filename = luaL_checkstring(L, 1);
/* Unlike io.open, we want to treat mode as non-optional. */
if (lua_gettop(L) < 2) {
lua_pushstring(L, "r");
}
mode = luaL_optstring(L, 2, "r");
/* sandbox checks */
/* Do we need some ud from the calling state to let this be different for
each call without redoing the HO table?? Maybe for version 2. XXX */
params = lua_gettop(L)-1; /* point at first param */
nhl_pushhooked_open_table(L);
hot = lua_gettop(L);
if (lua_type(L, hot) == LUA_TTABLE) {
int idx;
for (idx = 1;
lua_pushinteger(L, idx),
lua_geti(L, hot, idx),
!lua_isnoneornil(L, -1);
++idx) {
/* top of stack is our configtbl[idx] */
switch (lua_type(L, -1)) {
/* lots of options to expand this with other types XXX */
case LUA_TTABLE: {
int moderv, filerv;
moderv = opencheckpat(L, "modepat", params+1);
if (moderv == NHL_SBRV_FAIL)
return moderv;
filerv = opencheckpat(L, "filepat", params);
if (filerv == NHL_SBRV_FAIL)
return moderv;
if (filerv == moderv) {
if (filerv == NHL_SBRV_DENY)
return NHL_SBRV_DENY;
if (filerv == NHL_SBRV_ACCEPT)
goto doopen;
}
break; /* try next entry */
}
default:
return NHL_SBRV_FAIL;
}
}
} else
return NHL_SBRV_DENY; /* default to "no" */
doopen:
lua_settop(L, params + 1);
return (*io_open)(L);
}
static boolean
hook_open(lua_State *L)
{
boolean rv = FALSE;
if (!io_open) {
int tos = lua_gettop(L);
lua_pushglobaltable(L);
if (lua_getfield(L, -1, "io") != LUA_TTABLE)
goto out;
lua_getfield(L, -1, "open");
/* The only way this can happen is if someone is messing with us,
* and I'm not sure even that is possible. */
if (!lua_iscfunction(L, -1))
goto out;
/* XXX This is fragile: C11 says casting func* to void*
* doesn't have to work, but POSIX says it does. So it
* _should_ work everywhere but all we can do without messing
* around inside Lua is to try to keep the compiler quiet. */
io_open = (int (*)(lua_State *)) lua_topointer(L, -1);
lua_pushcfunction(L, hooked_open);
lua_setfield(L, -1, "open");
rv = TRUE;
out:
lua_settop(L, tos);
}
return rv;
}
#endif
DISABLE_WARNING_CONDEXPR_IS_CONSTANT
#ifdef NHL_SANDBOX
static void
nhlL_openlibs(lua_State *L, uint32_t lflags)
{
/* translate lflags from user-friendly to internal */
if (NHL_SB_DEBUGGING & lflags) {
lflags |= NHL_SB_DB_SAFE;
}
/* only for debugging the sandbox integration */
if (NHL_SB_ALL & lflags) {
lflags = -1;
} else if (NHL_SB_SAFE & lflags) {
lflags |= NHL_SB_BASE_BASE;
lflags |= NHL_SB_COROUTINE;
lflags |= NHL_SB_TABLE;
lflags |= NHL_SB_STRING;
lflags |= NHL_SB_MATH;
lflags |= NHL_SB_UTF8;
} else if (NHL_SB_VERSION) {
lflags |= NHL_SB_BASE_BASE;
}
#ifdef notyet
/* Handling I/O is complex, so it's not available yet. I'll
finish it if and when we need it. (keni)
- hooked open; array of tuples of (r/w/rw/a/etc, directory pat, file pat)
{"close", io_close}, but with no args closes default output, so needs hook
{"flush", io_flush},
{"lines", io_lines}, hook due to filename
{"open", io_open}, hooked version:
only safe if mode not present or == "r"
or WRITEIO
only safe if path has no slashes
XXX probably need to be: matches port-specific list of paths
WRITEIO needs a different list
dlb integration?
may need to #define l_getc (but that wouldn't hook core)
may need to #define fopen/fread/fwrite/feof/ftell (etc?)
ugh: lauxlib.c uses getc() below luaL_loadfilex
override in lua.h?
ugh: liolib.c uses getc() below g_read->test_eof
override in lua.h?
{"read", io_read},
{"type", io_type},
{"input", io_input}, safe with a complex hook, but may be needed for read?
WRITEIO: needs changes to hooked open?
{"output", io_output}, do we want to allow access to default output?
{"write", io_write},
UNSAFEIO:
{"popen", io_popen},
{"tmpfile", io_tmpfile},
*/
#endif
if (lflags & NHL_SB_BASEMASK) {
int baselib;
/* load the entire library ... */
luaL_requiref(L, LUA_GNAME, luaopen_base, 1);
baselib = lua_gettop(L);
/* ... and remove anything unsupported or not requested */
DROPIF(NHL_SB_BASE_BASE, baselib, ct_base_base);
DROPIF(NHL_SB_BASE_ERROR, baselib, ct_base_error);
DROPIF(NHL_SB_BASE_META, baselib, ct_base_meta);
DROPIF(NHL_SB_BASE_GC, baselib, ct_base_iffy);
DROPIF(NHL_SB_BASE_UNSAFE, baselib, ct_base_unsafe);
lua_pop(L, 1);
}
if (lflags & NHL_SB_COROUTINE) {
luaL_requiref(L, LUA_COLIBNAME, luaopen_coroutine, 1);
lua_pop(L, 1);
}
if (lflags & NHL_SB_TABLE) {
luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1);
lua_pop(L, 1);
}
#ifdef notyet
if (lflags & NHL_SB_IO) {
luaL_requiref(L, LUA_IOLIBNAME, luaopen_io, 1);
lua_pop(L, 1);
if (!hook_open(L))
panic("can't hook io.open");
}
#endif
if (lflags & NHL_SB_OSMASK) {
int oslib;
luaL_requiref(L, LUA_OSLIBNAME, luaopen_os, 1);
oslib = lua_gettop(L);
DROPIF(NHL_SB_OS_TIME, oslib, ct_os_time);
DROPIF(NHL_SB_OS_FILES, oslib, ct_os_files);
lua_pop(L, 1);
}
if (lflags & NHL_SB_STRING) {
luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1);
lua_pop(L, 1);
}
if (lflags & NHL_SB_MATH) {
luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1);
/* XXX Note that math.random uses Lua's built-in xoshiro256**
* algorithm regardless of what the rest of the game uses.
* Fixing this would require changing lmathlib.c. */
lua_pop(L, 1);
}
if (lflags & NHL_SB_UTF8) {
luaL_requiref(L, LUA_UTF8LIBNAME, luaopen_utf8, 1);
lua_pop(L, 1);
}
if (lflags & NHL_SB_DBMASK) {
int dblib;
luaL_requiref(L, LUA_DBLIBNAME, luaopen_debug, 1);
dblib = lua_gettop(L);
DROPIF(NHL_SB_DB_DB, dblib, ct_debug_debug);
DROPIF(NHL_SB_DB_SAFE, dblib, ct_debug_safe);
lua_pop(L, 1);
}
}
#endif
RESTORE_WARNING_CONDEXPR_IS_CONSTANT
static void *
nhl_alloc(void *ud, void *ptr, size_t osize UNUSED, size_t nsize)
{
nhl_user_data *nud = ud;
if (nsize == 0) {
if (ptr != NULL) {
free(ptr);
}
return NULL;
}
/* Check nud->L because it will be NULL during state init. */
if (nud && nud->L && nud->memlimit) { /* this state is size limited */
if (nhl_getmeminuse(nud->L) > nud->memlimit)
return NULL;
}
return re_alloc(ptr, nsize);
}
DISABLE_WARNING_UNREACHABLE_CODE
static int
nhl_panic(lua_State *L)
{
const char *msg = lua_tostring(L, -1);
if (msg == NULL)
msg = "error object is not a string";
panic("unprotected error in call to Lua API (%s)\n", msg);
/*NOTREACHED*/
return 0; /* return to Lua to abort */
}
RESTORE_WARNING_UNREACHABLE_CODE
/* called when lua issues a warning message; the text of the message
is passed to us in pieces across multiple function calls */
static void
nhl_warn(
void *userdata UNUSED,
const char *msg_fragment,
int to_be_continued) /* 0: last fragment; 1: more to come */
{
size_t fraglen, buflen = strlen(gl.lua_warnbuf);
if (msg_fragment && buflen < sizeof gl.lua_warnbuf - 1) {
fraglen = strlen(msg_fragment);
if (buflen + fraglen > sizeof gl.lua_warnbuf - 1)
fraglen = sizeof gl.lua_warnbuf - 1 - buflen;
(void) strncat(gl.lua_warnbuf, msg_fragment, fraglen);
}
if (!to_be_continued) {
paniclog("[lua]", gl.lua_warnbuf);
gl.lua_warnbuf[0] = '\0';
}
}
#ifdef NHL_SANDBOX
static void
nhl_hookfn(lua_State *L, lua_Debug *ar UNUSED)
{
nhl_user_data *nud;
(void) lua_getallocf(L, (void **) &nud);
if (nud->steps <= NHL_SB_STEPSIZE)
longjmp(nud->jb, 1);
nud->steps -= NHL_SB_STEPSIZE;
nud->statctr++;
}
#endif
static lua_State *
nhlL_newstate(nhl_sandbox_info *sbi, const char *name)
{
nhl_user_data *nud = 0;
if (sbi->memlimit || sbi->steps || sbi->perpcall) {
nud = nhl_alloc(NULL, NULL, 0, sizeof (struct nhl_user_data));
if (!nud)
return 0;
nud->L = NULL;
nud->memlimit = sbi->memlimit;
nud->perpcall = 0; /* set up below, if needed */
nud->steps = 0;
nud->osteps = 0;
nud->flags = sbi->flags; /* save reporting flags */
nud->statctr = 0;
if (name) {
nud->name = name;
}
nud->sid = ++gl.lua_sid;
}
lua_State *L = lua_newstate(nhl_alloc, nud);
if (!L)
panic("NULL lua_newstate");
if(nud) nud->L = L;
lua_atpanic(L, nhl_panic);
#if LUA_VERSION_NUM == 504
lua_setwarnf(L, nhl_warn, L);
#endif
#ifdef NHL_SANDBOX
if (nud && (sbi->steps || sbi->perpcall)) {
if (sbi->steps && sbi->perpcall)
impossible("steps and perpcall both non-zero");
if (sbi->perpcall) {
nud->perpcall = sbi->perpcall;
} else {
nud->steps = sbi->steps;
nud->osteps = sbi->steps;
}
lua_sethook(L, nhl_hookfn, LUA_MASKCOUNT, NHL_SB_STEPSIZE);
}
#endif
return L;
}
/*
(See end of comment for conclusion.)
to make packages safe, we need something like:
if setuid/setgid (but does NH drop privs before we can check? TBD)
unsetenv LUA_CPATH, LUA_CPATH_5_4 (and this needs to change with
version) maybe more
luaopen_package calls getenv
unsetenv(LUA_PATH_VAR)
unsetenv(LUA_CPATH_VAR)
unsetenv(LUA_PATH_VAR LUA_VERSUFFIX)
unsetenv(LUA_CPATH_VAR LUA_VERSUFFIX)
package.config
oackage[fieldname] = path
NB: LUA_PATH_DEFAULT and LUA_CPATH_DEFAULT must be safe
or we must setenv LUA_PATH_VAR and LUA_CPATH_VAR to something
safe
or we could just clean out the searchers table?
package.searchers[preload,Lua,C,Croot]
also, can setting package.path to something odd get Lua to load files
it shouldn't? (see docs package.searchers)
set (and disallow changing) package.cpath (etc?)
loadlib.c:
lsys_load -> dlopen Kill with undef LUA_USE_DLOPEN LUA_DL_DLL
searchpath -> readable -> fopen
<- ll_searchpath
<- findfile <- {searchers C, Croot, Lua}
Probably the best thing to do is replace G.require with our own function
that does whatever it is we need and completely ignore the package library.
*/
/*
TODO:
docs
unfinished functionality & design
commit, cleanup, commit with SHA1 of full code version
BUT how do we compact the current history?
new branch, then compress there
XXX
*/
/*nhlua.c*/