gcc has recognized various "magic comments" for white-listing
occurrences of implicit fallthrough in switch statements for
a long time:
The range and shape of "falls through" comments accepted are
contingent upon the level of the warning. (The default level is =3.)
-Wimplicit-fallthrough=0 disables the warning altogether.
-Wimplicit-fallthrough=1 treats any kind of comment as a "falls through" comment.
-Wimplicit-fallthrough=2 essentially accepts any comment that contains something
that matches (case insensitively) "falls?[ \t-]*thr(ough|u)" regular expression.
-Wimplicit-fallthrough=3 case sensitively matches a wide range of regular
expressions, listed in the GCC manual. E.g., all of these are accepted:
/* Falls through. */
/* fall-thru */
/* Else falls through. */
/* FALLTHRU */
/* ... falls through ... */
etc.
-Wimplicit-fallthrough=4 also, case sensitively matches a range of regular
expressions but is much more strict than level =3.
-Wimplicit-fallthrough=5 doesn't recognize any comments.
Plenty of other compilers did not recognize the gcc comment convention,
and up until now the compiler warning for detecting unintended
fallthrough had to be suppressed on other compilers. That's because the code
in NetHack has been relying on the gcc approach, and only the gcc approach.
The C23 standard introduces an attribute [[fallthrough]] for the
functionality, when implicit fallthrough warnings have been enabled.
Several popular compilers already support that, or a very similar attribute
style approach, today, even ahead of their C23 support:
C compiler whitelist approach
--------------------------- -------------------------------------
C23 conforming compilers [[fallthrough]]
clang versions supporting
standards prior to
C23 __attribute__((__fallthrough__))
Microsoft Visual Studio
since VS 2022 17.4.
The warning C5262 controls
whether the implict
fallthrough is detected and
warned about with
/std:clatest. [[fallthrough]]
This adds support to NetHack for the attribute approach by inserting a
macro FALLTHROUGH to the existing cases that require white-listing, so
other compilers can analyze things too.
The definition of the FALLTHROUGH macro is controlled in include/tradstdc.h.
The gcc comment approach has also been left in place at this time.
2961 lines
81 KiB
C
2961 lines
81 KiB
C
/* NetHack 3.7 nhlua.c $NHDT-Date: 1711034373 2024/03/21 15:19:33 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.141 $ */
|
|
/* 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>
|
|
*/
|
|
|
|
/* */
|
|
|
|
struct e;
|
|
|
|
/* lua_CFunction prototypes */
|
|
#ifdef DUMPLOG
|
|
staticfn int nhl_dump_fmtstr(lua_State *);
|
|
#endif /* DUMPLOG */
|
|
staticfn int nhl_dnum_name(lua_State *);
|
|
staticfn int nhl_stairways(lua_State *);
|
|
staticfn int nhl_pushkey(lua_State *);
|
|
staticfn int nhl_doturn(lua_State *);
|
|
staticfn int nhl_debug_flags(lua_State *);
|
|
staticfn int nhl_timer_has_at(lua_State *);
|
|
staticfn int nhl_timer_peek_at(lua_State *);
|
|
staticfn int nhl_timer_stop_at(lua_State *);
|
|
staticfn int nhl_timer_start_at(lua_State *);
|
|
staticfn int nhl_get_cmd_key(lua_State *);
|
|
staticfn int nhl_callback(lua_State *);
|
|
staticfn int nhl_gamestate(lua_State *);
|
|
staticfn int nhl_test(lua_State *);
|
|
staticfn int nhl_getmap(lua_State *);
|
|
staticfn char splev_typ2chr(schar);
|
|
staticfn int nhl_gettrap(lua_State *);
|
|
staticfn int nhl_deltrap(lua_State *);
|
|
#if 0
|
|
staticfn int nhl_setmap(lua_State *);
|
|
#endif
|
|
staticfn int nhl_impossible(lua_State *);
|
|
staticfn int nhl_pline(lua_State *);
|
|
staticfn int nhl_verbalize(lua_State *);
|
|
staticfn int nhl_parse_config(lua_State *);
|
|
staticfn int nhl_menu(lua_State *);
|
|
staticfn int nhl_text(lua_State *);
|
|
staticfn int nhl_getlin(lua_State *);
|
|
staticfn int nhl_makeplural(lua_State *);
|
|
staticfn int nhl_makesingular(lua_State *);
|
|
staticfn int nhl_s_suffix(lua_State *);
|
|
staticfn int nhl_ing_suffix(lua_State *);
|
|
staticfn int nhl_an(lua_State *);
|
|
staticfn int nhl_rn2(lua_State *);
|
|
staticfn int nhl_random(lua_State *);
|
|
staticfn int nhl_level_difficulty(lua_State *);
|
|
staticfn void init_nhc_data(lua_State *);
|
|
staticfn int nhl_push_anything(lua_State *, int, void *);
|
|
staticfn int nhl_meta_u_index(lua_State *);
|
|
staticfn int nhl_meta_u_newindex(lua_State *);
|
|
staticfn int nhl_u_clear_inventory(lua_State *);
|
|
staticfn int nhl_u_giveobj(lua_State *);
|
|
staticfn void init_u_data(lua_State *);
|
|
#ifdef notyet
|
|
staticfn int nhl_set_package_path(lua_State *, const char *);
|
|
#endif
|
|
staticfn int traceback_handler(lua_State *);
|
|
staticfn uint32_t nhl_getmeminuse(lua_State *);
|
|
#ifdef NHL_SANDBOX
|
|
staticfn void nhlL_openlibs(lua_State *, uint32_t);
|
|
#endif
|
|
staticfn void *nhl_alloc(void *, void *, size_t, size_t);
|
|
staticfn lua_State *nhlL_newstate (nhl_sandbox_info *, const char *);
|
|
staticfn void end_luapat(void);
|
|
staticfn int nhl_get_config(lua_State *);
|
|
staticfn int nhl_variable(lua_State *);
|
|
staticfn void nhl_clearfromtable(lua_State *, int, int, struct e *);
|
|
staticfn void nhl_warn(void *, const char *, int);
|
|
staticfn void nhl_clearfromtable(lua_State *, int, int, struct e *);
|
|
staticfn int nhl_panic(lua_State *);
|
|
staticfn void nhl_hookfn(lua_State *, lua_Debug *);
|
|
|
|
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) {
|
|
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. */
|
|
static 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;
|
|
}
|
|
|
|
staticfn 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 }); */
|
|
staticfn 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 }); */
|
|
staticfn 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 }); */
|
|
staticfn 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!") */
|
|
staticfn 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) */
|
|
staticfn 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!") */
|
|
staticfn 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") */
|
|
staticfn 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"); */
|
|
staticfn 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?");
|
|
*/
|
|
staticfn 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"},... })
|
|
*/
|
|
staticfn 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") */
|
|
staticfn 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") */
|
|
staticfn 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") */
|
|
staticfn 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") */
|
|
staticfn 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") */
|
|
staticfn 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") */
|
|
staticfn 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) */
|
|
staticfn 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); */
|
|
staticfn 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() */
|
|
staticfn 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"); */
|
|
staticfn 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); */
|
|
staticfn 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 < svn.n_dgns)
|
|
lua_pushstring(L, svd.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"); */
|
|
staticfn int
|
|
nhl_variable(lua_State *L)
|
|
{
|
|
int argc = lua_gettop(L);
|
|
int typ;
|
|
const char *key;
|
|
|
|
if (!gl.luacore) {
|
|
panic("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) {
|
|
panic("nh luacore not inited");
|
|
/*NOTREACHED*/
|
|
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));
|
|
}
|
|
lua_pop(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(); */
|
|
staticfn 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 } );
|
|
*/
|
|
staticfn 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"); */
|
|
staticfn 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); */
|
|
staticfn 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 }); */
|
|
staticfn 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) {
|
|
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"); */
|
|
staticfn 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"); */
|
|
staticfn 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"); */
|
|
staticfn 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); */
|
|
staticfn 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"); */
|
|
staticfn 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 */
|
|
staticfn int
|
|
nhl_callback(lua_State *L)
|
|
{
|
|
int argc = lua_gettop(L);
|
|
int i;
|
|
boolean rm;
|
|
const char *fn, *cb;
|
|
|
|
if (!gl.luacore) {
|
|
panic("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 */
|
|
/*
|
|
* Currently handles
|
|
* turn counter,
|
|
* hero inventory and hunger,
|
|
* hero attributes and skills and conducts (all via 'struct u'),
|
|
* object discoveries,
|
|
* monster generation and vanquished statistics.
|
|
* NOTE: wouldn't work after restore if game has been saved, so the
|
|
* #save command ('S') is disabled during the tutorial.
|
|
*/
|
|
/* gamestate(); -- save state */
|
|
/* gamestate(true); -- restore state */
|
|
staticfn int
|
|
nhl_gamestate(lua_State *L)
|
|
{
|
|
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', gg.gmst_stored ? 't' : 'f');
|
|
|
|
if (reststate && gg.gmst_stored) {
|
|
d_level cur_uz = u.uz, cur_uz0 = u.uz0;
|
|
|
|
/* restore game state */
|
|
svm.moves = gg.gmst_moves;
|
|
pline("Resetting time to move #%ld.", svm.moves);
|
|
gg.gmst_moves = 0L;
|
|
|
|
gl.lastinvnr = 51;
|
|
while (gi.invent)
|
|
useupall(gi.invent);
|
|
while ((otmp = gg.gmst_invent) != NULL) {
|
|
wornmask = otmp->owornmask;
|
|
otmp->owornmask = 0L;
|
|
extract_nobj(otmp, &gg.gmst_invent);
|
|
addinv_nomerge(otmp);
|
|
if (wornmask)
|
|
setworn(otmp, wornmask);
|
|
}
|
|
assert(gg.gmst_ubak != NULL);
|
|
(void) memcpy((genericptr_t) &u, gg.gmst_ubak, sizeof u);
|
|
assert(gg.gmst_disco != NULL);
|
|
(void) memcpy((genericptr_t) &svd.disco, gg.gmst_disco,
|
|
sizeof svd.disco);
|
|
assert(gg.gmst_mvitals != NULL);
|
|
(void) memcpy((genericptr_t) &svm.mvitals, gg.gmst_mvitals,
|
|
sizeof svm.mvitals);
|
|
/* some restored state would confuse the level change in progress */
|
|
u.uz = cur_uz, u.uz0 = cur_uz0;
|
|
init_uhunger();
|
|
free_tutorial(); /* release gg.gmst_XYZ */
|
|
gg.gmst_stored = FALSE;
|
|
} else if (!reststate && !gg.gmst_stored) {
|
|
/* store game state */
|
|
gg.gmst_moves = svm.moves;
|
|
while ((otmp = gi.invent) != NULL) {
|
|
wornmask = otmp->owornmask;
|
|
setnotworn(otmp);
|
|
freeinv(otmp);
|
|
otmp->owornmask = wornmask; /* flag for later restore */
|
|
otmp->nobj = gg.gmst_invent;
|
|
gg.gmst_invent = otmp;
|
|
}
|
|
gl.lastinvnr = 51; /* next inv letter to try to use will be 'a' */
|
|
gg.gmst_ubak = (genericptr_t) alloc(sizeof u);
|
|
(void) memcpy(gg.gmst_ubak, (genericptr_t) &u, sizeof u);
|
|
gg.gmst_disco = (genericptr_t) alloc(sizeof svd.disco);
|
|
(void) memcpy(gg.gmst_disco, (genericptr_t) &svd.disco,
|
|
sizeof svd.disco);
|
|
gg.gmst_mvitals = (genericptr_t) alloc(sizeof svm.mvitals);
|
|
(void) memcpy(gg.gmst_mvitals, (genericptr_t) &svm.mvitals,
|
|
sizeof svm.mvitals);
|
|
gg.gmst_stored = TRUE;
|
|
} else {
|
|
impossible("nhl_gamestate: inconsistent state (%s vs %s)",
|
|
reststate ? "restore" : "save",
|
|
gg.gmst_stored ? "already stored" : "not stored");
|
|
}
|
|
update_inventory();
|
|
return 0;
|
|
}
|
|
|
|
RESTORE_WARNING_UNREACHABLE_CODE
|
|
|
|
/* free dynamic date allocated when entering tutorial;
|
|
called when exiting tutorial normally or if player quits while in it */
|
|
void
|
|
free_tutorial(void)
|
|
{
|
|
struct obj *otmp;
|
|
|
|
/* for normal tutorial exit, gmst_invent will already be Null */
|
|
while ((otmp = gg.gmst_invent) != 0) {
|
|
/* set otmp->where = OBJ_FREE, otmp->nobj = NULL */
|
|
extract_nobj(otmp, &gg.gmst_invent);
|
|
/* gmst_invent is a list of invent items sequestered when entering
|
|
the tutorial; for them, owornmask is used as a flag to re-wear
|
|
them when exiting tutorial, not that they are currently worn;
|
|
clear it to avoid a "deleting worn obj" complaint from obfree() */
|
|
otmp->owornmask = 0L;
|
|
/* dealloc_obj() isn't enough (for containers, it assumes that
|
|
caller has already freed their contents) */
|
|
obfree(otmp, (struct obj *) 0);
|
|
}
|
|
|
|
if (gg.gmst_ubak)
|
|
free(gg.gmst_ubak), gg.gmst_ubak = NULL;
|
|
if (gg.gmst_disco)
|
|
free(gg.gmst_disco), gg.gmst_disco = NULL;
|
|
if (gg.gmst_mvitals)
|
|
free(gg.gmst_mvitals), gg.gmst_mvitals = NULL;
|
|
}
|
|
|
|
/* 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 */
|
|
staticfn 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");
|
|
}
|
|
|
|
staticfn 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
|
|
|
|
staticfn 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, svm.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;
|
|
}
|
|
|
|
staticfn int
|
|
nhl_meta_u_newindex(lua_State *L)
|
|
{
|
|
nhl_error(L, "Cannot set u table values");
|
|
/*NOTREACHED*/
|
|
return 0;
|
|
}
|
|
|
|
RESTORE_WARNING_UNREACHABLE_CODE
|
|
|
|
staticfn 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")); */
|
|
staticfn 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 }
|
|
};
|
|
|
|
staticfn 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
|
|
staticfn 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
|
|
|
|
staticfn int
|
|
traceback_handler(lua_State *L)
|
|
{
|
|
luaL_traceback(L, L, lua_tostring(L, 1), 0);
|
|
/* TODO: call impossible() if fuzzing? */
|
|
return 1;
|
|
}
|
|
|
|
staticfn 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) {
|
|
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));
|
|
/*NOTREACHED*/
|
|
break;
|
|
case NHLpa_impossible:
|
|
impossible("Lua error: %d:%s %s", nud->sid,
|
|
nud->name ? nud->name : "(unknown)",
|
|
lua_tostring(L, -1));
|
|
/* Drop the error. If the caller cares, use nhl_pcall(). */
|
|
lua_pop(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");
|
|
if(!L) return 0;
|
|
|
|
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) {
|
|
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
|
|
* which write to stdout */
|
|
{ NEVER, "warn" }, /* not ok - calls lua_writestringerror which writes
|
|
* to 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)
|
|
|
|
staticfn 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
|
|
staticfn 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
|
|
|
|
staticfn void
|
|
end_luapat(void)
|
|
{
|
|
if (luapat) {
|
|
lua_close(luapat);
|
|
luapat = NULL;
|
|
}
|
|
}
|
|
|
|
#ifdef notyet
|
|
staticfn 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
|
|
* struct g (gi now) 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
|
|
staticfn 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);
|
|
}
|
|
|
|
staticfn 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
|
|
staticfn 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
|
|
|
|
staticfn 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
|
|
|
|
staticfn 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 */
|
|
staticfn 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
|
|
staticfn 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
|
|
|
|
staticfn 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*/
|