/* 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 #endif /* #- include #- include #- include */ /* */ /* 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()" and "" 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 "." although we check to see whether it is "Lua-." and strip prefix if so; LUA_RELEASE is . 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: * message=LUASTATS : * 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*/