/*! \file command.cpp * \brief Command parser and support routines. * * The functions here crack command lists into commands, decode switches, and * match commands to built-in and softcoded commands. This is one of the * three parsers in the server. The other two parsers are for functions * (see eval.cpp) and locks (boolexp.cpp). */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "externs.h" #include "ganl_stub.h" #include "walk.h" #include "mux_table.h" #define PCRE2_CODE_UNIT_WIDTH 8 #include // Switch tables for the various commands. // static NAMETAB attrib_sw[] = { {T("access"), 1, CA_GOD, ATTRIB_ACCESS}, {T("delete"), 1, CA_GOD, ATTRIB_DELETE}, {T("rename"), 1, CA_GOD, ATTRIB_RENAME}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB boot_sw[] = { {T("port"), 1, CA_WIZARD, BOOT_PORT|SW_MULTIPLE}, {T("quiet"), 1, CA_WIZARD, BOOT_QUIET|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB break_sw[] = { {T("inline"), 1, CA_WIZARD, BREAK_INLINE}, {T("queued"), 1, CA_WIZARD, BREAK_QUEUED}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB cboot_sw[] = { {T("quiet"), 1, CA_PUBLIC, CBOOT_QUIET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB comtitle_sw[] = { {T("gag"), 1, CA_PUBLIC, COMTITLE_GAG}, {T("off"), 2, CA_PUBLIC, COMTITLE_OFF}, {T("on"), 2, CA_PUBLIC, COMTITLE_ON}, {T("ungag"), 1, CA_PUBLIC, COMTITLE_UNGAG}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB cemit_sw[] = { {T("noheader"), 1, CA_PUBLIC, CEMIT_NOHEADER}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB chown_sw[] = { {T("nostrip"), 1, CA_PUBLIC, CHOWN_NOSTRIP}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB clone_sw[] = { {T("cost"), 1, CA_PUBLIC, CLONE_SET_COST}, {T("inherit"), 3, CA_PUBLIC, CLONE_INHERIT|SW_MULTIPLE}, {T("inventory"), 3, CA_PUBLIC, CLONE_INVENTORY}, {T("location"), 1, CA_PUBLIC, CLONE_LOCATION}, {T("nostrip"), 2, CA_WIZARD, CLONE_NOSTRIP|SW_MULTIPLE}, {T("parent"), 2, CA_PUBLIC, CLONE_FROM_PARENT|SW_MULTIPLE}, {T("preserve"), 2, CA_WIZARD, CLONE_PRESERVE|SW_MULTIPLE}, { nullptr, 0, 0, 0} }; static NAMETAB clist_sw[] = { {T("full"), 0, CA_PUBLIC, CLIST_FULL}, {T("headers"), 0, CA_PUBLIC, CLIST_HEADERS}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB cset_sw[] = { {T("anon"), 1, CA_PUBLIC, CSET_SPOOF}, {T("header"), 1, CA_PUBLIC, CSET_HEADER}, {T("list"), 2, CA_PUBLIC, CSET_LIST}, {T("log") , 3, CA_PUBLIC, CSET_LOG}, {T("loud"), 3, CA_PUBLIC, CSET_LOUD}, {T("mute"), 1, CA_PUBLIC, CSET_QUIET}, {T("nospoof"), 1, CA_PUBLIC, CSET_NOSPOOF}, {T("object"), 1, CA_PUBLIC, CSET_OBJECT}, {T("private"), 2, CA_PUBLIC, CSET_PRIVATE}, {T("public"), 2, CA_PUBLIC, CSET_PUBLIC}, {T("quiet"), 1, CA_PUBLIC, CSET_QUIET}, {T("spoof"), 1, CA_PUBLIC, CSET_SPOOF}, {T("timestamp_logs"), 1, CA_PUBLIC, CSET_LOG_TIME}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB dbck_sw[] = { {T("full"), 1, CA_WIZARD, DBCK_FULL}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB decomp_sw[] = { {T("dbref"), 1, CA_PUBLIC, DECOMP_DBREF}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB destroy_sw[] = { {T("instant"), 4, CA_PUBLIC, DEST_INSTANT|SW_MULTIPLE}, {T("override"), 8, CA_PUBLIC, DEST_OVERRIDE|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB dig_sw[] = { {T("teleport"), 1, CA_PUBLIC, DIG_TELEPORT}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB doing_sw[] = { {T("header"), 1, CA_PUBLIC, DOING_HEADER}, {T("message"), 1, CA_PUBLIC, DOING_MESSAGE}, {T("poll"), 1, CA_PUBLIC, DOING_POLL}, {T("quiet"), 1, CA_PUBLIC, DOING_QUIET|SW_MULTIPLE}, {T("unique"), 1, CA_PUBLIC, DOING_UNIQUE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB dolist_sw[] = { {T("break"), 1, CA_PUBLIC, DOLIST_BREAK|SW_MULTIPLE}, {T("delimit"), 1, CA_PUBLIC, DOLIST_DELIMIT}, {T("notify"), 1, CA_PUBLIC, DOLIST_NOTIFY|SW_MULTIPLE}, {T("now"), 1, CA_WIZARD, DOLIST_NOW|SW_MULTIPLE}, {T("space"), 1, CA_PUBLIC, DOLIST_SPACE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB query_sw[] = { {T("sql"), 1, CA_PUBLIC, QUERY_SQL}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB drop_sw[] = { {T("quiet"), 1, CA_PUBLIC, DROP_QUIET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB dump_sw[] = { {T("flatfile"), 1, CA_WIZARD, DUMP_FLATFILE|SW_MULTIPLE}, {T("structure"), 1, CA_WIZARD, DUMP_STRUCT|SW_MULTIPLE}, {T("text"), 1, CA_WIZARD, DUMP_TEXT|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB emit_sw[] = { {T("here"), 2, CA_PUBLIC, SAY_HERE|SW_MULTIPLE}, {T("html"), 2, CA_PUBLIC, SAY_HTML|SW_MULTIPLE}, {T("room"), 1, CA_PUBLIC, SAY_ROOM|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB enter_sw[] = { {T("quiet"), 1, CA_PUBLIC, MOVE_QUIET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB examine_sw[] = { {T("brief"), 1, CA_PUBLIC, EXAM_BRIEF}, {T("debug"), 1, CA_WIZARD, EXAM_DEBUG}, {T("full"), 1, CA_PUBLIC, EXAM_LONG}, {T("parent"), 1, CA_PUBLIC, EXAM_PARENT}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB femit_sw[] = { {T("here"), 1, CA_PUBLIC, PEMIT_HERE|SW_MULTIPLE}, {T("room"), 1, CA_PUBLIC, PEMIT_ROOM|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB fixdb_sw[] = { {T("contents"), 1, CA_GOD, FIXDB_CON}, {T("exits"), 1, CA_GOD, FIXDB_EXITS}, {T("location"), 1, CA_GOD, FIXDB_LOC}, {T("next"), 1, CA_GOD, FIXDB_NEXT}, {T("owner"), 1, CA_GOD, FIXDB_OWNER}, {T("pennies"), 1, CA_GOD, FIXDB_PENNIES}, {T("rename"), 1, CA_GOD, FIXDB_NAME}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB flag_sw[] = { {T("remove"), 1, CA_GOD, FLAG_REMOVE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB folder_sw[] = { {T("file"), 1, CA_PUBLIC, FOLDER_FILE}, {T("list"), 1, CA_PUBLIC, FOLDER_LIST}, {T("read"), 1, CA_PUBLIC, FOLDER_READ}, {T("set"), 1, CA_PUBLIC, FOLDER_SET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB force_sw[] = { {T("now"), 1, CA_WIZARD, FORCE_NOW}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB fpose_sw[] = { {T("default"), 1, CA_PUBLIC, 0}, {T("nospace"), 1, CA_PUBLIC, SAY_NOSPACE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB function_sw[] = { {T("delete"), 1, CA_WIZARD, FN_DELETE}, {T("list"), 1, CA_WIZARD, FN_LIST}, {T("preserve"), 3, CA_WIZARD, FN_PRES|SW_MULTIPLE}, {T("privileged"), 3, CA_WIZARD, FN_PRIV|SW_MULTIPLE}, {T("restrict"), 1, CA_WIZARD, FN_RESTRICT|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB get_sw[] = { {T("quiet"), 1, CA_PUBLIC, GET_QUIET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB give_sw[] = { {T("quiet"), 1, CA_WIZARD, GIVE_QUIET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB goto_sw[] = { {T("quiet"), 1, CA_PUBLIC, MOVE_QUIET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB halt_sw[] = { {T("all"), 1, CA_PUBLIC, HALT_ALL}, {T("pid"), 1, CA_PUBLIC, HALT_PID}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB hook_sw[] = { {T("after"), 3, CA_GOD, CEF_HOOK_AFTER}, {T("before"), 3, CA_GOD, CEF_HOOK_BEFORE}, {T("clear"), 3, CA_GOD, CEF_HOOK_CLEAR|SW_MULTIPLE}, {T("fail"), 1, CA_GOD, CEF_HOOK_AFAIL}, {T("ignore"), 3, CA_GOD, CEF_HOOK_IGNORE}, {T("igswitch"), 3, CA_GOD, CEF_HOOK_IGSWITCH}, {T("list"), 3, CA_GOD, CEF_HOOK_LIST}, {T("permit"), 3, CA_GOD, CEF_HOOK_PERMIT}, {T("args"), 3, CA_GOD, CEF_HOOK_ARGS}, {nullptr, 0, 0, 0} }; static NAMETAB icmd_sw[] = { {T("check"), 2, CA_GOD, ICMD_CHECK}, {T("clear"), 2, CA_GOD, ICMD_CLEAR}, {T("croom"), 2, CA_GOD, ICMD_CROOM}, {T("disable"), 1, CA_GOD, ICMD_DISABLE}, {T("droom"), 2, CA_GOD, ICMD_DROOM}, {T("ignore"), 1, CA_GOD, ICMD_IGNORE}, {T("iroom"), 2, CA_GOD, ICMD_IROOM}, {T("lroom"), 2, CA_GOD, ICMD_LROOM}, {T("lallroom"), 2, CA_GOD, ICMD_LALLROOM}, {T("off"), 2, CA_GOD, ICMD_OFF}, {T("on"), 2, CA_GOD, ICMD_ON}, {nullptr, 0, 0, 0} }; static NAMETAB leave_sw[] = { {T("quiet"), 1, CA_PUBLIC, MOVE_QUIET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB listmotd_sw[] = { {T("brief"), 1, CA_WIZARD, MOTD_BRIEF}, {static_cast(nullptr), 0, 0, 0} }; NAMETAB lock_sw[] = { {T("defaultlock"), 1, CA_PUBLIC, A_LOCK}, {T("droplock"), 1, CA_PUBLIC, A_LDROP}, {T("enterlock"), 1, CA_PUBLIC, A_LENTER}, {T("getfromlock"), 1, CA_PUBLIC, A_LGET}, {T("givelock"), 1, CA_PUBLIC, A_LGIVE}, {T("leavelock"), 2, CA_PUBLIC, A_LLEAVE}, {T("linklock"), 2, CA_PUBLIC, A_LLINK}, {T("maillock"), 1, CA_PUBLIC, A_LMAIL}, {T("openlock"), 1, CA_PUBLIC, A_LOPEN}, {T("pagelock"), 3, CA_PUBLIC, A_LPAGE}, {T("parentlock"), 3, CA_PUBLIC, A_LPARENT}, {T("receivelock"), 1, CA_PUBLIC, A_LRECEIVE}, {T("speechlock"), 1, CA_PUBLIC, A_LSPEECH}, {T("teloutlock"), 2, CA_PUBLIC, A_LTELOUT}, {T("tportlock"), 2, CA_PUBLIC, A_LTPORT}, {T("uselock"), 1, CA_PUBLIC, A_LUSE}, {T("userlock"), 4, CA_PUBLIC, A_LUSER}, {T("visiblelock"), 1, CA_PUBLIC, A_LVISIBLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB look_sw[] = { {T("outside"), 1, CA_PUBLIC, LOOK_OUTSIDE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB mail_sw[] = { {T("abort"), 2, CA_PUBLIC, MAIL_ABORT}, {T("alias"), 4, CA_PUBLIC, MAIL_ALIAS}, {T("alist"), 4, CA_PUBLIC, MAIL_ALIST}, {T("bcc"), 1, CA_PUBLIC, MAIL_BCC}, {T("cc"), 2, CA_PUBLIC, MAIL_CC}, {T("clear"), 2, CA_PUBLIC, MAIL_CLEAR}, {T("debug"), 2, CA_PUBLIC, MAIL_DEBUG}, {T("dstats"), 2, CA_PUBLIC, MAIL_DSTATS}, {T("edit"), 1, CA_PUBLIC, MAIL_EDIT}, {T("file"), 2, CA_PUBLIC, MAIL_FILE}, {T("folder"), 3, CA_PUBLIC, MAIL_FOLDER}, {T("forward"), 3, CA_PUBLIC, MAIL_FORWARD}, {T("fstats"), 2, CA_PUBLIC, MAIL_FSTATS}, {T("fwd"), 2, CA_PUBLIC, MAIL_FORWARD}, {T("list"), 1, CA_PUBLIC, MAIL_LIST}, {T("next"), 3, CA_PUBLIC, MAIL_NEXT}, {T("nuke"), 1, CA_PUBLIC, MAIL_NUKE}, {T("proof"), 2, CA_PUBLIC, MAIL_PROOF}, {T("purge"), 2, CA_PUBLIC, MAIL_PURGE}, {T("quick"), 3, CA_PUBLIC, MAIL_QUICK}, {T("quote"), 3, CA_PUBLIC, MAIL_QUOTE|SW_MULTIPLE}, {T("read"), 3, CA_PUBLIC, MAIL_READ}, {T("reply"), 3, CA_PUBLIC, MAIL_REPLY}, {T("replyall"), 6, CA_PUBLIC, MAIL_REPLYALL}, {T("retract"), 3, CA_PUBLIC, MAIL_RETRACT}, {T("review"), 3, CA_PUBLIC, MAIL_REVIEW}, {T("safe"), 2, CA_PUBLIC, MAIL_SAFE}, {T("send"), 2, CA_PUBLIC, MAIL_SEND}, {T("stats"), 2, CA_PUBLIC, MAIL_STATS}, {T("tag"), 1, CA_PUBLIC, MAIL_TAG}, {T("unclear"), 3, CA_PUBLIC, MAIL_UNCLEAR}, {T("unsafe"), 3, CA_PUBLIC, MAIL_UNSAFE}, {T("untag"), 3, CA_PUBLIC, MAIL_UNTAG}, {T("urgent"), 2, CA_PUBLIC, MAIL_URGENT}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB malias_sw[] = { {T("add"), 1, CA_PUBLIC, MALIAS_ADD}, {T("chown"), 1, CA_PUBLIC, MALIAS_CHOWN}, {T("desc"), 1, CA_PUBLIC, MALIAS_DESC}, {T("delete"), 1, CA_PUBLIC, MALIAS_DELETE}, {T("list"), 1, CA_PUBLIC, MALIAS_LIST}, {T("remove"), 1, CA_PUBLIC, MALIAS_REMOVE}, {T("rename"), 1, CA_PUBLIC, MALIAS_RENAME}, {T("status"), 1, CA_PUBLIC, MALIAS_STATUS}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB mark_sw[] = { {T("clear"), 1, CA_PUBLIC, MARK_CLEAR}, {T("set"), 1, CA_PUBLIC, MARK_SET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB markall_sw[] = { {T("clear"), 1, CA_PUBLIC, MARK_CLEAR}, {T("set"), 1, CA_PUBLIC, MARK_SET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB motd_sw[] = { {T("brief"), 1, CA_WIZARD, MOTD_BRIEF|SW_MULTIPLE}, {T("connect"), 1, CA_WIZARD, MOTD_ALL}, {T("down"), 1, CA_WIZARD, MOTD_DOWN}, {T("full"), 1, CA_WIZARD, MOTD_FULL}, {T("list"), 1, CA_PUBLIC, MOTD_LIST}, {T("wizard"), 1, CA_WIZARD, MOTD_WIZ}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB notify_sw[] = { {T("all"), 1, CA_PUBLIC, NFY_NFYALL}, {T("first"), 1, CA_PUBLIC, NFY_NFY}, {T("quiet"), 1, CA_PUBLIC, NFY_QUIET|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB open_sw[] = { {T("inventory"), 1, CA_PUBLIC, OPEN_INVENTORY}, {T("location"), 1, CA_PUBLIC, OPEN_LOCATION}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB page_sw[] = { {T("blind"), 1, CA_PUBLIC, PAGE_BLIND|SW_MULTIPLE}, {T("noeval"), 1, CA_PUBLIC, SW_NOEVAL|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB addcommand_sw[] = { {T("noeval"), 1, CA_GOD, ADDCMD_NOEVAL|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB oemit_sw[] = { {T("list"), 1, CA_PUBLIC, PEMIT_LIST|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB pemit_sw[] = { {T("contents"), 1, CA_PUBLIC, PEMIT_CONTENTS|SW_MULTIPLE}, {T("html"), 1, CA_PUBLIC, PEMIT_HTML|SW_MULTIPLE}, {T("list"), 1, CA_PUBLIC, PEMIT_LIST|SW_MULTIPLE}, {T("noeval"), 1, CA_PUBLIC, SW_NOEVAL|SW_MULTIPLE}, {T("object"), 1, CA_PUBLIC, 0}, {T("silent"), 1, CA_PUBLIC, 0}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB pose_sw[] = { {T("default"), 1, CA_PUBLIC, 0}, {T("noeval"), 3, CA_PUBLIC, SW_NOEVAL|SW_MULTIPLE}, {T("nospace"), 3, CA_PUBLIC, SAY_NOSPACE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB ps_sw[] = { {T("all"), 1, CA_PUBLIC, PS_ALL|SW_MULTIPLE}, {T("brief"), 1, CA_PUBLIC, PS_BRIEF}, {T("long"), 1, CA_PUBLIC, PS_LONG}, {T("summary"), 1, CA_PUBLIC, PS_SUMM}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB quota_sw[] = { {T("all"), 1, CA_GOD, QUOTA_ALL|SW_MULTIPLE}, {T("fix"), 1, CA_WIZARD, QUOTA_FIX}, {T("remaining"), 1, CA_WIZARD, QUOTA_REM|SW_MULTIPLE}, {T("set"), 1, CA_WIZARD, QUOTA_SET}, {T("total"), 1, CA_WIZARD, QUOTA_TOT|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB protect_sw[] = { {T("add"), 2, CA_PUBLIC, PROTECT_ADD}, {T("alias"), 2, CA_PUBLIC, PROTECT_ALIAS}, {T("all"), 3, CA_WIZARD, PROTECT_ALL}, {T("del"), 1, CA_PUBLIC, PROTECT_DEL}, {T("list"), 1, CA_PUBLIC, PROTECT_LIST}, {T("unalias"), 1, CA_PUBLIC, PROTECT_UNALIAS}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB reference_sw[] = { {T("list"), 1, CA_PUBLIC, REFERENCE_LIST}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB say_sw[] = { {T("noeval"), 1, CA_PUBLIC, SAY_NOEVAL|SW_NOEVAL|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB set_sw[] = { {T("quiet"), 1, CA_PUBLIC, SET_QUIET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB stats_sw[] = { {T("all"), 1, CA_PUBLIC, STAT_ALL}, {T("me"), 1, CA_PUBLIC, STAT_ME}, {T("player"), 1, CA_PUBLIC, STAT_PLAYER}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB scan_sw[] = { {T("globals"), 1, CA_PUBLIC, SCAN_GLOBALS|SW_MULTIPLE}, {T("room"), 1, CA_PUBLIC, SCAN_ROOM|SW_MULTIPLE}, {T("self"), 1, CA_PUBLIC, SCAN_SELF|SW_MULTIPLE}, {T("zone"), 1, CA_PUBLIC, SCAN_ZONE|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB sweep_sw[] = { {T("commands"), 3, CA_PUBLIC, SWEEP_COMMANDS|SW_MULTIPLE}, {T("connected"), 3, CA_PUBLIC, SWEEP_CONNECT|SW_MULTIPLE}, {T("exits"), 1, CA_PUBLIC, SWEEP_EXITS|SW_MULTIPLE}, {T("here"), 1, CA_PUBLIC, SWEEP_HERE|SW_MULTIPLE}, {T("inventory"), 1, CA_PUBLIC, SWEEP_ME|SW_MULTIPLE}, {T("listeners"), 1, CA_PUBLIC, SWEEP_LISTEN|SW_MULTIPLE}, {T("players"), 1, CA_PUBLIC, SWEEP_PLAYER|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB switch_sw[] = { {T("all"), 1, CA_PUBLIC, SWITCH_ANY}, {T("default"), 1, CA_PUBLIC, SWITCH_DEFAULT}, {T("first"), 1, CA_PUBLIC, SWITCH_ONE}, {T("notify"), 1, CA_PUBLIC, SWITCH_NOTIFY|SW_MULTIPLE}, {T("now"), 1, CA_WIZARD, SWITCH_NOW|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB teleport_sw[] = { {T("list"), 1, CA_PUBLIC, TELEPORT_LIST|SW_MULTIPLE}, {T("loud"), 1, CA_PUBLIC, TELEPORT_DEFAULT}, {T("quiet"), 1, CA_PUBLIC, TELEPORT_QUIET}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB timecheck_sw[] = { {T("log"), 1, CA_WIZARD, TIMECHK_LOG | SW_MULTIPLE}, {T("reset"), 1, CA_WIZARD, TIMECHK_RESET | SW_MULTIPLE}, {T("screen"), 1, CA_WIZARD, TIMECHK_SCREEN | SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB toad_sw[] = { {T("no_chown"), 1, CA_WIZARD, TOAD_NO_CHOWN|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB walk_sw[] = { {T("stop"), 1, CA_PUBLIC, WALK_STOP}, {T("quiet"), 1, CA_PUBLIC, WALK_QUIET|SW_MULTIPLE}, {T("locked"), 1, CA_PUBLIC, WALK_LOCKED|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB include_sw[] = { {T("nobreak"), 1, CA_PUBLIC, INCLUDE_NOBREAK}, {T("localize"), 1, CA_PUBLIC, INCLUDE_LOCALIZE|SW_MULTIPLE}, {T("clearregs"), 1, CA_PUBLIC, INCLUDE_CLEARREGS|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB trig_sw[] = { {T("quiet"), 1, CA_PUBLIC, TRIG_QUIET}, {T("notify"), 1, CA_PUBLIC, TRIG_NOTIFY|SW_MULTIPLE}, {T("now"), 1, CA_WIZARD, TRIG_NOW|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB wait_sw[] = { {T("until"), 1, CA_PUBLIC, WAIT_UNTIL}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB verb_sw[] = { {T("no_name"), 3, CA_PUBLIC, VERB_NONAME}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB wall_sw[] = { {T("admin"), 1, CA_ADMIN, SHOUT_ADMIN|SW_MULTIPLE}, {T("emit"), 1, CA_ANNOUNCE, SHOUT_EMIT|SW_MULTIPLE}, {T("no_prefix"), 1, CA_ANNOUNCE, SHOUT_NOTAG|SW_MULTIPLE}, {T("pose"), 1, CA_ANNOUNCE, SHOUT_POSE|SW_MULTIPLE}, {T("wizard"), 1, CA_ANNOUNCE, SHOUT_WIZARD|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB warp_sw[] = { {T("check"), 1, CA_WIZARD, TWARP_CLEAN|SW_MULTIPLE}, {T("dump"), 1, CA_WIZARD, TWARP_DUMP|SW_MULTIPLE}, {T("events"), 1, CA_WIZARD, TWARP_EVENTS|SW_MULTIPLE}, {T("idle"), 1, CA_WIZARD, TWARP_IDLE|SW_MULTIPLE}, {T("queue"), 1, CA_WIZARD, TWARP_QUEUE|SW_MULTIPLE}, {static_cast(nullptr), 0, 0, 0} }; static NAMETAB lua_sw[] = { {T("inline"), 1, CA_WIZARD, LUA_INLINE}, {T("stats"), 1, CA_PUBLIC, LUA_STATS}, {static_cast(nullptr), 0, 0, 0} }; // @lua handler — execute Lua scripts or show stats. // static void do_lua(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *arg1, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); if (nullptr == mudstate.pILuaControl) { notify(executor, M_("Lua module is not loaded.")); return; } if (LUA_STATS == key) { size_t nCalls, nErrors, nInsnHits, nMemHits, nBytes; size_t nCacheHits, nCacheMisses, nCacheEntries; mudstate.pILuaControl->GetStats(&nCalls, &nErrors, &nInsnHits, &nMemHits, &nBytes, &nCacheHits, &nCacheMisses, &nCacheEntries); notify(executor, tprintf(M_("Lua stats: %zu calls, %zu errors, " "%zu insn-limit hits, %zu mem-limit hits, %zu bytes used"), nCalls, nErrors, nInsnHits, nMemHits, nBytes)); notify(executor, tprintf(M_("Cache: %zu entries, %zu hits, %zu misses"), nCacheEntries, nCacheHits, nCacheMisses)); return; } if (LUA_INLINE == key) { if (!Wizard(executor)) { notify(executor, M_("Permission denied.")); return; } UTF8 result[8000]; size_t nResult = 0; mudstate.pILuaControl->Eval(executor, caller, enactor, arg1, strlen(reinterpret_cast(arg1)), result, sizeof(result), &nResult); notify(executor, result); return; } // Default: @lua obj/attr — execute a Lua script on an object. // UTF8 *pSlash = reinterpret_cast( const_cast(strchr(reinterpret_cast(arg1), '/'))); if (nullptr == pSlash) { notify(executor, M_("Usage: @lua /")); return; } *pSlash = '\0'; UTF8 *pAttrName = pSlash + 1; dbref obj = lookup_player(executor, arg1, true); if (NOTHING == obj) { init_match(executor, arg1, NOTYPE); match_everything(MAT_EXIT_PARENTS); obj = match_result(); } if (!Good_obj(obj)) { notify(executor, M_("No such object.")); return; } UTF8 result[8000]; size_t nResult = 0; mudstate.pILuaControl->CallAttr(executor, caller, enactor, obj, pAttrName, nullptr, 0, result, sizeof(result), &nResult); notify(executor, result); } /* --------------------------------------------------------------------------- * Command table: Definitions for builtin commands, used to build the command * hash table. * * Format: Name Switches Permissions Needed * Key (if any) Calling Seq Handler */ static CMDENT_NO_ARG command_table_no_arg[] = { {T("@@"), nullptr, CA_PUBLIC, 0, CS_NO_ARGS, 0, do_comment}, {T("@dbck"), dbck_sw, CA_WIZARD, 0, CS_NO_ARGS, 0, do_dbck}, {T("@dbclean"), nullptr, CA_GOD, 0, CS_NO_ARGS, 0, do_dbclean}, {T("@dump"), dump_sw, CA_WIZARD, 0, CS_NO_ARGS, 0, do_dump}, {T("@logrotate"), nullptr, CA_WIZARD, 0, CS_NO_ARGS, 0, do_logrotate}, {T("@mark_all"), markall_sw, CA_WIZARD, MARK_SET, CS_NO_ARGS, 0, do_markall}, {T("@readcache"), nullptr, CA_WIZARD, 0, CS_NO_ARGS, 0, do_readcache}, {T("@restart"), nullptr, CA_NO_GUEST|CA_NO_SLAVE, 0, CS_NO_ARGS, 0, do_restart}, {T("@startslave"), nullptr, CA_WIZARD, 0, CS_NO_ARGS, 0, do_startslave}, {T("@timecheck"), timecheck_sw, CA_WIZARD, 0, CS_NO_ARGS, 0, do_timecheck}, {T("clearcom"), nullptr, CA_NO_SLAVE, 0, CS_NO_ARGS, 0, do_clearcom}, {T("help"), nullptr, CA_PUBLIC, CMD_HELP, CS_NO_ARGS, 0, logged_out0}, {T("info"), nullptr, CA_PUBLIC, CMD_INFO, CS_NO_ARGS, 0, logged_out0}, {T("inventory"), nullptr, CA_PUBLIC, 0, CS_NO_ARGS, 0, do_inventory}, {T("leave"), leave_sw, CA_LOCATION, 0, CS_NO_ARGS, 0, do_leave}, {T("logout"), nullptr, CA_PUBLIC, CMD_LOGOUT, CS_NO_ARGS, 0, logged_out0}, {T("quit"), nullptr, CA_PUBLIC, CMD_QUIT, CS_NO_ARGS, 0, logged_out0}, {T("report"), nullptr, CA_PUBLIC, 0, CS_NO_ARGS, 0, do_report}, {T("score"), nullptr, CA_PUBLIC, 0, CS_NO_ARGS, 0, do_score}, {T("version"), nullptr, CA_PUBLIC, 0, CS_NO_ARGS, 0, do_version}, {static_cast(nullptr), nullptr, 0, 0, 0, 0, nullptr} }; static CMDENT_ONE_ARG command_table_one_arg[] = { {T("@apply_marked"), nullptr, CA_WIZARD|CA_GBL_INTERP, 0, CS_ONE_ARG|CS_CMDARG|CS_NOINTERP|CS_STRIP_AROUND, 0, do_apply_marked}, {T("@boot"), boot_sw, CA_NO_GUEST|CA_NO_SLAVE, 0, CS_ONE_ARG|CS_INTERP, 0, do_boot}, {T("@ccreate"), nullptr, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_ONE_ARG, 0, do_createchannel}, {T("@cdestroy"), nullptr, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_ONE_ARG, 0, do_destroychannel}, {T("@clist"), clist_sw, CA_NO_SLAVE, 0, CS_ONE_ARG, 0, do_chanlist}, {T("@cut"), nullptr, CA_WIZARD|CA_LOCATION, 0, CS_ONE_ARG|CS_INTERP, 0, do_cut}, {T("@cwho"), nullptr, CA_NO_SLAVE, 0, CS_ONE_ARG, 0, do_channelwho}, {T("@crondel"), nullptr, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_ONE_ARG|CS_INTERP, 0, do_crondel}, {T("@crontab"), nullptr, CA_NO_SLAVE, 0, CS_ONE_ARG, 0, do_crontab}, {T("@destroy"), destroy_sw, CA_NO_SLAVE|CA_NO_GUEST|CA_GBL_BUILD, DEST_ONE, CS_ONE_ARG|CS_INTERP, 0, do_destroy}, {T("@disable"), nullptr, CA_WIZARD, GLOB_DISABLE, CS_ONE_ARG, 0, do_global}, {T("@doing"), doing_sw, CA_PUBLIC, 0, CS_ONE_ARG, 0, do_doing}, {T("@emit"), emit_sw, CA_LOCATION|CA_NO_GUEST|CA_NO_SLAVE, SAY_EMIT, CS_ONE_ARG|CS_INTERP, 0, do_say}, {T("@enable"), nullptr, CA_WIZARD, GLOB_ENABLE, CS_ONE_ARG, 0, do_global}, {T("@entrances"), nullptr, CA_NO_GUEST, 0, CS_ONE_ARG|CS_INTERP, 0, do_entrances}, {T("@eval"), nullptr, CA_NO_SLAVE, 0, CS_ONE_ARG|CS_INTERP, 0, do_eval}, {T("@find"), nullptr, CA_PUBLIC, 0, CS_ONE_ARG|CS_INTERP, 0, do_find}, {T("@halt"), halt_sw, CA_NO_SLAVE, 0, CS_ONE_ARG|CS_INTERP, 0, do_halt}, {T("@hook"), hook_sw, CA_GOD, 0, CS_ONE_ARG|CS_NOINTERP, 0, do_hook}, {T("@kick"), nullptr, CA_WIZARD, QUEUE_KICK, CS_ONE_ARG|CS_INTERP, 0, do_queue}, {T("@last"), nullptr, CA_NO_GUEST, 0, CS_ONE_ARG|CS_INTERP, 0, do_last}, {T("@list"), nullptr, CA_PUBLIC, 0, CS_ONE_ARG|CS_INTERP, 0, do_list}, {T("@lua"), lua_sw, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_ONE_ARG|CS_INTERP, 0, do_lua}, {T("@list_file"), nullptr, CA_WIZARD, 0, CS_ONE_ARG|CS_INTERP, 0, do_list_file}, {T("@listcommands"), nullptr, CA_GOD, 0, CS_ONE_ARG, 0, do_listcommands}, {T("@listmotd"), listmotd_sw,CA_PUBLIC, MOTD_LIST, CS_ONE_ARG, 0, do_motd}, {T("@mark"), mark_sw, CA_WIZARD, SRCH_MARK, CS_ONE_ARG|CS_NOINTERP, 0, do_search}, {T("@motd"), motd_sw, CA_WIZARD, 0, CS_ONE_ARG, 0, do_motd}, {T("@nemit"), emit_sw, CA_LOCATION|CA_NO_GUEST|CA_NO_SLAVE, SAY_EMIT, CS_ONE_ARG|CS_UNPARSE|CS_NOSQUISH, 0, do_say}, {T("@poor"), nullptr, CA_GOD, 0, CS_ONE_ARG|CS_INTERP, 0, do_poor}, {T("@ps"), ps_sw, CA_PUBLIC, 0, CS_ONE_ARG|CS_INTERP, 0, do_ps}, {T("@quitprogram"), nullptr, CA_PUBLIC, 0, CS_ONE_ARG|CS_INTERP, 0, do_quitprog}, {T("@scan"), scan_sw, CA_PUBLIC, 0, CS_ONE_ARG|CS_NOINTERP, 0, do_scan}, {T("@search"), nullptr, CA_PUBLIC, SRCH_SEARCH, CS_ONE_ARG|CS_NOINTERP, 0, do_search}, {T("@shutdown"), nullptr, CA_NO_GUEST|CA_NO_SLAVE, 0, CS_ONE_ARG, 0, do_shutdown}, {T("@stats"), stats_sw, CA_PUBLIC, 0, CS_ONE_ARG|CS_INTERP, 0, do_stats}, {T("@sweep"), sweep_sw, CA_PUBLIC, 0, CS_ONE_ARG, 0, do_sweep}, {T("@timewarp"), warp_sw, CA_WIZARD, 0, CS_ONE_ARG|CS_INTERP, 0, do_timewarp}, {T("@unlink"), nullptr, CA_NO_SLAVE|CA_GBL_BUILD, 0, CS_ONE_ARG|CS_INTERP, 0, do_unlink}, {T("@unlock"), lock_sw, CA_NO_SLAVE, 0, CS_ONE_ARG|CS_INTERP, 0, do_unlock}, {T("@wall"), wall_sw, CA_ANNOUNCE, SHOUT_DEFAULT, CS_ONE_ARG|CS_INTERP, 0, do_shout}, {T("@wipe"), nullptr, CA_NO_SLAVE|CA_NO_GUEST|CA_GBL_BUILD, 0, CS_ONE_ARG|CS_INTERP, 0, do_wipe}, {T("allcom"), nullptr, CA_NO_SLAVE, 0, CS_ONE_ARG, 0, do_allcom}, {T("comlist"), nullptr, CA_NO_SLAVE, 0, CS_ONE_ARG, 0, do_comlist}, {T("delcom"), nullptr, CA_NO_SLAVE, 0, CS_ONE_ARG, 0, do_delcom}, {T("doing"), nullptr, CA_PUBLIC, CMD_DOING, CS_ONE_ARG, 0, logged_out1}, {T("drop"), drop_sw, CA_NO_SLAVE|CA_CONTENTS|CA_LOCATION|CA_NO_GUEST, 0, CS_ONE_ARG|CS_INTERP, 0, do_drop}, {T("enter"), enter_sw, CA_LOCATION, 0, CS_ONE_ARG|CS_INTERP, 0, do_enter}, {T("examine"), examine_sw, CA_PUBLIC, 0, CS_ONE_ARG|CS_INTERP, 0, do_examine}, {T("get"), get_sw, CA_LOCATION|CA_NO_GUEST, 0, CS_ONE_ARG|CS_INTERP, 0, do_get}, {T("goto"), goto_sw, CA_LOCATION, 0, CS_ONE_ARG|CS_INTERP, 0, do_move}, {T("look"), look_sw, CA_LOCATION, LOOK_LOOK, CS_ONE_ARG|CS_INTERP, 0, do_look}, {T("outputprefix"), nullptr, CA_PUBLIC, CMD_PREFIX, CS_ONE_ARG, 0, logged_out1}, {T("outputsuffix"), nullptr, CA_PUBLIC, CMD_SUFFIX, CS_ONE_ARG, 0, logged_out1}, {T("pose"), pose_sw, CA_LOCATION|CA_NO_SLAVE, SAY_POSE, CS_ONE_ARG|CS_INTERP, 0, do_say}, {T("puebloclient"), nullptr, CA_PUBLIC, CMD_PUEBLOCLIENT, CS_ONE_ARG, 0, logged_out1}, {T("say"), say_sw, CA_LOCATION|CA_NO_SLAVE, SAY_SAY, CS_ONE_ARG|CS_INTERP, 0, do_say}, {T("session"), nullptr, CA_PUBLIC, CMD_SESSION, CS_ONE_ARG, 0, logged_out1}, {T("think"), nullptr, CA_NO_SLAVE, 0, CS_ONE_ARG, 0, do_think}, {T("train"), nullptr, CA_PUBLIC, 0, CS_ONE_ARG, 0, do_train}, {T("use"), nullptr, CA_NO_SLAVE|CA_GBL_INTERP, 0, CS_ONE_ARG|CS_INTERP, 0, do_use}, {T("who"), nullptr, CA_PUBLIC, CMD_WHO, CS_ONE_ARG, 0, logged_out1}, {T("\\"), nullptr, CA_NO_GUEST|CA_LOCATION|CF_DARK|CA_NO_SLAVE, SAY_PREFIX, CS_ONE_ARG|CS_INTERP|CS_LEADIN, 0, do_say}, {T(":"), nullptr, CA_LOCATION|CF_DARK|CA_NO_SLAVE, SAY_PREFIX, CS_ONE_ARG|CS_INTERP|CS_LEADIN, 0, do_say}, {T(";"), nullptr, CA_LOCATION|CF_DARK|CA_NO_SLAVE, SAY_PREFIX, CS_ONE_ARG|CS_INTERP|CS_LEADIN, 0, do_say}, {T("\""), nullptr, CA_LOCATION|CF_DARK|CA_NO_SLAVE, SAY_PREFIX, CS_ONE_ARG|CS_INTERP|CS_LEADIN, 0, do_say}, {T("-"), nullptr, CA_NO_GUEST|CA_NO_SLAVE|CF_DARK, 0, CS_ONE_ARG|CS_LEADIN, 0, do_postpend}, {T("~"), nullptr, CA_NO_GUEST|CA_NO_SLAVE|CF_DARK, 0, CS_ONE_ARG|CS_LEADIN, 0, do_prepend}, {T("#"), nullptr, CA_NO_SLAVE|CA_GBL_INTERP|CF_DARK, 0, CS_ONE_ARG|CS_INTERP|CS_CMDARG, 0, do_force_prefixed}, {static_cast(nullptr), nullptr, 0, 0, 0, 0, nullptr} }; static CMDENT_TWO_ARG command_table_two_arg[] = { {T("@addcommand"), addcommand_sw, CA_GOD, 0, CS_TWO_ARG, 0, do_addcommand}, {T("@admin"), nullptr, CA_WIZARD, 0, CS_TWO_ARG|CS_INTERP, 0, do_admin}, {T("@alias"), nullptr, CA_NO_GUEST|CA_NO_SLAVE, 0, CS_TWO_ARG, 0, do_alias}, {T("@assert"), break_sw, CA_PUBLIC, 0, CS_TWO_ARG|CS_CMDARG|CS_NOINTERP|CS_STRIP_AROUND, 0, do_assert}, {T("@attribute"), attrib_sw, CA_GOD, 0, CS_TWO_ARG|CS_INTERP, 0, do_attribute}, {T("@break"), break_sw, CA_PUBLIC, 0, CS_TWO_ARG|CS_CMDARG|CS_NOINTERP|CS_STRIP_AROUND, 0, do_break}, {T("@cboot"), cboot_sw, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG, 0, do_chboot}, {T("@ccharge"), nullptr, CA_NO_SLAVE|CA_NO_GUEST, EDIT_CHANNEL_CCHARGE, CS_TWO_ARG, 0, do_editchannel}, {T("@cchown"), nullptr, CA_NO_SLAVE|CA_NO_GUEST, EDIT_CHANNEL_CCHOWN, CS_TWO_ARG, 0, do_editchannel}, {T("@cemit"), cemit_sw, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG, 0, do_cemit}, {T("@chown"), chown_sw, CA_NO_SLAVE|CA_NO_GUEST|CA_GBL_BUILD, CHOWN_ONE, CS_TWO_ARG|CS_INTERP, 0, do_chown}, {T("@chownall"), chown_sw, CA_WIZARD|CA_GBL_BUILD, CHOWN_ALL, CS_TWO_ARG|CS_INTERP, 0, do_chownall}, {T("@chzone"), nullptr, CA_NO_SLAVE|CA_NO_GUEST|CA_GBL_BUILD, 0, CS_TWO_ARG|CS_INTERP, 0, do_chzone}, {T("@clone"), clone_sw, CA_NO_SLAVE|CA_GBL_BUILD|CA_CONTENTS|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_clone}, {T("@coflags"), nullptr, CA_NO_SLAVE, EDIT_CHANNEL_COFLAGS, CS_TWO_ARG, 0, do_editchannel}, {T("@cpflags"), nullptr, CA_NO_SLAVE, EDIT_CHANNEL_CPFLAGS, CS_TWO_ARG, 0, do_editchannel}, {T("@create"), nullptr, CA_NO_SLAVE|CA_GBL_BUILD|CA_CONTENTS|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_create}, {T("@cron"), nullptr, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_cron}, {T("@cset"), cset_sw, CA_NO_SLAVE, 0, CS_TWO_ARG|CS_INTERP, 0, do_chopen}, {T("@decompile"), decomp_sw, CA_PUBLIC, 0, CS_TWO_ARG|CS_INTERP, 0, do_decomp}, {T("@delcommand"), nullptr, CA_GOD, 0, CS_TWO_ARG, 0, do_delcommand}, {T("@dolist"), dolist_sw, CA_GBL_INTERP, 0, CS_TWO_ARG|CS_CMDARG|CS_NOINTERP|CS_STRIP_AROUND, 0, do_dolist}, {T("@drain"), nullptr, CA_GBL_INTERP|CA_NO_SLAVE|CA_NO_GUEST, NFY_DRAIN, CS_TWO_ARG, 0, do_notify}, {T("@email"), nullptr, CA_WIZARD, 0, CS_TWO_ARG, 0, do_plusemail}, {T("@femit"), femit_sw, CA_LOCATION|CA_NO_GUEST|CA_NO_SLAVE, PEMIT_FEMIT, CS_TWO_ARG|CS_INTERP, 0, do_pemit}, {T("@fixdb"), fixdb_sw, CA_GOD, 0, CS_TWO_ARG|CS_INTERP, 0, do_fixdb}, {T("@flag"), flag_sw, CA_GOD, 0, CS_TWO_ARG, 0, do_flag}, {T("@folder"), folder_sw, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_folder}, {T("@force"), force_sw, CA_NO_SLAVE|CA_GBL_INTERP|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP|CS_CMDARG, 0, do_force}, {T("@forwardlist"), nullptr, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG, 0, do_forwardlist}, {T("@fpose"), fpose_sw, CA_LOCATION|CA_NO_SLAVE, PEMIT_FPOSE, CS_TWO_ARG|CS_INTERP, 0, do_pemit}, {T("@fsay"), nullptr, CA_LOCATION|CA_NO_SLAVE, PEMIT_FSAY, CS_TWO_ARG|CS_INTERP, 0, do_pemit}, {T("@function"), function_sw,CA_GOD, 0, CS_TWO_ARG|CS_INTERP, 0, do_function}, {T("@link"), nullptr, CA_NO_SLAVE|CA_GBL_BUILD|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_link}, {T("@lock"), lock_sw, CA_NO_SLAVE, 0, CS_TWO_ARG|CS_INTERP, 0, do_lock}, {T("@log"), nullptr, CA_WIZARD, 0, CS_TWO_ARG, 0, do_log}, {T("@mail"), mail_sw, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_mail}, {T("@malias"), malias_sw, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_malias}, {T("@moniker"), nullptr, CA_NO_GUEST|CA_NO_SLAVE, 0, CS_TWO_ARG|CS_INTERP, 0, do_moniker}, {T("@name"), nullptr, CA_NO_SLAVE|CA_GBL_BUILD|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_name}, {T("@newpassword"), nullptr, CA_WIZARD, 0, CS_TWO_ARG, 0, do_newpassword}, {T("@notify"), notify_sw, CA_GBL_INTERP|CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG, 0, do_notify}, {T("@npemit"), pemit_sw, CA_NO_GUEST|CA_NO_SLAVE, PEMIT_PEMIT, CS_TWO_ARG|CS_UNPARSE|CS_NOSQUISH, 0, do_pemit}, {T("@oemit"), oemit_sw, CA_NO_GUEST|CA_NO_SLAVE, PEMIT_OEMIT, CS_TWO_ARG|CS_INTERP, 0, do_pemit}, {T("@parent"), nullptr, CA_NO_SLAVE|CA_GBL_BUILD|CA_NO_GUEST, 0, CS_TWO_ARG, 0, do_parent}, {T("@password"), nullptr, CA_NO_GUEST, 0, CS_TWO_ARG, 0, do_password}, {T("@pcreate"), nullptr, CA_WIZARD|CA_GBL_BUILD, PCRE_PLAYER, CS_TWO_ARG, 0, do_pcreate}, {T("@pemit"), pemit_sw, CA_NO_GUEST|CA_NO_SLAVE, PEMIT_PEMIT, CS_TWO_ARG|CS_INTERP, 0, do_pemit}, {T("@power"), nullptr, CA_PUBLIC, 0, CS_TWO_ARG, 0, do_power}, {T("@protect"), protect_sw, CA_NO_GUEST|CA_NO_SLAVE, PROTECT_ADD, CS_TWO_ARG|CS_INTERP, 0, do_protect}, {T("@program"), nullptr, CA_PUBLIC, 0, CS_TWO_ARG|CS_INTERP, 0, do_prog}, {T("@query"), query_sw, CA_WIZARD, 0, CS_TWO_ARG|CS_INTERP|CS_CMDARG, 0, do_query}, {T("@quota"), quota_sw, CA_PUBLIC, 0, CS_TWO_ARG|CS_INTERP, 0, do_quota}, {T("@reference"), reference_sw, CA_PUBLIC, 0, CS_TWO_ARG|CS_INTERP, 0, do_reference}, {T("@robot"), nullptr, CA_NO_SLAVE|CA_GBL_BUILD|CA_NO_GUEST|CA_PLAYER, PCRE_ROBOT, CS_TWO_ARG, 0, do_pcreate}, #ifdef REALITY_LVLS {T("@rxlevel"), nullptr, CA_WIZARD, 0, CS_TWO_ARG|CS_INTERP, 0, do_rxlevel}, #endif {T("@set"), set_sw, CA_NO_SLAVE|CA_GBL_BUILD|CA_NO_GUEST, 0, CS_TWO_ARG, 0, do_set}, {T("@teleport"), teleport_sw,CA_NO_GUEST, TELEPORT_DEFAULT, CS_TWO_ARG|CS_INTERP, 0, do_teleport}, #ifdef REALITY_LVLS {T("@txlevel"), nullptr, CA_WIZARD, 0, CS_TWO_ARG|CS_INTERP, 0, do_txlevel}, #endif {T("@toad"), toad_sw, CA_WIZARD, 0, CS_TWO_ARG|CS_INTERP, 0, do_toad}, {T("@walk"), walk_sw, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_walk}, {T("@patrol"), walk_sw, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_patrol}, {T("@wait"), wait_sw, CA_GBL_INTERP, 0, CS_TWO_ARG|CS_CMDARG|CS_NOINTERP|CS_STRIP_AROUND, 0, do_wait}, {T("addcom"), nullptr, CA_NO_SLAVE, 0, CS_TWO_ARG, 0, do_addcom}, {T("comtitle"), comtitle_sw,CA_NO_SLAVE, 0, CS_TWO_ARG, 0, do_comtitle}, {T("give"), give_sw, CA_LOCATION|CA_NO_GUEST, 0, CS_TWO_ARG|CS_INTERP, 0, do_give}, {T("kill"), nullptr, CA_NO_GUEST|CA_NO_SLAVE, KILL_KILL, CS_TWO_ARG|CS_INTERP, 0, do_kill}, {T("page"), page_sw, CA_NO_SLAVE, 0, CS_TWO_ARG|CS_INTERP, 0, do_page}, {T("slay"), nullptr, CA_WIZARD, KILL_SLAY, CS_TWO_ARG|CS_INTERP, 0, do_kill}, {T("whisper"), nullptr, CA_LOCATION|CA_NO_SLAVE, PEMIT_WHISPER, CS_TWO_ARG|CS_INTERP, 0, do_pemit}, {T("&"), nullptr, CA_NO_GUEST|CA_NO_SLAVE|CF_DARK, 0, CS_TWO_ARG|CS_LEADIN, 0, do_setvattr}, {static_cast(nullptr), nullptr, 0, 0, 0, 0, nullptr} }; static CMDENT_TWO_ARG_ARGV command_table_two_arg_argv[] = { {T("@cpattr"), nullptr, CA_NO_SLAVE|CA_NO_GUEST|CA_GBL_BUILD, 0, CS_TWO_ARG|CS_ARGV|CS_INTERP, 0, do_cpattr}, {T("@dig"), dig_sw, CA_NO_SLAVE|CA_NO_GUEST|CA_GBL_BUILD, 0, CS_TWO_ARG|CS_ARGV|CS_INTERP, 0, do_dig}, {T("@edit"), nullptr, CA_NO_SLAVE|CA_NO_GUEST, 0, CS_TWO_ARG|CS_ARGV|CS_STRIP_AROUND, 0, do_edit}, {T("@icmd"), icmd_sw, CA_GOD, 0, CS_TWO_ARG|CS_ARGV|CS_INTERP, 0, do_icmd}, {T("@if"), nullptr, CA_GBL_INTERP, 0, CS_TWO_ARG|CS_ARGV|CS_CMDARG|CS_NOINTERP|CS_STRIP_AROUND, 0, do_if}, {T("@include"), include_sw, CA_GBL_INTERP, 0, CS_TWO_ARG|CS_ARGV, 0, do_include}, {T("@mvattr"), nullptr, CA_NO_SLAVE|CA_NO_GUEST|CA_GBL_BUILD, 0, CS_TWO_ARG|CS_ARGV, 0, do_mvattr}, {T("@open"), open_sw, CA_NO_SLAVE|CA_GBL_BUILD|CA_NO_GUEST, 0, CS_TWO_ARG|CS_ARGV|CS_INTERP, 0, do_open}, {T("@switch"), switch_sw, CA_GBL_INTERP, 0, CS_TWO_ARG|CS_ARGV|CS_CMDARG|CS_NOINTERP|CS_STRIP_AROUND, 0, do_switch}, {T("@trigger"), trig_sw, CA_GBL_INTERP, 0, CS_TWO_ARG|CS_ARGV, 0, do_trigger}, {T("@verb"), verb_sw, CA_GBL_INTERP|CA_NO_SLAVE, 0, CS_TWO_ARG|CS_ARGV|CS_INTERP|CS_STRIP_AROUND, 0, do_verb}, {static_cast(nullptr), nullptr, 0, 0, 0, 0, nullptr} }; static CMDENT *goto_cmdp; void commands_no_arg_add(CMDENT_NO_ARG cmdent[]) { for (CMDENT_NO_ARG* cp0a = cmdent; cp0a->cmdname; cp0a++) { size_t nLen = strlen(reinterpret_cast(cp0a->cmdname)); auto it = mudstate.command_htab.find(std::vector(cp0a->cmdname, cp0a->cmdname + nLen)); if (it == mudstate.command_htab.end()) { mudstate.command_htab.emplace(std::vector(cp0a->cmdname, cp0a->cmdname + nLen), cp0a); } } } void commands_one_arg_add(CMDENT_ONE_ARG cmdent[]) { CMDENT_ONE_ARG *cp1a; for (cp1a = cmdent; cp1a->cmdname; cp1a++) { size_t nLen = strlen(reinterpret_cast(cp1a->cmdname)); auto it = mudstate.command_htab.find(std::vector(cp1a->cmdname, cp1a->cmdname + nLen)); if (it == mudstate.command_htab.end()) { mudstate.command_htab.emplace(std::vector(cp1a->cmdname, cp1a->cmdname + nLen), cp1a); } } } void commands_two_arg_add(CMDENT_TWO_ARG cmdent[]) { CMDENT_TWO_ARG *cp2a; for (cp2a = cmdent; cp2a->cmdname; cp2a++) { size_t nLen = strlen(reinterpret_cast(cp2a->cmdname)); auto it = mudstate.command_htab.find(std::vector(cp2a->cmdname, cp2a->cmdname + nLen)); if (it == mudstate.command_htab.end()) { mudstate.command_htab.emplace(std::vector(cp2a->cmdname, cp2a->cmdname + nLen), cp2a); } } } void commands_two_arg_argv_add(CMDENT_TWO_ARG_ARGV cmdent[]) { CMDENT_TWO_ARG_ARGV *cp2aa; for (cp2aa = cmdent; cp2aa->cmdname; cp2aa++) { size_t nLen = strlen(reinterpret_cast(cp2aa->cmdname)); auto it = mudstate.command_htab.find(std::vector(cp2aa->cmdname, cp2aa->cmdname + nLen)); if (it == mudstate.command_htab.end()) { mudstate.command_htab.emplace(std::vector(cp2aa->cmdname, cp2aa->cmdname + nLen), cp2aa); } } } void init_cmdtab(void) { // Load attribute-setting commands. // for (ATTR* ap = AttrTable; ap->name; ap++) { if (ap->flags & AF_NOCMD) { continue; } size_t nBuffer; bool bValid; const UTF8 *cbuff = MakeCanonicalAttributeCommand(ap->name, &nBuffer, &bValid); if (!bValid) { continue; } CMDENT_TWO_ARG *cp2a = nullptr; try { cp2a = new CMDENT_TWO_ARG; } catch (...) { } if (nullptr != cp2a) { cp2a->cmdname = StringClone(cbuff); cp2a->perms = CA_NO_GUEST | CA_NO_SLAVE; cp2a->switches = nullptr; if (ap->flags & (AF_WIZARD | AF_MDARK)) { cp2a->perms |= CA_WIZARD; } cp2a->extra = ap->number; cp2a->callseq = CS_TWO_ARG; cp2a->flags = CEF_ALLOC; cp2a->handler = do_setattr; mudstate.command_htab.emplace(std::vector(cp2a->cmdname, cp2a->cmdname + nBuffer), cp2a); } else { STARTLOG(LOG_PROBLEMS, "CMD", "MEM"); log_printf(T("init_cmdtab: out of memory adding @set-attr command.")); ENDLOG; } } // Load the builtin commands // commands_no_arg_add(command_table_no_arg); commands_one_arg_add(command_table_one_arg); commands_two_arg_add(command_table_two_arg); commands_two_arg_argv_add(command_table_two_arg_argv); cache_prefix_cmds(); { const UTF8 *tmp = reinterpret_cast("goto"); auto it = mudstate.command_htab.find(std::vector(tmp, tmp + 4)); goto_cmdp = (it != mudstate.command_htab.end()) ? static_cast(it->second) : nullptr; } } static CMDENT *g_prefix_cmds[256]; void clear_prefix_cmds() { for (auto& g_prefix_cmd : g_prefix_cmds) { g_prefix_cmd = nullptr; } } #ifdef SELFCHECK void finish_cmdtab() { clear_prefix_cmds(); goto_cmdp = nullptr; // First pass is to get rid of aliases. // StringPtrMap ht; CMDENT *cmdp; for (auto &[key, val] : mudstate.command_htab) { cmdp = static_cast(val); if (0 == (cmdp->flags & CEF_VISITED)) { cmdp->flags |= CEF_VISITED; size_t nLen = strlen(reinterpret_cast(cmdp->cmdname)); ht.emplace(std::vector(cmdp->cmdname, cmdp->cmdname + nLen), cmdp); } } mudstate.command_htab.clear(); // Second pass is to free unique CMDENTs and related things. // for (auto &[htkey, htval] : ht) { cmdp = static_cast(htval); if (cmdp->callseq & CS_ADDED) { delete cmdp->addent; cmdp->addent = nullptr; } if (cmdp->flags & CEF_ALLOC) { MEMFREE(cmdp->cmdname); cmdp->cmdname = nullptr; delete cmdp; cmdp = nullptr; } } } #endif /*! \brief Fills in the table of single-character prefix commands. * * Command entries for known prefix commands (" : ; \ # & - ~) * are copied from the regular command table. Entries for all other starting * characters are set to nullptr. * * \return None. */ void cache_prefix_cmds(void) { clear_prefix_cmds(); #define SET_PREFIX_CMD(s) \ do { \ const UTF8 *_tmp = reinterpret_cast(s); \ auto _it = mudstate.command_htab.find(std::vector(_tmp, _tmp + 1)); \ g_prefix_cmds[static_cast((s)[0])] = \ (_it != mudstate.command_htab.end()) ? static_cast(_it->second) : nullptr; \ } while (0) SET_PREFIX_CMD("\""); SET_PREFIX_CMD(":"); SET_PREFIX_CMD(";"); SET_PREFIX_CMD("\\"); SET_PREFIX_CMD("#"); SET_PREFIX_CMD("&"); SET_PREFIX_CMD("-"); SET_PREFIX_CMD("~"); #undef SET_PREFIX_CMD } inline bool is_prefix_cmd(const UTF8 *pCommand, size_t *pnPrefix, CMDENT **ppcmd) { bool fReturn = false; size_t nPrefix = 0; CMDENT *pcmd = nullptr; if (nullptr != pCommand) { pcmd = g_prefix_cmds[static_cast(pCommand[0])]; if (nullptr != pcmd) { nPrefix = 1; fReturn = true; } else if ( 0xE2 == pCommand[0] && 0x80 == pCommand[1] && 0x9C == pCommand[2]) { // U+201C is a Unicode quote typically sent instead of ASCII double quote. // pcmd = g_prefix_cmds[static_cast('"')]; if (nullptr != pcmd) { nPrefix = 3; fReturn = true; } } } if (nullptr != pnPrefix) { *pnPrefix = nPrefix; } if (nullptr != ppcmd) { *ppcmd = pcmd; } return fReturn; } // --------------------------------------------------------------------------- // check_access: Check if player has access to function. // bool check_access(dbref player, int mask) { if (mask & (CA_DISABLED|CA_STATIC)) { return false; } if ( God(player) || mudstate.bReadingConfiguration) { return true; } if (mask & CA_MUSTBE_MASK) { // Since CA_GOD by itself is a frequent case, for the sake of // performance, we test CA_GOD specifically. If CA_GOD were ever // combined with anything, it would be passed through to the general // case. // if ((mask & CA_MUSTBE_MASK) == CA_GOD) { return false; } // Since God(player) is always false here, CA_GOD is still handled by // the following code even though it doesn't appear in any of the // cases explicitly. CA_WIZARD by itself is also a common case, but // since we have have a bit (mask & CA_MUSTBE_MASK), and since that // bit is not a lone CA_GOD bit (handled above), and since CA_WIZARD // it tested first below, it doesn't make sense to test CA_WIZARD // as a special case. // if (!( ((mask & CA_WIZARD) && Wizard(player)) || ((mask & CA_ADMIN) && WizRoy(player)) || ((mask & CA_BUILDER) && Builder(player)) || ((mask & CA_STAFF) && Staff(player)) || ((mask & CA_HEAD) && Head(player)) || ((mask & CA_ANNOUNCE) && Announce(player)) || ((mask & CA_IMMORTAL) && Immortal(player)) || ((mask & CA_UNINS) && Uninspected(player)) || ((mask & CA_ROBOT) && Robot(player)))) { return false; } } // Check for forbidden flags. // if ( (mask & CA_CANTBE_MASK) && !Wizard(player)) { if ( ((mask & CA_NO_HAVEN) && Player_haven(player)) || ((mask & CA_NO_ROBOT) && Robot(player)) || ((mask & CA_NO_SLAVE) && Slave(player)) || ((mask & CA_NO_SUSPECT) && Suspect(player)) || ((mask & CA_NO_GUEST) && Guest(player)) || ((mask & CA_NO_UNINS) && Uninspected(player))) { return false; } } return true; } /***************************************************************************** * Process the various hook calls. * Idea taken from TinyMUSH3, code from RhostMUSH, ported by Jake Nelson. * Hooks processed: before, after, ignore, permit, fail *****************************************************************************/ static UTF8 *hook_name(const UTF8 *pCommand, int key) { const char *keylet; switch (key) { case CEF_HOOK_AFAIL: keylet = "AF"; break; case CEF_HOOK_AFTER: keylet = "A"; break; case CEF_HOOK_BEFORE: keylet = "B"; break; case CEF_HOOK_IGNORE: keylet = "I"; break; case CEF_HOOK_PERMIT: keylet = "P"; break; case CEF_HOOK_ARGS: keylet = "R"; break; default: return nullptr; } const UTF8 *cmdName = pCommand; if ( pCommand[0] && !pCommand[1]) { switch (pCommand[0]) { case '"' : cmdName = T("say"); break; case ':' : case ';' : cmdName = T("pose"); break; case '\\': cmdName = T("@emit"); break; case '#' : cmdName = T("@force"); break; case '&' : cmdName = T("@set"); break; case '-' : cmdName = T("@mail"); break; case '~' : cmdName = T("@mail"); break; } } return tprintf(T("%s_%s"), keylet, cmdName); } static bool process_hook(dbref executor, CMDENT *cmdp, int key, bool save_flg) { bool retval = true; ATTR *hk_attr = atr_str(hook_name(cmdp->cmdname, key)); if (hk_attr) { dbref aowner; int aflags; LBuf atext = LBuf_Adopt(atr_get("process_hook.1060", mudconf.hook_obj, hk_attr->number, &aowner, &aflags)); if (atext[0] && !(aflags & AF_NOPROG)) { if ((aflags & AF_NOEVAL) || NoEval(mudconf.hook_obj)) { // NOEVAL means "use this attribute's text literally", not // "this attribute has no opinion". Returning true here // discarded the content outright, so a NOEVAL permit or // ignore hook allowed every command -- a permission control // silently disabled, and indistinguishable from having no // hook configured at all (#1280). // // Derive the boolean from the unevaluated text instead. That // is what the rest of the NOEVAL work does: eval_boolexp, // added in the same commit (4cb904fcc) and the only other // boolean consumer in its list, compares the raw text rather // than short-circuiting. A literal "0" or "#-1 ..." hook now // blocks as written. // retval = xlate(atext); } else { reg_ref **preserve = nullptr; if (save_flg) { preserve = PushRegisters(MAX_GLOBAL_REGS); save_global_regs(preserve); } UTF8 *bufc; LBuf buff = LBuf_Src("process_hook"); bufc = buff; mux_exec(atext, LBUF_SIZE-1, buff, &bufc, mudconf.hook_obj, executor, executor, AttrTrace(aflags, EV_FCHECK|EV_EVAL), nullptr, 0); *bufc = '\0'; if (save_flg) { restore_global_regs(preserve); PopRegisters(preserve, MAX_GLOBAL_REGS); } retval = xlate(buff); } } } return retval; } void process_hook_args(dbref executor, CMDENT *cmdp, UTF8* arg1, UTF8* arg2, UTF8* args[], int nargs, UTF8* sw) { ATTR *hk_attr = atr_str(hook_name(cmdp->cmdname, CEF_HOOK_ARGS)); if (hk_attr) { dbref aowner; int aflags; LBuf atext = LBuf_Adopt(atr_get("process_hook_args.1093", mudconf.hook_obj, hk_attr->number, &aowner, &aflags)); if (atext[0] && !(aflags & AF_NOPROG)) { reg_ref **preserve = nullptr; preserve = PushRegisters(MAX_GLOBAL_REGS); save_global_regs(preserve); UTF8 *bufc = nullptr; UTF8* inargs[5]; if (arg1) { inargs[0] = alloc_lbuf("process_hook_args.1103"); mux_strncpy(inargs[0], arg1, LBUF_SIZE-1); } else { inargs[0] = const_cast(T("")); } if (arg2) { inargs[1] = alloc_lbuf("process_hook_args.1110"); mux_strncpy(inargs[1], arg2, LBUF_SIZE-1); } else { inargs[1] = const_cast(T("")); } inargs[2] = const_cast(T("")); inargs[4] = sw; if (arg1) { bufc = arg1; inargs[3] = const_cast(T("0")); mux_exec(atext, LBUF_SIZE-1, arg1, &bufc, mudconf.hook_obj, executor, executor, AttrTrace(aflags, EV_FCHECK|EV_EVAL), (const UTF8**)inargs, 5); } if (arg2) { bufc = arg2; inargs[3] = const_cast(T("1")); mux_exec(atext, LBUF_SIZE-1, arg2, &bufc, mudconf.hook_obj, executor, executor, AttrTrace(aflags, EV_FCHECK|EV_EVAL), (const UTF8**)inargs, 5); } if (nargs) { inargs[2] = alloc_lbuf("process_hook_args.1135"); UTF8 qbuff[I64BUF_SIZE]; inargs[3] = qbuff; for (int i = 0; i < nargs; i++) { mux_i64toa(i+2, qbuff); bufc = args[i]; mux_strncpy(inargs[2], args[i], LBUF_SIZE-1); mux_exec(atext, LBUF_SIZE-1, args[i], &bufc, mudconf.hook_obj, executor, executor, AttrTrace(aflags, EV_FCHECK|EV_EVAL), (const UTF8**)inargs, 5); } free_lbuf(inargs[2]); } if (arg2) free_lbuf(inargs[1]); if (arg1) free_lbuf(inargs[0]); if (nullptr != bufc) { *bufc = '\0'; } restore_global_regs(preserve); PopRegisters(preserve, MAX_GLOBAL_REGS); } } } /* --------------------------------------------------------------------------- * process_cmdent: Perform indicated command with passed args. */ static void process_cmdent(CMDENT *cmdp, UTF8 *switchp, dbref executor, dbref caller, dbref enactor, int eval, bool interactive, UTF8 *arg, UTF8 *unp_command, const UTF8 *cargs[], int ncargs) { // Perform object type checks. // if (Invalid_Objtype(executor)) { notify(executor, M_("Command incompatible with executor type.")); return; } // Check if we have permission to execute the command. // if (!check_access(executor, cmdp->perms)) { notify(executor, NOPERM_MESSAGE); return; } // Check global flags // if ( !Builder(executor) && Protect(CA_GBL_BUILD) && !(mudconf.control_flags & CF_BUILD)) { notify(executor, M_("Sorry, building is not allowed now.")); return; } if (Protect(CA_GBL_INTERP) && !(mudconf.control_flags & CF_INTERP)) { notify(executor, M_("Sorry, queueing and triggering are not allowed now.")); return; } UTF8 *buf1, *buf2, tchar, *bp, *str, *buff, *j, *new0; UTF8 *args[MAX_ARG]; UTF8* sw = switchp; int nargs, i, interp, key, xkey, aflags; dbref aowner; ADDENT *add; key = cmdp->extra & ~SW_MULTIPLE; if (key & SW_GOT_UNIQUE) { i = 1; key = key & ~SW_GOT_UNIQUE; } else { i = 0; } // Check command switches. Note that there may be more than one, // and that we OR all of them together along with the extra value // from the command table to produce the key value in the handler // call. // if (switchp && cmdp->switches) { do { buf1 = reinterpret_cast(strchr(reinterpret_cast(switchp), '/')); if (buf1) { *buf1++ = '\0'; } if (!search_nametab(executor, cmdp->switches, switchp, &xkey)) { if (xkey == -1) { notify(executor, tprintf(M_("Unrecognized switch ‘%s’ for command ‘%s’."), switchp, cmdp->cmdname)); return; } else if (xkey == -2) { notify(executor, NOPERM_MESSAGE); return; } } else if (!(xkey & SW_MULTIPLE)) { if (i == 1) { notify(executor, M_("Illegal combination of switches.")); return; } i = 1; } else { xkey &= ~SW_MULTIPLE; } key |= xkey; switchp = buf1; } while (buf1); } else if (switchp && !(cmdp->callseq & CS_ADDED)) { notify(executor, tprintf(M_("Command %s does not take switches."), cmdp->cmdname)); return; } bool bGoodHookObj = (Good_obj(mudconf.hook_obj) && !Going(mudconf.hook_obj)); // 'Before' hooks. // @hook idea from TinyMUSH 3, code from RhostMUSH. Ported by Jake Nelson. // if ( (cmdp->flags & CEF_HOOK_BEFORE) && bGoodHookObj) { process_hook(executor, cmdp, CEF_HOOK_BEFORE, false); } // We are allowed to run the command. Now, call the handler using // the appropriate calling sequence and arguments. // //if ((cmdp->callseq & CS_INTERP) && (key & SW_NOEVAL)) if (key & SW_NOEVAL) { // The user specified /noeval on a @pemit or a @npemit, // just do EV_STRIP_CURLY and remove the SW_NOEVAL from the // 'key'. // interp = 0; key &= ~SW_NOEVAL; } else if ( (cmdp->callseq & CS_INTERP) || !( interactive || (cmdp->callseq & CS_NOINTERP))) { // If the command is interpreted, or we're interactive (and // the command isn't specified CS_NOINTERP), eval the args. // interp = EV_EVAL | EV_STRIP_CURLY; } else if (cmdp->callseq & CS_STRIP) { interp = EV_STRIP_CURLY; } else if (cmdp->callseq & CS_STRIP_AROUND) { interp = EV_STRIP_AROUND; } else { interp = 0; } UTF8 *aargs[NUM_ENV_VARS]; int nargs2; switch (cmdp->callseq & CS_NARG_MASK) { case CS_NO_ARGS: // (no args) (*((reinterpret_cast(cmdp))->handler))(executor, caller, enactor, eval, key); break; case CS_ONE_ARG: // // If an unparsed command, just give it to the handler // // Interpret if necessary, but not twice for CS_ADDED. // if ((interp & EV_EVAL) && !(cmdp->callseq & CS_ADDED)) { buf1 = bp = alloc_lbuf("process_cmdent"); str = arg; // Copy and skip first character for CS_LEADIN, to // prevent the \ prefix command from escaping part of // its argument. // if ( (cmdp->callseq & CS_LEADIN) && UTF8_SIZE1 == utf8_FirstByte[static_cast(str[0])]) { UTF8 ch = *str++; *bp++ = ch; if ( mudconf.space_compress && ( ';' == ch || ':' == ch) && str[0] == ' ') { // Skip following space to prevent the parser from space // compressing it. // *bp++ = *str++; } } mux_exec(str, LBUF_SIZE-1, buf1, &bp, executor, caller, enactor, eval|interp|EV_FCHECK|EV_TOP, cargs, ncargs); *bp = '\0'; } else { buf1 = parse_to(&arg, '\0', interp | EV_TOP); } // Call the correct handler. // if (cmdp->callseq & CS_ADDED) { for (auto &addent : *cmdp->addent) { buff = atr_get("process_cmdent.1347", addent.thing, addent.atr, &aowner, &aflags); // Attribute should contain at least two characters and first // character is '$'. // if ( AMATCH_CMD != buff[0] || '\0' == buff[1]) { free_lbuf(buff); break; } // Skip the '$' character and the next to allow "$::cmd". // size_t iBuff; for (iBuff = 2; '\0' != buff[iBuff] && ':' != buff[iBuff]; iBuff++) { ; // Nothing. } if ('\0' == buff[iBuff]) { free_lbuf(buff); break; } buff[iBuff++] = '\0'; if (!(cmdp->callseq & CS_LEADIN)) { for (j = unp_command; *j && (*j != ' '); j++) { ; // Nothing. } } else { for (j = unp_command; *j; j++) { ; // Nothing. } } new0 = alloc_lbuf("process_cmdent.soft"); bp = new0; if (!*j) { // No args. // if (!(cmdp->callseq & CS_LEADIN)) { safe_str(cmdp->cmdname, new0, &bp); } else { safe_str(unp_command, new0, &bp); } if (switchp) { safe_chr('/', new0, &bp); safe_str(switchp, new0, &bp); } *bp = '\0'; } else { j++; safe_str(cmdp->cmdname, new0, &bp); if (switchp) { safe_chr('/', new0, &bp); safe_str(switchp, new0, &bp); } safe_chr(' ', new0, &bp); safe_str(j, new0, &bp); *bp = '\0'; } if ( ( (aflags & AF_REGEXP) && regexp_match(buff + 1, new0, ((aflags & AF_CASE) ? PCRE2_CASELESS : 0), aargs, NUM_ENV_VARS)) || ( (aflags & AF_REGEXP) == 0 && wild(buff + 1, new0, aargs, NUM_ENV_VARS))) { CLinearTimeAbsolute lta; wait_que(addent.thing, caller, executor, AttrTrace(aflags, 0), false, lta, NOTHING, 0, buff + iBuff, NUM_ENV_VARS, (const UTF8 **)aargs, mudstate.global_regs); for (i = 0; i < NUM_ENV_VARS; i++) { if (aargs[i]) { free_lbuf(aargs[i]); } } } free_lbuf(new0); free_lbuf(buff); } } else { if ((cmdp->flags & CEF_HOOK_ARGS) && bGoodHookObj) { process_hook_args(executor, cmdp, buf1, nullptr, nullptr, 0, sw); } (*((reinterpret_cast(cmdp))->handler))(executor, caller, enactor, eval, key, buf1, cargs, ncargs); } // Free the buffer if one was allocated. // if ((interp & EV_EVAL) && !(cmdp->callseq & CS_ADDED)) { free_lbuf(buf1); } break; case CS_TWO_ARG: // = // Interpret ARG1 // buf2 = parse_to(&arg, '=', EV_STRIP_TS); nargs2 = 0; if (buf2) { if (arg) { nargs2 = 2; } else { nargs2 = 1; } } // Handle when no '=' was specified. // if (!arg || (arg && !*arg)) { arg = &tchar; *arg = '\0'; } buf1 = bp = alloc_lbuf("process_cmdent.2"); mux_exec(buf2, LBUF_SIZE-1, buf1, &bp, executor, caller, enactor, eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL|EV_TOP, cargs, ncargs); *bp = '\0'; if (cmdp->callseq & CS_ARGV) { // Arg2 is ARGV style. Go get the args. // parse_arglist(executor, caller, enactor, arg, eval|interp|EV_STRIP_LS|EV_STRIP_TS, args, MAX_ARG, cargs, ncargs, &nargs); if ((cmdp->flags & CEF_HOOK_ARGS) && bGoodHookObj) { process_hook_args(executor, cmdp, buf1, nullptr, args, nargs, sw); } (*((reinterpret_cast(cmdp))->handler))(executor, caller, enactor, eval, key, buf1, args, nargs, cargs, ncargs); // Free the argument buffers. // for (i = 0; i < nargs; i++) { free_lbuf(args[i]); } } else { // Arg2 is normal style. Interpret if needed. // if (interp & EV_EVAL) { buf2 = bp = alloc_lbuf("process_cmdent.3"); mux_exec(arg, LBUF_SIZE-1, buf2, &bp, executor, caller, enactor, eval|interp|EV_FCHECK|EV_TOP, cargs, ncargs); *bp = '\0'; } else if (cmdp->callseq & CS_UNPARSE) { buf2 = parse_to(&arg, '\0', eval|interp|EV_TOP|EV_NO_COMPRESS); } else { buf2 = parse_to(&arg, '\0', eval|interp|EV_STRIP_LS|EV_STRIP_TS|EV_TOP); } if ((cmdp->flags & CEF_HOOK_ARGS) && bGoodHookObj) { process_hook_args(executor, cmdp, nargs2>=1?buf1:nullptr, nargs2>=2?buf2:nullptr, nullptr, 0, sw); } (*((reinterpret_cast(cmdp))->handler))(executor, caller, enactor, eval, key, nargs2, buf1, buf2, cargs, ncargs); // Free the buffer, if needed. // if (interp & EV_EVAL) { free_lbuf(buf2); } } // Free the buffer obtained by evaluating Arg1. // free_lbuf(buf1); break; } // 'After' hooks. // @hook idea from TinyMUSH 3, code from RhostMUSH. Ported by Jake Nelson. // if ( (cmdp->flags & CEF_HOOK_AFTER) && bGoodHookObj) { process_hook(executor, cmdp, CEF_HOOK_AFTER, false); } } static int cmdtest(dbref player, const UTF8 *cmd) { dbref aowner; int aflags; int rval = 0; LBuf buff1 = LBuf_Adopt(atr_get("cmdtest.1573", player, A_CMDCHECK, &aowner, &aflags)); if (nullptr == buff1.get()) return 0; const UTF8* pt1 = buff1; while (pt1 && *pt1) { const auto pt2 = reinterpret_cast(strchr(reinterpret_cast(pt1), ':')); if (!pt2 || (pt2 == pt1)) break; if (!strncmp(reinterpret_cast(pt2)+1, reinterpret_cast(cmd), strlen(reinterpret_cast(cmd)))) { if (*(pt2-1) == '1') rval = 1; else rval = 2; break; } pt1 = reinterpret_cast(strchr(reinterpret_cast(pt2) + 1, ' ')); if (pt1 && *pt1) { while (mux_isspace(*pt1)) { pt1++; } } } return rval; } static int zonecmdtest(dbref player, const UTF8 *cmd) { if (!Good_obj(player) || God(player)) { return 0; } dbref loc = Location(player); int i_ret = 0; if (Good_obj(loc)) { i_ret = cmdtest(loc, cmd); if (i_ret == 0) { dbref zone = Zone(loc); if ( Good_obj(zone) && ( isRoom(zone) || isThing(zone))) { i_ret = cmdtest(zone, cmd); } } } return i_ret; } // --------------------------------------------------------------------------- // handle_gmcp: Process an inbound GMCP message. // // The payload is "package[.sub] [json]". We fire the A_GMCP attribute // on the player with %0 = package, %1 = json data. // static void handle_gmcp(dbref player, const UTF8 *payload) { if (!Good_obj(player) || !isPlayer(player)) { return; } // Split payload into package and JSON at the first space. // const UTF8 *pSpace = payload; while (*pSpace && *pSpace != ' ') { pSpace++; } UTF8 pkg[SBUF_SIZE]; size_t nPkg = static_cast(pSpace - payload); if (nPkg >= SBUF_SIZE) nPkg = SBUF_SIZE - 1; memcpy(pkg, payload, nPkg); pkg[nPkg] = '\0'; const UTF8 *json = (*pSpace == ' ') ? pSpace + 1 : T(""); dbref aowner; int aflags; LBuf pGmcpAttr = LBuf_Adopt(atr_pget(player, A_GMCP, &aowner, &aflags)); if (pGmcpAttr[0] != '\0') { const UTF8 *gmcp_args[2]; gmcp_args[0] = pkg; gmcp_args[1] = json; LBuf lbuf = LBuf_Src("handle_gmcp"); UTF8 *lp = lbuf; mux_exec(pGmcpAttr, strlen(reinterpret_cast(pGmcpAttr.get())), lbuf, &lp, player, player, player, EV_FCHECK | EV_EVAL | EV_TOP, gmcp_args, 2); *lp = '\0'; } } // --------------------------------------------------------------------------- // process_command: Execute a command. // UTF8 *process_command ( dbref executor, dbref caller, dbref enactor, int eval, bool interactive, UTF8 *arg_command, const UTF8 *args[], int nargs ) { thread_local UTF8 preserve_cmd[LBUF_SIZE]; UTF8 *pOriginalCommand = arg_command; thread_local UTF8 SpaceCompressCommand[LBUF_SIZE]; thread_local UTF8 LowerCaseCommand[LBUF_SIZE]; // Robustify player. // const UTF8 *cmdsave = g_debug_cmd; g_debug_cmd = T("< process_command >"); mudstate.nStackNest = 0; mudstate.bStackLimitReached = false; mux_assert(pOriginalCommand); if (!Good_obj(executor)) { STARTLOG(LOG_BUGS, T("CMD"), T("PLYR")); log_printf(T("Bad player in process_command: %d"), executor); ENDLOG; g_debug_cmd = cmdsave; return pOriginalCommand; } // Make sure player isn't going or halted. // if ( Going(executor) || ( Halted(executor) && !( isPlayer(executor) && interactive))) { notify(Owner(executor), tprintf(T("Attempt to execute command by halted object #%d"), executor)); g_debug_cmd = cmdsave; return pOriginalCommand; } // Intercept internal GMCP messages from the telnet layer. // Format: "\x01GMCP package [json]" // if ( '\x01' == pOriginalCommand[0] && memcmp(pOriginalCommand + 1, "GMCP ", 5) == 0 && interactive) { handle_gmcp(executor, pOriginalCommand + 6); g_debug_cmd = cmdsave; return pOriginalCommand; } if ( Suspect(executor) && (mudconf.log_options & LOG_SUSPECTCMDS)) { STARTLOG(LOG_SUSPECTCMDS, T("CMD"), T("SUSP")); log_name_and_loc(executor); log_text(T(" entered: ")); log_text(pOriginalCommand); ENDLOG; } else { STARTLOG(LOG_ALLCOMMANDS, T("CMD"), T("ALL")); log_name_and_loc(executor); log_text(T(" entered: ")); log_text(pOriginalCommand); ENDLOG; } // Reset recursion limits. // // Note: include_nest_lev is intentionally not reset here — it tracks // recursive process_command_list_inline depth across nested // @include/@dolist/now invocations and is balanced by that path. // mudstate.func_nest_lev = 0; mudstate.func_invk_ctr = 0; mudstate.ntfy_nest_lev = 0; mudstate.lock_nest_lev = 0; if (Verbose(executor)) { notify(Owner(executor), tprintf(T("%s] %s"), Name(executor), pOriginalCommand)); } // Eat leading whitespace, and space-compress if configured. // while (mux_isspace(*pOriginalCommand)) { pOriginalCommand++; } mux_strncpy(preserve_cmd, pOriginalCommand, sizeof(preserve_cmd)-1); g_debug_cmd = pOriginalCommand; mudstate.curr_cmd = preserve_cmd; UTF8 *pCommand; UTF8 *p, *q, *arg, *bp; int aflags; dbref exit, aowner; if (mudconf.space_compress) { // Compress out the spaces and use that as the command. // Leave room for the trailing NUL: writing up to LBUF_SIZE // then `*q = 0` was one past the end for a full LBUF of // non-space (WebSocket can deliver LBUF-sized text; #1819). // pCommand = SpaceCompressCommand; p = pOriginalCommand; q = SpaceCompressCommand; UTF8 *const qend = SpaceCompressCommand + (LBUF_SIZE - 1); while ( *p && q < qend) { while ( *p && !mux_isspace(*p) && q < qend) { *q++ = *p++; } while (mux_isspace(*p)) { p++; } if ( *p && q < qend) { *q++ = ' '; } } *q = '\0'; } else { // Don't compress the spaces. Use the original command // (without leading spaces) as the command to use. // pCommand = pOriginalCommand; } // Now comes the fun stuff. First check for single-letter leadins. // We check these before checking HOME because they are among the // most frequently executed commands, and they can never be the // HOME command. // int cval = 0; int hval = 0; bool bGoodHookObj = (Good_obj(mudconf.hook_obj) && !Going(mudconf.hook_obj)); size_t nPrefix = 0; CMDENT *cmdp = nullptr; if (is_prefix_cmd(pCommand, &nPrefix, &cmdp)) { // CmdCheck tests for @icmd. higcheck tests for i/p hooks. // Both from RhostMUSH. // cval/hval values: 0 normal, 1 disable, 2 ignore // UTF8 check2[UTF8_SIZE4+1]; memcpy(check2, pCommand, nPrefix); check2[nPrefix] = '\0'; if (CmdCheck(executor)) { cval = cmdtest(executor, check2); } else if (CmdCheck(Owner(executor))) { cval = cmdtest(Owner(executor), check2); } else { cval = 0; } if (cval == 0) { cval = zonecmdtest(executor, check2); } hval = 0; if ( (cmdp->flags & (CEF_HOOK_IGNORE|CEF_HOOK_PERMIT)) && bGoodHookObj) { if ( (cmdp->flags & CEF_HOOK_IGNORE) && !process_hook(executor, cmdp, CEF_HOOK_IGNORE, true)) { hval = 2; } else if ( (cmdp->flags & CEF_HOOK_PERMIT) && !process_hook(executor, cmdp, CEF_HOOK_PERMIT, true)) { hval = 1; } } if ( cval != 2 && hval != 2) { if ( cval == 1 || hval == 1) { if ( (cmdp->flags & CEF_HOOK_AFAIL) && bGoodHookObj) { process_hook(executor, cmdp, CEF_HOOK_AFAIL, false); } else { notify(executor, NOPERM_MESSAGE); } g_debug_cmd = cmdsave; return preserve_cmd; } process_cmdent(cmdp, nullptr, executor, caller, enactor, eval, interactive, pCommand, pCommand, args, nargs); if (mudstate.bStackLimitReached) { STARTLOG(LOG_ALWAYS, T("CMD"), T("SPAM")); log_name_and_loc(executor); log_text(T(" entered: ")); log_text(pOriginalCommand); ENDLOG; } mudstate.bStackLimitReached = false; g_debug_cmd = cmdsave; return preserve_cmd; } } // Talk mode: if the player has TALKMODE set and this is interactive // input, treat unprefix'd input as 'say'. A leading '.' escapes // to normal command processing. // if ( interactive && isPlayer(executor) && TalkMode(executor)) { if ('.' == pCommand[0]) { // Strip the dot and fall through to normal command // processing. // pCommand++; // If the dot was the only character, ignore the empty // command. // if ('\0' == *pCommand) { g_debug_cmd = cmdsave; return preserve_cmd; } } else { // Route to say. // do_say(executor, caller, enactor, eval, SAY_SAY, pCommand, args, nargs); g_debug_cmd = cmdsave; return preserve_cmd; } } if (!Slave(executor)) { bool bHandled = false; if (nullptr != mudstate.pIComsysControl) { mudstate.pIComsysControl->ProcessCommand(executor, pCommand, &bHandled); } else { bHandled = !do_comsystem(executor, pCommand); } if (bHandled) { g_debug_cmd = cmdsave; return preserve_cmd; } } // Check for the HOME command. // if ( Has_location(executor) && string_compare(pCommand, T("home")) == 0) { // CmdCheck tests for @icmd. higcheck tests for i/p hooks. // Both from RhostMUSH. // cval/hval values: 0 normal, 1 disable, 2 ignore. // if (CmdCheck(executor)) { cval = cmdtest(executor, T("home")); } else if (CmdCheck(Owner(executor))) { cval = cmdtest(Owner(executor), T("home")); } else { cval = 0; } if (cval == 0) { cval = zonecmdtest(executor, T("home")); } if (cval != 2) { if (!check_access(executor, mudconf.restrict_home)) { notify(executor, NOPERM_MESSAGE); g_debug_cmd = cmdsave; return preserve_cmd; } if (cval == 1) { notify(executor, NOPERM_MESSAGE); g_debug_cmd = cmdsave; return preserve_cmd; } if ( ( Fixed(executor) || Fixed(Owner(executor))) && !WizRoy(executor)) { notify(executor, mudconf.fixed_home_msg); g_debug_cmd = cmdsave; return preserve_cmd; } do_move(executor, caller, enactor, eval, 0, const_cast(T("home")), nullptr, 0); g_debug_cmd = cmdsave; return preserve_cmd; } } // Only check for exits if we may use the goto command. // if (check_access(executor, goto_cmdp->perms)) { // CmdCheck tests for @icmd. higcheck tests for i/p hooks. // Both from RhostMUSH. // cval/hval values: 0 normal, 1 disable, 2 ignore // Master room exits are not affected. // if (CmdCheck(executor)) { cval = cmdtest(executor, T("goto")); } else if (CmdCheck(Owner(executor))) { cval = cmdtest(Owner(executor), T("goto")); } else { cval = 0; } if (cval == 0) { cval = zonecmdtest(executor, T("goto")); } hval = 0; if ( (goto_cmdp->flags & (CEF_HOOK_IGNORE|CEF_HOOK_PERMIT)) && bGoodHookObj) { if ( (goto_cmdp->flags & CEF_HOOK_IGNORE) && !process_hook(executor, goto_cmdp, CEF_HOOK_IGNORE, true)) { hval = 2; } else if ( (goto_cmdp->flags & CEF_HOOK_PERMIT) && !process_hook(executor, goto_cmdp, CEF_HOOK_PERMIT, true)) { hval = 1; } } if ( cval != 2 && hval != 2) { // Check for an exit name. // init_match_check_keys(executor, pCommand, TYPE_EXIT); match_exit_with_parents(); exit = last_match_result(); bool bMaster = (NOTHING == exit); if (bMaster) { // Check for an exit in the master room. // init_match_check_keys(executor, pCommand, TYPE_EXIT); match_master_exit(); exit = last_match_result(); } if (exit != NOTHING) { if ( 1 == cval || 1 == hval) { if ( (goto_cmdp->flags & CEF_HOOK_AFAIL) && bGoodHookObj) { process_hook(executor, goto_cmdp, CEF_HOOK_AFAIL, false); } else { notify(executor, NOPERM_MESSAGE); } g_debug_cmd = cmdsave; return preserve_cmd; } if ( (goto_cmdp->flags & CEF_HOOK_BEFORE) && bGoodHookObj) { process_hook(executor, goto_cmdp, CEF_HOOK_BEFORE, false); } if (!bMaster) { move_exit(executor, exit, false, T("You can’t go that way."), 0); } else { move_exit(executor, exit, true, nullptr, 0); } if ( (goto_cmdp->flags & CEF_HOOK_AFTER) && bGoodHookObj) { process_hook(executor, goto_cmdp, CEF_HOOK_AFTER, false); } g_debug_cmd = cmdsave; return preserve_cmd; } } } // Set up a lowercase command and an arg pointer for the hashed // command check. Since some types of argument processing destroy // the arguments, make a copy so that we keep the original command // line intact. Store the edible copy in LowerCaseCommand after // the lower-cased command. // // Make lowercase command (Unicode-aware). // size_t iPos = 0; while ( pCommand[iPos] && !mux_isspace(pCommand[iPos])) { iPos++; } LBuf TempCmd = LBuf_Src("process_command"); size_t nCopy = iPos; if (nCopy >= LBUF_SIZE) { nCopy = LBUF_SIZE - 1; } memcpy(TempCmd, pCommand, nCopy); TempCmd[nCopy] = '\0'; size_t nLowerCaseCommand; UTF8 *pLower = mux_strlwr(TempCmd, nLowerCaseCommand); if (nLowerCaseCommand >= LBUF_SIZE) { nLowerCaseCommand = LBUF_SIZE - 1; } memcpy(LowerCaseCommand, pLower, nLowerCaseCommand); LowerCaseCommand[nLowerCaseCommand] = '\0'; // Skip spaces before arg // while (mux_isspace(pCommand[iPos])) { iPos++; } // Remember where arg starts // arg = pCommand + iPos; // Strip off any command switches and save them. // UTF8 *pSlash = reinterpret_cast(strchr(reinterpret_cast(LowerCaseCommand), '/')); if (pSlash) { nLowerCaseCommand = pSlash - LowerCaseCommand; *pSlash++ = '\0'; } // Check for a builtin command (or an alias of a builtin command) // { auto it_cmd = mudstate.command_htab.find(std::vector(LowerCaseCommand, LowerCaseCommand + nLowerCaseCommand)); cmdp = (it_cmd != mudstate.command_htab.end()) ? static_cast(it_cmd->second) : nullptr; } /* If command is checked to ignore NONMATCHING switches, fall through */ if (cmdp) { // CmdCheck tests for @icmd. higcheck tests for i/p hooks. // Both from RhostMUSH. // cval/hval values: 0 normal, 1 disable, 2 ignore // if (CmdCheck(executor)) { cval = cmdtest(executor, cmdp->cmdname); } else if (CmdCheck(Owner(executor))) { cval = cmdtest(Owner(executor), cmdp->cmdname); } else { cval = 0; } if (cval == 0) { cval = zonecmdtest(executor, cmdp->cmdname); } hval = 0; if ( (cmdp->flags & (CEF_HOOK_IGNORE|CEF_HOOK_PERMIT)) && bGoodHookObj) { if ( (cmdp->flags & CEF_HOOK_IGNORE) && !process_hook(executor, cmdp, CEF_HOOK_IGNORE, true)) { hval = 2; } else if ( (cmdp->flags & CEF_HOOK_PERMIT) && !process_hook(executor, cmdp, CEF_HOOK_PERMIT, true)) { hval = 1; } } // If the command contains a switch, but the command doesn't support // any switches or the command contains one that isn't supported, // CEF_HOOK_IGSWITCH will allow us to treat the entire command as if it // weren't a built-in command. // int flagvalue; if ( (cmdp->flags & CEF_HOOK_IGSWITCH) && pSlash) { if (cmdp->switches) { search_nametab(executor, cmdp->switches, pSlash, &flagvalue); if (flagvalue & SW_MULTIPLE) { // All the switches given a command shouldn't exceed 200 chars together UTF8 switch_buff[200]; UTF8 *switch_ptr; mux_strncpy(switch_buff, pSlash, sizeof(switch_buff)-1); string_token st(switch_buff, T("/")); switch_ptr = st.parse(); while ( switch_ptr && *switch_ptr) { search_nametab(executor, cmdp->switches, switch_ptr, &flagvalue); if (flagvalue == -1) { break; } switch_ptr = st.parse(); } } if (flagvalue == -1) { cval = 2; } } else { // Switch exists but no switches allowed for command. // cval = 2; } } if ( cval != 2 && hval != 2) { if ( cval == 1 || hval == 1) { if ( (cmdp->flags & CEF_HOOK_AFAIL) && bGoodHookObj) { process_hook(executor, cmdp, CEF_HOOK_AFAIL, false); } else { notify(executor, NOPERM_MESSAGE); } g_debug_cmd = cmdsave; return preserve_cmd; } if ( mudconf.space_compress && (cmdp->callseq & CS_NOSQUISH)) { // We handle this specially -- there is no space compression // involved, so we must go back to the original command. // We skip over the command and a single space to position // arg at the arguments. // arg = pCommand = pOriginalCommand; while (*arg && !mux_isspace(*arg)) { arg++; } if (*arg) { // We stopped on the space, advance to next. // arg++; } } process_cmdent(cmdp, pSlash, executor, caller, enactor, eval, interactive, arg, pCommand, args, nargs); if (mudstate.bStackLimitReached) { STARTLOG(LOG_ALWAYS, T("CMD"), T("SPAM")); log_name_and_loc(executor); log_text(T(" entered: ")); log_text(pOriginalCommand); ENDLOG; } mudstate.bStackLimitReached = false; g_debug_cmd = cmdsave; return preserve_cmd; } } // Check for enter and leave aliases, user-defined commands on the // player, other objects where the player is, on objects in the // player's inventory, and on the room that holds the player. We // evaluate the command line here to allow chains of $-commands // to work. // bp = LowerCaseCommand; mux_exec(pCommand, LBUF_SIZE-1, LowerCaseCommand, &bp, executor, caller, enactor, eval|EV_EVAL|EV_FCHECK|EV_STRIP_CURLY|EV_TOP, args, nargs); *bp = '\0'; bool succ = false; // Idea for enter/leave aliases from R'nice@TinyTIM // if (Has_location(executor) && Good_obj(Location(executor))) { // Check for a leave alias. // p = atr_pget(Location(executor), A_LALIAS, &aowner, &aflags); if (*p) { if (matches_exit_from_list(LowerCaseCommand, p)) { free_lbuf(p); // CmdCheck tests for @icmd. higcheck tests for i/p hooks. // Both from RhostMUSH. // cval/hval values: 0 normal, 1 disable, 2 ignore // if (CmdCheck(executor)) { cval = cmdtest(executor, T("leave")); } else if (CmdCheck(Owner(executor))) { cval = cmdtest(Owner(executor), T("leave")); } else { cval = 0; } if (cval == 0) { cval = zonecmdtest(executor, T("leave")); } { const UTF8 *tmp_leave = reinterpret_cast("leave"); auto it_leave = mudstate.command_htab.find(std::vector(tmp_leave, tmp_leave + 5)); cmdp = (it_leave != mudstate.command_htab.end()) ? static_cast(it_leave->second) : nullptr; } hval = 0; if ( (cmdp->flags & (CEF_HOOK_IGNORE|CEF_HOOK_PERMIT)) && bGoodHookObj) { if ( (cmdp->flags & CEF_HOOK_IGNORE) && !process_hook(executor, cmdp, CEF_HOOK_IGNORE, true)) { hval = 2; } else if ( (cmdp->flags & CEF_HOOK_PERMIT) && !process_hook(executor, cmdp, CEF_HOOK_PERMIT, true)) { hval = 1; } } if ( cval != 2 && hval != 2) { if ( cval == 1 || hval == 1) { if ( (cmdp->flags & CEF_HOOK_AFAIL) && bGoodHookObj) { process_hook(executor, cmdp, CEF_HOOK_AFAIL, false); } else { notify(executor, NOPERM_MESSAGE); } g_debug_cmd = cmdsave; return preserve_cmd; } if ( (cmdp->flags & CEF_HOOK_BEFORE) && bGoodHookObj) { process_hook(executor, cmdp, CEF_HOOK_BEFORE, false); } do_leave(executor, caller, executor, 0, 0); if ( (cmdp->flags & CEF_HOOK_AFTER) && bGoodHookObj) { process_hook(executor, cmdp, CEF_HOOK_AFTER, false); } g_debug_cmd = cmdsave; return preserve_cmd; } } } free_lbuf(p); DOLIST(exit, Contents(Location(executor))) { p = atr_pget(exit, A_EALIAS, &aowner, &aflags); if (*p) { if (matches_exit_from_list(LowerCaseCommand, p)) { free_lbuf(p); // Check for enter aliases. // // CmdCheck tests for @icmd. higcheck tests for i/p hooks. // Both from RhostMUSH. // cval/hval values: 0 normal, 1 disable, 2 ignore // if (CmdCheck(executor)) { cval = cmdtest(executor, T("enter")); } else if (CmdCheck(Owner(executor))) { cval = cmdtest(Owner(executor), T("enter")); } else { cval = 0; } if (cval == 0) { cval = zonecmdtest(executor, T("enter")); } { const UTF8 *tmp_enter = reinterpret_cast("enter"); auto it_enter = mudstate.command_htab.find(std::vector(tmp_enter, tmp_enter + 5)); cmdp = (it_enter != mudstate.command_htab.end()) ? static_cast(it_enter->second) : nullptr; } hval = 0; if ( (cmdp->flags & (CEF_HOOK_IGNORE|CEF_HOOK_PERMIT)) && bGoodHookObj) { if ( (cmdp->flags & CEF_HOOK_IGNORE) && !process_hook(executor, cmdp, CEF_HOOK_IGNORE, true)) { hval = 2; } else if ( (cmdp->flags & CEF_HOOK_PERMIT) && !process_hook(executor, cmdp, CEF_HOOK_PERMIT, true)) { hval = 1; } } if ( cval != 2 && hval != 2) { if ( cval == 1 || hval == 1) { if ( (cmdp->flags & CEF_HOOK_AFAIL) && bGoodHookObj) { process_hook(executor, cmdp, CEF_HOOK_AFAIL, false); } else { notify(executor, NOPERM_MESSAGE); } g_debug_cmd = cmdsave; return preserve_cmd; } if ( (cmdp->flags & CEF_HOOK_BEFORE) && bGoodHookObj) { process_hook(executor, cmdp, CEF_HOOK_BEFORE, false); } do_enter_internal(executor, exit, false); if ( (cmdp->flags & CEF_HOOK_AFTER) && bGoodHookObj) { process_hook(executor, cmdp, CEF_HOOK_AFTER, false); } g_debug_cmd = cmdsave; return preserve_cmd; } else if (cval == 1) { notify_quiet(executor, NOPERM_MESSAGE); g_debug_cmd = cmdsave; return preserve_cmd; } } } free_lbuf(p); } } // Check for $-command matches on me. // if (mudconf.match_mine && !No_Command(executor)) { if ( ( !isPlayer(executor) || mudconf.match_mine_pl) && atr_match(executor, executor, AMATCH_CMD, LowerCaseCommand, preserve_cmd, true)) { succ = true; } } // Check for $-command matches on nearby things and on my room. // if (Has_location(executor)) { succ |= list_check(Contents(Location(executor)), executor, AMATCH_CMD, LowerCaseCommand, preserve_cmd, true); if (!No_Command(Location(executor))) { succ |= atr_match(Location(executor), executor, AMATCH_CMD, LowerCaseCommand, preserve_cmd, true); } } // Check for $-command matches in my inventory. // if (Has_contents(executor)) { succ |= list_check(Contents(executor), executor, AMATCH_CMD, LowerCaseCommand, preserve_cmd, true); } if ( !succ && mudconf.have_zones) { // now do check on zones. // dbref zone = Zone(executor); dbref loc = Location(executor); dbref zone_loc = NOTHING; if ( Good_obj(loc) && Good_obj(zone_loc = Zone(loc))) { if (isRoom(zone_loc)) { // zone of player's location is a parent room. // if (loc != zone) { // check parent room exits. // init_match_check_keys(executor, pCommand, TYPE_EXIT); match_zone_exit(); exit = last_match_result(); if (exit != NOTHING) { move_exit(executor, exit, true, nullptr, 0); g_debug_cmd = cmdsave; return preserve_cmd; } succ |= list_check(Contents(zone_loc), executor, AMATCH_CMD, LowerCaseCommand, preserve_cmd, true); // end of parent room checks. // } } else { // try matching commands on area zone object. // if (!No_Command(zone_loc)) { succ |= atr_match(zone_loc, executor, AMATCH_CMD, LowerCaseCommand, preserve_cmd, true); } } } // End of matching on zone of player's location. // // if nothing matched with parent room/zone object, try matching // zone commands on the player's personal zone. // if ( !succ && Good_obj(zone) && !No_Command(zone) && zone_loc != zone) { succ |= atr_match(zone, executor, AMATCH_CMD, LowerCaseCommand, preserve_cmd, true); } } // If we didn't find anything, try in the master room. // if (!succ) { if ( Good_obj(mudconf.master_room) && Has_contents(mudconf.master_room)) { succ |= list_check(Contents(mudconf.master_room), executor, AMATCH_CMD, LowerCaseCommand, preserve_cmd, false); if (!No_Command(mudconf.master_room)) { succ |= atr_match(mudconf.master_room, executor, AMATCH_CMD, LowerCaseCommand, preserve_cmd, false); } } } // If we still didn't find anything, tell how to get help. // if (!succ) { if ( Good_obj(mudconf.global_error_obj) && !Going(mudconf.global_error_obj)) { LBuf errtext = LBuf_Adopt(atr_get("process_command.2491", mudconf.global_error_obj, A_VA, &aowner, &aflags)); LBuf errbuff = LBuf_Src("process_command.error_msg"); UTF8 *errbufc = errbuff; mux_exec(errtext, LBUF_SIZE-1, errbuff, &errbufc, mudconf.global_error_obj, caller, enactor, AttrTrace(aflags, EV_EVAL|EV_FCHECK|EV_STRIP_CURLY|EV_TOP), (const UTF8 **)&pCommand, 1); *errbufc = '\0'; notify(executor, errbuff); } else { // We use LowerCaseCommand for another purpose. // notify(executor, M_("Huh? (Type “help” for help.)")); STARTLOG(LOG_BADCOMMANDS, "CMD", "BAD"); log_name_and_loc(executor); log_text(T(" entered: ")); log_text(pCommand); ENDLOG; } } g_debug_cmd = cmdsave; return preserve_cmd; } // --------------------------------------------------------------------------- // list_cmdtable: List internal commands. // static void list_cmdtable(dbref player) { LBuf buf = LBuf_Src("list_cmdtable"); UTF8 *bp = buf; ITL itl; ItemToList_Init(&itl, buf, &bp); ItemToList_AddString(&itl, T("Commands:")); for ( CMDENT_NO_ARG *cmdp0a = command_table_no_arg; cmdp0a->cmdname; cmdp0a++) { if ( check_access(player, cmdp0a->perms) && !(cmdp0a->perms & CF_DARK)) { ItemToList_AddString(&itl, cmdp0a->cmdname); } } for ( CMDENT_ONE_ARG *cmdp1a = command_table_one_arg; cmdp1a->cmdname; cmdp1a++) { if ( check_access(player, cmdp1a->perms) && !(cmdp1a->perms & CF_DARK)) { ItemToList_AddString(&itl, cmdp1a->cmdname); } } for ( CMDENT_TWO_ARG *cmdp2a = command_table_two_arg; cmdp2a->cmdname; cmdp2a++) { if ( check_access(player, cmdp2a->perms) && !(cmdp2a->perms & CF_DARK)) { ItemToList_AddString(&itl, cmdp2a->cmdname); } } for ( CMDENT_TWO_ARG_ARGV *cmdp2av = command_table_two_arg_argv; cmdp2av->cmdname; cmdp2av++) { if ( check_access(player, cmdp2av->perms) && !(cmdp2av->perms & CF_DARK)) { ItemToList_AddString(&itl, cmdp2av->cmdname); } } // Players get the list of logged-out cmds too // if (isPlayer(player)) { for (NAMETAB *nt = logout_cmdtable; nt->name; nt++) { if ( God(player) || check_access(player, nt->perm)) { ItemToList_AddString(&itl, nt->name); } } } ItemToList_Final(&itl); notify(player, buf); } // --------------------------------------------------------------------------- // list_attrtable: List available attributes. // static void list_attrtable(dbref player) { LBuf buf = LBuf_Src("list_attrtable"); UTF8 *bp = buf; ITL itl; ItemToList_Init(&itl, buf, &bp); ItemToList_AddString(&itl, T("Attributes:")); for (ATTR *ap = AttrTable; ap->name; ap++) { if (See_attr(player, player, ap)) { ItemToList_AddString(&itl, ap->name); } } ItemToList_Final(&itl); *bp = '\0'; raw_notify(player, buf); } // --------------------------------------------------------------------------- // list_cmdaccess: List access commands. // NAMETAB access_nametab[] = { {T("builder"), 6, CA_WIZARD, CA_BUILDER}, {T("dark"), 4, CA_GOD, CF_DARK}, {T("disabled"), 4, CA_GOD, CA_DISABLED}, {T("global_build"), 8, CA_PUBLIC, CA_GBL_BUILD}, {T("global_interp"), 8, CA_PUBLIC, CA_GBL_INTERP}, {T("god"), 2, CA_GOD, CA_GOD}, {T("head"), 2, CA_WIZARD, CA_HEAD}, {T("immortal"), 3, CA_WIZARD, CA_IMMORTAL}, {T("need_location"), 6, CA_PUBLIC, CA_LOCATION}, {T("need_contents"), 6, CA_PUBLIC, CA_CONTENTS}, {T("need_player"), 6, CA_PUBLIC, CA_PLAYER}, {T("no_haven"), 4, CA_PUBLIC, CA_NO_HAVEN}, {T("no_robot"), 4, CA_WIZARD, CA_NO_ROBOT}, {T("no_slave"), 5, CA_PUBLIC, CA_NO_SLAVE}, {T("no_suspect"), 5, CA_WIZARD, CA_NO_SUSPECT}, {T("no_guest"), 5, CA_WIZARD, CA_NO_GUEST}, {T("no_uninspected"), 5, CA_WIZARD, CA_NO_UNINS}, {T("robot"), 2, CA_WIZARD, CA_ROBOT}, {T("staff"), 4, CA_WIZARD, CA_STAFF}, {T("static"), 4, CA_GOD, CA_STATIC}, {T("uninspected"), 5, CA_WIZARD, CA_UNINS}, {T("wizard"), 3, CA_WIZARD, CA_WIZARD}, {nullptr, 0, 0, 0} }; static void list_cmdaccess(dbref player) { for ( CMDENT_NO_ARG *cmdp0a = command_table_no_arg; cmdp0a->cmdname; cmdp0a++) { if ( check_access(player, cmdp0a->perms) && !(cmdp0a->perms & CF_DARK)) { listset_nametab(player, access_nametab, cmdp0a->perms, cmdp0a->cmdname, true); } } for ( CMDENT_ONE_ARG *cmdp1a = command_table_one_arg; cmdp1a->cmdname; cmdp1a++) { if ( check_access(player, cmdp1a->perms) && !(cmdp1a->perms & CF_DARK)) { listset_nametab(player, access_nametab, cmdp1a->perms, cmdp1a->cmdname, true); } } for ( CMDENT_TWO_ARG *cmdp2a = command_table_two_arg; cmdp2a->cmdname; cmdp2a++) { if ( check_access(player, cmdp2a->perms) && !(cmdp2a->perms & CF_DARK)) { listset_nametab(player, access_nametab, cmdp2a->perms, cmdp2a->cmdname, true); } } for ( CMDENT_TWO_ARG_ARGV *cmdp2av = command_table_two_arg_argv; cmdp2av->cmdname; cmdp2av++) { if ( check_access(player, cmdp2av->perms) && !(cmdp2av->perms & CF_DARK)) { listset_nametab(player, access_nametab, cmdp2av->perms, cmdp2av->cmdname, true); } } for (ATTR *ap = AttrTable; ap->name; ap++) { if (ap->flags & AF_NOCMD) { continue; } size_t nBuffer; bool bValid; UTF8 *buff2 = MakeCanonicalAttributeCommand(ap->name, &nBuffer, &bValid); if (!bValid) { continue; } auto it_b2 = mudstate.command_htab.find(std::vector(buff2, buff2 + nBuffer)); CMDENT *cmdp = (it_b2 != mudstate.command_htab.end()) ? static_cast(it_b2->second) : nullptr; if ( nullptr != cmdp && check_access(player, cmdp->perms) && !(cmdp->perms & CF_DARK)) { listset_nametab(player, access_nametab, cmdp->perms, cmdp->cmdname, true); } } } // --------------------------------------------------------------------------- // list_cmdswitches: List switches for commands. // static void list_cmdswitches(dbref player) { for ( CMDENT_NO_ARG *cmdp0a = command_table_no_arg; cmdp0a->cmdname; cmdp0a++) { if ( cmdp0a->switches && check_access(player, cmdp0a->perms) && !(cmdp0a->perms & CF_DARK)) { display_nametab(player, cmdp0a->switches, cmdp0a->cmdname, false); } } for ( CMDENT_ONE_ARG *cmdp1a = command_table_one_arg; cmdp1a->cmdname; cmdp1a++) { if ( cmdp1a->switches && check_access(player, cmdp1a->perms) && !(cmdp1a->perms & CF_DARK)) { display_nametab(player, cmdp1a->switches, cmdp1a->cmdname, false); } } for ( CMDENT_TWO_ARG *cmdp2a = command_table_two_arg; cmdp2a->cmdname; cmdp2a++) { if ( cmdp2a->switches && check_access(player, cmdp2a->perms) && !(cmdp2a->perms & CF_DARK)) { display_nametab(player, cmdp2a->switches, cmdp2a->cmdname, false); } } for ( CMDENT_TWO_ARG_ARGV *cmdp2av = command_table_two_arg_argv; cmdp2av->cmdname; cmdp2av++) { if ( cmdp2av->switches && check_access(player, cmdp2av->perms) && !(cmdp2av->perms & CF_DARK)) { display_nametab(player, cmdp2av->switches, cmdp2av->cmdname, false); } } } // --------------------------------------------------------------------------- // list_attraccess: List access to attributes. // NAMETAB attraccess_nametab[] = { {T("const"), 1, CA_PUBLIC, AF_CONST}, {T("dark"), 2, CA_WIZARD, AF_DARK}, {T("deleted"), 2, CA_WIZARD, AF_DELETED}, {T("god"), 1, CA_PUBLIC, AF_GOD}, {T("hidden"), 1, CA_WIZARD, AF_MDARK}, {T("ignore"), 2, CA_WIZARD, AF_NOCMD}, {T("internal"), 2, CA_WIZARD, AF_INTERNAL}, {T("is_lock"), 4, CA_PUBLIC, AF_IS_LOCK}, {T("locked"), 1, CA_PUBLIC, AF_LOCK}, {T("no_command"), 4, CA_PUBLIC, AF_NOPROG}, {T("no_inherit"), 4, CA_PUBLIC, AF_PRIVATE}, {T("private"), 1, CA_PUBLIC, AF_ODARK}, {T("regexp"), 1, CA_PUBLIC, AF_REGEXP}, {T("visual"), 1, CA_PUBLIC, AF_VISUAL}, {T("wizard"), 1, CA_PUBLIC, AF_WIZARD}, {static_cast(nullptr), 0, 0, 0} }; NAMETAB indiv_attraccess_nametab[] = { {T("case"), 1, CA_PUBLIC, AF_CASE}, {T("hidden"), 1, CA_WIZARD, AF_MDARK}, {T("html"), 2, CA_PUBLIC, AF_HTML}, {T("no_command"), 4, CA_PUBLIC, AF_NOPROG}, {T("no_inherit"), 4, CA_PUBLIC, AF_PRIVATE}, {T("no_eval"), 4, CA_PUBLIC, AF_NOEVAL}, {T("no_name"), 4, CA_PUBLIC, AF_NONAME}, {T("no_parse"), 4, CA_PUBLIC, AF_NOPARSE}, {T("regexp"), 1, CA_PUBLIC, AF_REGEXP}, {T("trace"), 1, CA_PUBLIC, AF_TRACE}, {T("visual"), 1, CA_PUBLIC, AF_VISUAL}, {T("wizard"), 1, CA_WIZARD, AF_WIZARD}, {static_cast(nullptr), 0, 0, 0} }; static void list_attraccess(dbref player) { for (ATTR *ap = AttrTable; ap->name; ap++) { if (bCanReadAttr(player, player, ap, false)) { listset_nametab(player, attraccess_nametab, ap->flags, ap->name, true); } } } // --------------------------------------------------------------------------- // cf_access: Change command or switch permissions. // CF_HAND(cf_access) { UNUSED_PARAMETER(vp); CMDENT *cmdp; UTF8 *ap; bool set_switch; for (ap = str; *ap && !mux_isspace(*ap) && (*ap != '/'); ap++) ; if (*ap == '/') { set_switch = true; *ap++ = '\0'; } else { set_switch = false; if (*ap) { *ap++ = '\0'; } while (mux_isspace(*ap)) { ap++; } } { size_t nStr = strlen(reinterpret_cast(str)); auto it_str = mudstate.command_htab.find(std::vector(str, str + nStr)); cmdp = (it_str != mudstate.command_htab.end()) ? static_cast(it_str->second) : nullptr; } if (cmdp != nullptr) { if (set_switch) { return cf_ntab_access(reinterpret_cast(cmdp->switches), ap, pExtra, nExtra, player, cmd); } else { return cf_modify_bits(&(cmdp->perms), ap, pExtra, nExtra, player, cmd); } } else { if (!mux_stricmp(str, T("home"))) { return cf_modify_bits(&(mudconf.restrict_home), ap, pExtra, nExtra, player, cmd); } cf_log_notfound(player, cmd, T("Command"), str); return -1; } } // --------------------------------------------------------------------------- // cf_acmd_access: Change command permissions for all attr-setting cmds. // CF_HAND(cf_acmd_access) { UNUSED_PARAMETER(vp); ATTR *ap; for (ap = AttrTable; ap->name; ap++) { size_t nBuffer; bool bValid; UTF8 *buff = MakeCanonicalAttributeCommand(ap->name, &nBuffer, &bValid); if (!bValid) { continue; } auto it_bf = mudstate.command_htab.find(std::vector(buff, buff + nBuffer)); CMDENT *cmdp = (it_bf != mudstate.command_htab.end()) ? static_cast(it_bf->second) : nullptr; if (cmdp != nullptr) { int save = cmdp->perms; int failure = cf_modify_bits(&(cmdp->perms), str, pExtra, nExtra, player, cmd); if (failure != 0) { cmdp->perms = save; return -1; } } } return 0; } // --------------------------------------------------------------------------- // cf_attr_access: Change access on an attribute. // CF_HAND(cf_attr_access) { ATTR *ap; UTF8 *sp; ATTRPERM **ppv = (ATTRPERM **)vp; for (sp = str; *sp && !mux_isspace(*sp); sp++) { ; // Nothing } if (*sp) { *sp++ = '\0'; } while (mux_isspace(*sp)) { sp++; } ap = atr_str(reinterpret_cast(str)); if (ap) { // This is a straight-out built-in attribute, so we'll just modify directly. // return cf_modify_bits(&(ap->flags), sp, pExtra, nExtra, player, cmd); } else { // This is either a wildcard or a vattr, so should be added to the table. // ATTRPERM *perm = nullptr; try { perm = new ATTRPERM; } catch (...) { ; // Nothing. } if (nullptr == perm) { cf_log_syntax(player, cmd, T("Out of memory.")); return -1; } perm->wildcard = StringClone(str); perm->flags = 0; ATTRPERM *head = *ppv; // Add our permission to the list. // if (mudstate.bReadingConfiguration) { if (nullptr == head) { *ppv = perm; } else { ATTRPERM *last; for (last = head; last->next; last = last->next) { ; // Nothing. } last->next = perm; } } else { perm->next = head; *ppv = perm; } // Call the standard permission parser. // return cf_modify_bits(&(perm->flags), sp, pExtra, nExtra, player, cmd); } } // --------------------------------------------------------------------------- // cf_cmd_alias: Add a command alias. // CF_HAND(cf_cmd_alias) { UNUSED_PARAMETER(pExtra); UNUSED_PARAMETER(nExtra); UTF8 *ap; CMDENT *cmdp, *cmd2; NAMETAB *nt; string_token st(str, T(" \t=,")); UTF8 *alias = st.parse(); UTF8 *orig = st.parse(); if (!orig) { // We only got one argument to @alias. Bad. // return -1; } for (ap = orig; *ap && (*ap != '/'); ap++) ; if (*ap == '/') { // Switch form of command aliasing: create an alias for // a command + a switch // *ap++ = '\0'; // Look up the command // { StringPtrMap *htab = reinterpret_cast(vp); size_t nOrig = strlen(reinterpret_cast(orig)); auto it_orig = htab->find(std::vector(orig, orig + nOrig)); cmdp = (it_orig != htab->end()) ? static_cast(it_orig->second) : nullptr; } if (cmdp == nullptr || cmdp->switches == nullptr) { cf_log_notfound(player, cmd, T("Command"), orig); return -1; } // Look up the switch // nt = find_nametab_ent(player, reinterpret_cast(cmdp->switches), ap); if (!nt) { cf_log_notfound(player, cmd, T("Switch"), ap); return -1; } { StringPtrMap *htab_chk = reinterpret_cast(vp); size_t nAlias = strlen(reinterpret_cast(alias)); auto it_alias = htab_chk->find(std::vector(alias, alias + nAlias)); if (it_alias == htab_chk->end()) { // Create the new command table entry. // cmd2 = nullptr; try { cmd2 = new CMDENT; } catch (...) { ; // Nothing. } if (nullptr == cmd2) { STARTLOG(LOG_PROBLEMS, "CMD", "MEM"); log_printf(T("cf_alias: out of memory creating switch alias.")); ENDLOG; return -1; } cmd2->cmdname = StringClone(alias); cmd2->switches = cmdp->switches; cmd2->perms = cmdp->perms | nt->perm; cmd2->extra = (cmdp->extra | nt->flag) & ~SW_MULTIPLE; if (!(nt->flag & SW_MULTIPLE)) { cmd2->extra |= SW_GOT_UNIQUE; } cmd2->callseq = cmdp->callseq; cmd2->handler = cmdp->handler; cmd2->flags = CEF_ALLOC; size_t nCmd2 = strlen(reinterpret_cast(cmd2->cmdname)); htab_chk->emplace(std::vector(cmd2->cmdname, cmd2->cmdname + nCmd2), cmd2); } } } else { // A normal (non-switch) alias // StringPtrMap *htab_ns = reinterpret_cast(vp); size_t nOrig2 = strlen(reinterpret_cast(orig)); auto it_hp = htab_ns->find(std::vector(orig, orig + nOrig2)); void *hp = (it_hp != htab_ns->end()) ? it_hp->second : nullptr; if (hp == nullptr) { cf_log_notfound(player, cmd, T("Entry"), orig); return -1; } size_t nAlias2 = strlen(reinterpret_cast(alias)); htab_ns->emplace(std::vector(alias, alias + nAlias2), hp); } return 0; } // --------------------------------------------------------------------------- // list_df_flags: List default flags at create time. // static void list_df_flags(dbref player) { FLAGSET fs; fs = mudconf.player_flags; fs.word[FLAG_WORD1] |= TYPE_PLAYER; UTF8 *playerb = decode_flags(player, &fs); fs = mudconf.room_flags; fs.word[FLAG_WORD1] |= TYPE_ROOM; UTF8 *roomb = decode_flags(player, &fs); fs = mudconf.exit_flags; fs.word[FLAG_WORD1] |= TYPE_EXIT; UTF8 *exitb = decode_flags(player, &fs); fs = mudconf.thing_flags; fs.word[FLAG_WORD1] |= TYPE_THING; UTF8 *thingb = decode_flags(player, &fs); fs = mudconf.robot_flags; fs.word[FLAG_WORD1] |= TYPE_PLAYER; UTF8 *robotb = decode_flags(player, &fs); LBuf buff = LBuf_Src("list_df_flags"); mux_sprintf(buff, LBUF_SIZE, T("Default flags: Players...%s Rooms...%s Exits...%s Things...%s Robots...%s"), playerb, roomb, exitb, thingb, robotb); free_sbuf(playerb); free_sbuf(roomb); free_sbuf(exitb); free_sbuf(thingb); free_sbuf(robotb); raw_notify(player, buff); } // --------------------------------------------------------------------------- // list_costs: List the costs of things. // #define coin_name(s) (((s)==1) ? mudconf.one_coin : mudconf.many_coins) static void list_costs(dbref player) { UTF8 *buff = alloc_mbuf("list_costs"); if (mudconf.quotas) { mux_sprintf(buff, MBUF_SIZE, T(" and %d quota"), mudconf.room_quota); } notify(player, tprintf(M_("Digging a room costs %d %s%s."), mudconf.digcost, coin_name(mudconf.digcost), buff)); if (mudconf.quotas) { mux_sprintf(buff, MBUF_SIZE, T(" and %d quota"), mudconf.exit_quota); } notify(player, tprintf(M_("Opening a new exit costs %d %s%s."), mudconf.opencost, coin_name(mudconf.opencost), buff)); notify(player, tprintf(M_("Linking an exit, home, or dropto costs %d %s."), mudconf.linkcost, coin_name(mudconf.linkcost))); if (mudconf.quotas) { mux_sprintf(buff, MBUF_SIZE, T(" and %d quota"), mudconf.thing_quota); } if (mudconf.createmin == mudconf.createmax) { raw_notify(player, tprintf(M_("Creating a new thing costs %d %s%s."), mudconf.createmin, coin_name(mudconf.createmin), buff)); } else { raw_notify(player, tprintf(M_("Creating a new thing costs between %d and %d %s%s."), mudconf.createmin, mudconf.createmax, mudconf.many_coins, buff)); } if (mudconf.quotas) { mux_sprintf(buff, MBUF_SIZE, T(" and %d quota"), mudconf.player_quota); } notify(player, tprintf(M_("Creating a robot costs %d %s%s."), mudconf.robotcost, coin_name(mudconf.robotcost), buff)); if (mudconf.killmin == mudconf.killmax) { int chance = 100; if (0 < mudconf.killguarantee) { chance = (mudconf.killmin * 100) / mudconf.killguarantee; } raw_notify(player, tprintf(M_("Killing costs %d %s, with a %d%% chance of success."), mudconf.killmin, coin_name(mudconf.digcost), chance)); } else { int cost_surething; raw_notify(player, tprintf(M_("Killing costs between %d and %d %s."), mudconf.killmin, mudconf.killmax, mudconf.many_coins)); if (0 < mudconf.killguarantee) { cost_surething = mudconf.killguarantee; } else { cost_surething = mudconf.killmin; } raw_notify(player, tprintf(M_("You must spend %d %s to guarantee success."), cost_surething, coin_name(cost_surething))); } raw_notify(player, tprintf(M_("Computationally expensive commands and functions (ie: @entrances, @find, @search, @stats (with an argument or switch), search(), and stats()) cost %d %s."), mudconf.searchcost, coin_name(mudconf.searchcost))); if (mudconf.machinecost > 0) raw_notify(player, tprintf(M_("Each command run from the queue costs 1/%d %s."), mudconf.machinecost, mudconf.one_coin)); if (mudconf.waitcost > 0) { raw_notify(player, tprintf(M_("A %d %s deposit is charged for putting a command on the queue."), mudconf.waitcost, mudconf.one_coin)); raw_notify(player, M_("The deposit is refunded when the command is run or canceled.")); } if (mudconf.sacfactor == 0) { mux_ltoa(mudconf.sacadjust, buff); } else if (mudconf.sacfactor == 1) { if (mudconf.sacadjust < 0) mux_sprintf(buff, MBUF_SIZE, T(" - %d"), -mudconf.sacadjust); else if (mudconf.sacadjust > 0) mux_sprintf(buff, MBUF_SIZE, T(" + %d"), mudconf.sacadjust); else mux_sprintf(buff, MBUF_SIZE, T("")); } else { if (mudconf.sacadjust < 0) mux_sprintf(buff, MBUF_SIZE, T("( / %d) - %d"), mudconf.sacfactor, -mudconf.sacadjust); else if (mudconf.sacadjust > 0) mux_sprintf(buff, MBUF_SIZE, T("( / %d) + %d"), mudconf.sacfactor, mudconf.sacadjust); else mux_sprintf(buff, MBUF_SIZE, T(" / %d"), mudconf.sacfactor); } raw_notify(player, tprintf(M_("The value of an object is %s."), buff)); if (mudconf.clone_copy_cost) raw_notify(player, M_("The default value of cloned objects is the value of the original object.")); else raw_notify(player, tprintf(M_("The default value of cloned objects is %d %s."), mudconf.createmin, coin_name(mudconf.createmin))); free_mbuf(buff); } // --------------------------------------------------------------------------- // list_options: List more game options from mudconf. // static const UTF8 *switchd[] = { T("/first"), T("/all") }; static const UTF8 *examd[] = { T("/brief"), T("/full") }; static const UTF8 *ed[] = { T("Disabled"), T("Enabled") }; static void list_options(dbref player) { UTF8 *buff; CLinearTimeAbsolute ltaNow; ltaNow.GetUTC(); if (mudconf.quotas) raw_notify(player, M_("Building quotas are enforced.")); if (mudconf.name_spaces) raw_notify(player, M_("Player names may contain spaces.")); else raw_notify(player, M_("Player names may not contain spaces.")); if (!mudconf.robot_speak) raw_notify(player, M_("Robots are not allowed to speak in public areas.")); if (mudconf.player_listen) raw_notify(player, M_("The @Listen/@Ahear attribute set works on player objects.")); if (mudconf.ex_flags) { raw_notify(player, M_("The ‘examine’ command lists the flag names for the object’s flags.")); } if (!mudconf.quiet_look) raw_notify(player, M_("The ‘look’ command shows visible attributes in addition to the description.")); if (mudconf.see_own_dark) raw_notify(player, M_("The ‘look’ command lists DARK objects owned by you.")); if (!mudconf.dark_sleepers) raw_notify(player, M_("The ‘look’ command shows disconnected players.")); if (mudconf.terse_look) raw_notify(player, M_("The ‘look’ command obeys the TERSE flag.")); if (mudconf.trace_topdown) { raw_notify(player, M_("Trace output is presented top-down (whole expression first, then sub-exprs).")); raw_notify(player, tprintf(M_("Only %d lines of trace output are displayed."), mudconf.trace_limit)); } else { raw_notify(player, M_("Trace output is presented bottom-up (subexpressions first).")); } if (!mudconf.quiet_whisper) raw_notify(player, M_("The ‘whisper’ command lets others in the room with you know you whispered.")); if (mudconf.pemit_players) raw_notify(player, M_("The ‘@pemit’ command may be used to emit to faraway players.")); if (!mudconf.terse_contents) raw_notify(player, M_("The TERSE flag suppresses listing the contents of a location.")); if (!mudconf.terse_exits) raw_notify(player, M_("The TERSE flag suppresses listing obvious exits in a location.")); if (!mudconf.terse_movemsg) raw_notify(player, M_("The TERSE flag suppresses enter/leave/succ/drop messages generated by moving.")); if (mudconf.pub_flags) { raw_notify(player, M_("The ‘flags()’ function will return the flags of any object.")); } if (mudconf.read_rem_desc) raw_notify(player, M_("The ‘get()’ function will return the description of faraway objects,")); if (mudconf.read_rem_name) raw_notify(player, M_("The ‘name()’ function will return the name of faraway objects.")); raw_notify(player, tprintf(M_("The default switch for the ‘@switch’ command is %s."), switchd[mudconf.switch_df_all])); raw_notify(player, tprintf(M_("The default switch for the ‘examine’ command is %s."), examd[mudconf.exam_public])); if (mudconf.sweep_dark) raw_notify(player, M_("Players may @sweep dark locations.")); if (mudconf.fascist_tport) raw_notify(player, M_("You may only @teleport out of locations that are JUMP_OK or that you control.")); raw_notify(player, tprintf(M_("Players may have at most %d commands in the queue at one time."), mudconf.queuemax)); if (mudconf.match_mine) { if (mudconf.match_mine_pl) raw_notify(player, M_("All objects search themselves for $-commands.")); else raw_notify(player, M_("Objects other than players search themselves for $-commands.")); } if (!Wizard(player)) return; buff = alloc_mbuf("list_options"); raw_notify(player, tprintf(M_("%d commands are run from the queue when there is no net activity."), mudconf.queue_chunk)); raw_notify(player, tprintf(M_("%d commands are run from the queue when there is net activity."), mudconf.active_q_chunk)); if (mudconf.idle_wiz_dark) raw_notify(player, M_("Wizards idle for longer than the default timeout are automatically set DARK.")); if (mudconf.safe_unowned) raw_notify(player, M_("Objects not owned by you are automatically considered SAFE.")); if (mudconf.paranoid_alloc) raw_notify(player, M_("The buffer pools are checked for consistency on each allocate or free.")); if (mudconf.cache_names) raw_notify(player, M_("A separate name cache is used.")); #if defined(HAVE_WORKING_FORK) if (mudconf.fork_dump) { raw_notify(player, M_("Database dumps are performed by a fork()ed process.")); } #endif // HAVE_WORKING_FORK if (mudconf.max_players >= 0) raw_notify(player, tprintf(MN_("There may be at most %d player logged in at once.", "There may be at most %d players logged in at once.", mudconf.max_players), mudconf.max_players)); if (mudconf.quotas) mux_sprintf(buff, MBUF_SIZE, T(" and %d quota"), mudconf.start_quota); else *buff = '\0'; raw_notify(player, tprintf(M_("New players are given %d %s to start with."), mudconf.paystart, mudconf.many_coins)); raw_notify(player, tprintf(M_("Players are given %d %s each day they connect."), mudconf.paycheck, mudconf.many_coins)); raw_notify(player, tprintf(M_("Earning money is difficult if you have more than %d %s."), mudconf.paylimit, mudconf.many_coins)); if (mudconf.payfind > 0) raw_notify(player, tprintf(M_("Players have a 1 in %d chance of finding a %s each time they move."), mudconf.payfind, mudconf.one_coin)); raw_notify(player, tprintf(T("The head of the object freelist is #%d."), mudstate.freelist)); mux_sprintf(buff, MBUF_SIZE, T("Intervals: Dump...%d Clean...%d Idlecheck...%d"), mudconf.dump_interval, mudconf.check_interval, mudconf.idle_interval); raw_notify(player, buff); CLinearTimeDelta ltdDump = mudstate.dump_counter - ltaNow; CLinearTimeDelta ltdCheck = mudstate.check_counter - ltaNow; CLinearTimeDelta ltdIdle = mudstate.idle_counter - ltaNow; long lDump = ltdDump.ReturnSeconds(); long lCheck = ltdCheck.ReturnSeconds(); long lIdle = ltdIdle.ReturnSeconds(); mux_sprintf(buff, MBUF_SIZE, T("Timers: Dump...%ld Clean...%ld Idlecheck...%ld"), lDump, lCheck, lIdle); raw_notify(player, buff); mux_sprintf(buff, MBUF_SIZE, T("Timeouts: Idle...%d Connect...%d Tries...%d"), mudconf.idle_timeout, mudconf.conn_timeout, mudconf.retry_limit); raw_notify(player, buff); mux_sprintf(buff, MBUF_SIZE, T("Scheduling: Timeslice...%s Max_Quota...%d Increment...%d"), mudconf.timeslice.ReturnSecondsString(3), mudconf.cmd_quota_max, mudconf.cmd_quota_incr); raw_notify(player, buff); mux_sprintf(buff, MBUF_SIZE, T("Spaces...%s"), ed[mudconf.space_compress]); raw_notify(player, buff); mux_sprintf(buff, MBUF_SIZE, T("New characters: Room...#%d Home...#%d DefaultHome...#%d Quota...%d"), mudconf.start_room, mudconf.start_home, mudconf.default_home, mudconf.start_quota); raw_notify(player, buff); mux_sprintf(buff, MBUF_SIZE, T("Misc: GuestChar...#%d IdleQueueChunk...%d ActiveQueueChunk...%d Master_room...#%d"), mudconf.guest_char, mudconf.queue_chunk, mudconf.active_q_chunk, mudconf.master_room); raw_notify(player, buff); free_mbuf(buff); } // --------------------------------------------------------------------------- // list_vattrs: List user-defined attributes // static void list_vattrs(dbref player, UTF8 *s_mask) { bool wild_mtch = s_mask && s_mask[0] != '\0'; LBuf buff = LBuf_Src("list_vattrs"); // If wild_match, then only list attributes that match wildcard(s) // UTF8 *p = tprintf(T("--- User-Defined Attributes %s---"), wild_mtch ? "(wildmatched) " : ""); raw_notify(player, p); ATTR *va; int na; int wna = 0; for (va = vattr_first(), na = 0; va; va = vattr_next(va), na++) { if (!(va->flags & AF_DELETED)) { // We need to be extremely careful that s_mask is !null and valid // if (wild_mtch) { mudstate.wild_invk_ctr = 0; if (!quick_wild(s_mask, va->name)) { continue; } wna++; } mux_sprintf(buff, LBUF_SIZE, T("%s(%d)"), va->name, va->number); listset_nametab(player, attraccess_nametab, va->flags, buff, true); } } if (wild_mtch) { p = tprintf(T("%d attributes matched, %d attributes total, next=%d"), wna, na, mudstate.attr_next); } else { p = tprintf(T("%d attributes, next=%d"), na, mudstate.attr_next); } raw_notify(player, p); } // LeftJustifyString and RightJustifyNumber moved to stringutil.cpp (libmux.so). // list_hashstats: List information from hash tables // // Hash table size list (#1667 Phase 4 C3). The old full-stat header // (Size/Num/Del/Lookups/…) did not match the abbreviated rows (name + // entry count only). Header and rows now share one schema. // static const size_t kHashNameCols = 13; static const size_t kHashEntriesCols = 11; static void list_hashstat_abbreviated(const dbref player, const UTF8* tab_name, const int entries) { UTF8 buff[MBUF_SIZE]; UTF8 num[16]; size_t pos = 0; pos = mux_table_append_ljust(buff, sizeof(buff), pos, tab_name, kHashNameCols); pos = mux_table_append_bytes(buff, sizeof(buff), pos, " "); // RightJustifyNumber — mux_vsnprintf has no %* width (#1429). // size_t nw = RightJustifyNumber(num, kHashEntriesCols, static_cast(entries), ' '); num[nw] = '\0'; pos = mux_table_append_bytes(buff, sizeof(buff), pos, reinterpret_cast(num)); raw_notify(player, buff); } static void list_hashstats(const dbref player) { { UTF8 header[MBUF_SIZE]; size_t pos = 0; pos = mux_table_append_ljust(header, sizeof(header), pos, M_("Name"), kHashNameCols); pos = mux_table_append_bytes(header, sizeof(header), pos, " "); pos = mux_table_append_ljust(header, sizeof(header), pos, M_("Entries"), kHashEntriesCols); raw_notify(player, header); } list_hashstat_abbreviated(player, T("Commands"), static_cast(mudstate.command_htab.size())); list_hashstat_abbreviated(player, T("Logout Cmds"), static_cast(mudstate.logout_cmd_htab.size())); list_hashstat_abbreviated(player, T("Functions"), static_cast(mudstate.builtin_functions.size()));; list_hashstat_abbreviated(player, T("Flags"), static_cast(mudstate.flag_names_map.size()));; list_hashstat_abbreviated(player, T("Powers"), static_cast(mudstate.powers_htab.size())); list_hashstat_abbreviated(player, T("Attr Names"), static_cast(mudstate.builtin_attribute_names.size())); list_hashstat_abbreviated(player, T("Vattr Names"), static_cast(mudstate.vattr_name_map.size())); list_hashstat_abbreviated(player, T("Player Names"), static_cast(mudstate.player_htab.size())); list_hashstat_abbreviated(player, T("Net Descr."), get_total_connections()); list_hashstat_abbreviated(player, T("Fwd. lists"), static_cast(mudstate.forward_lists.size())); list_hashstat_abbreviated(player, T("Excl. $-cmds"), static_cast(mudstate.parent_htab.size())); list_hashstat_abbreviated(player, T("Mail Messages"), static_cast(mudstate.mail_htab.size())); list_hashstat_abbreviated(player, T("Channel Names"), static_cast(mudstate.channel_names.size()));; list_hashstat_abbreviated(player, T("Attr. Cache"), static_cast(mudstate.attribute_lru_cache_list.size())); for (int i = 0; i < mudstate.nHelpDesc; i++) { list_hashstat_abbreviated(player, mudstate.aHelpDesc[i].pBaseFilename, static_cast(mudstate.aHelpDesc[i].ht->size())); } } // --------------------------------------------------------------------------- // list_process: List local resource usage stats of the mux process. // Adapted from code by Claudius@PythonMUCK, // posted to the net by Howard/Dark_Lord. // static void list_process(dbref player) { #ifdef HAVE_GETRUSAGE struct rusage usage; int ixrss, idrss, isrss, curr, last, dur; getrusage(RUSAGE_SELF, &usage); // Calculate memory use from the aggregate totals. // curr = mudstate.mstat_curr; last = 1 - curr; dur = mudstate.mstat_secs[curr] - mudstate.mstat_secs[last]; if (dur > 0) { ixrss = (mudstate.mstat_ixrss[curr] - mudstate.mstat_ixrss[last]) / dur; idrss = (mudstate.mstat_idrss[curr] - mudstate.mstat_idrss[last]) / dur; isrss = (mudstate.mstat_isrss[curr] - mudstate.mstat_isrss[last]) / dur; } else { ixrss = 0; idrss = 0; isrss = 0; } #endif // HAVE_GETRUSAGE #if defined(UNIX_NETWORKING) #ifdef HAVE_GETDTABLESIZE int maxfds = getdtablesize(); #else // HAVE_GETDTABLESIZE int maxfds = sysconf(_SC_OPEN_MAX); #endif // HAVE_GETDTABLESIZE long psize = sysconf(_SC_PAGESIZE); if (psize <= 0) { psize = 4096; } raw_notify(player, tprintf(T("Process ID: %10d %10ld bytes per page"), game_pid, psize)); #else // UNIX_NETWORKING raw_notify(player, tprintf(T("Process ID: %10d"), game_pid)); #endif // UNIX_NETWORKING #ifdef HAVE_GETRUSAGE raw_notify(player, tprintf(T("Time used: %10d user %10d sys"), usage.ru_utime.tv_sec, usage.ru_stime.tv_sec)); raw_notify(player, tprintf(T("Resident mem:%10d shared %10d private%10d stack"), ixrss, idrss, isrss)); raw_notify(player, tprintf(T("Integral mem:%10ld shared %10ld private%10ld stack"), usage.ru_ixrss, usage.ru_idrss, usage.ru_isrss)); raw_notify(player, tprintf(T("Max res mem: %10ld pages %10ld bytes"), usage.ru_maxrss, (usage.ru_maxrss * static_cast(psize)))); raw_notify(player, tprintf(T("Page faults: %10ld hard %10ld soft %10ld swapouts"), usage.ru_majflt, usage.ru_minflt, usage.ru_nswap)); raw_notify(player, tprintf(T("Disk I/O: %10ld reads %10ld writes"), usage.ru_inblock, usage.ru_oublock)); raw_notify(player, tprintf(T("Network I/O: %10ld in %10ld out"), usage.ru_msgrcv, usage.ru_msgsnd)); raw_notify(player, tprintf(T("Context swi: %10ld vol %10ld forced %10ld sigs"), usage.ru_nvcsw, usage.ru_nivcsw, usage.ru_nsignals)); raw_notify(player, tprintf(T("Descs avail: %10d"), maxfds)); #endif // HAVE_GETRUSAGE } //---------------------------------------------------------------------------- // list_modules // // static void list_modules(dbref executor) { raw_notify(executor, M_("Modules:")); int i; for (i = 0; ; i++) { MUX_MODULE_INFO ModuleInfo; MUX_RESULT mr = mux_ModuleInfo(i, &ModuleInfo); if ( MUX_FAILED(mr) || MUX_S_FALSE == mr) { break; } raw_notify(executor, tprintf(T("%s (%s)"), ModuleInfo.pName, ModuleInfo.bLoaded ? T("loaded") : T("unloaded"))); } #ifdef STUB_SLAVE { mux_ISlaveControl *pISlaveControl = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_StubSlave, nullptr, UseSlaveProcess, IID_ISlaveControl, (void **)&pISlaveControl); if (MUX_SUCCEEDED(mr) && nullptr != pISlaveControl) { for (i = 0; ; i++) { MUX_MODULE_INFO ModuleInfo; mr = pISlaveControl->ModuleInfo(i, &ModuleInfo); if ( MUX_FAILED(mr) || MUX_S_FALSE == mr) { break; } raw_notify(executor, tprintf(T("%s (%s) by stubslave"), ModuleInfo.pName, ModuleInfo.bLoaded ? T("loaded") : T("unloaded"))); } pISlaveControl->Release(); } else { // Say so, rather than printing nothing (#1535). // // Without this the whole slave section vanished on failure, and // @list modules answered as if the slave held no modules -- while // the slave process demonstrably had them mapped. The operator // could not tell "nothing is loaded there" from "I could not ask", // which on the release build (--enable-stubslave) is the only // question this command exists to answer about the slave. // // It fails every time today: the interface is created a second // time here, the slave's reply frame comes back empty, and // mux_UnmarshalInterface reports MUX_E_CLASSNOTAVAILABLE (-3). // Measured on aarch64 with a module confirmed loaded in the slave // via /proc//maps. That defect is not fixed here -- this // only stops it being invisible. // raw_notify(executor, tprintf( T("Could not query the stubslave module list (error %d)."), static_cast(mr))); } } #endif } //---------------------------------------------------------------------------- // list_rlevels // #ifdef REALITY_LVLS static void list_rlevels(dbref player) { // Reality levels (#1667 Phase 4 C9): labeled freeform fields with // mux_table name width for the level name. // static const size_t kRlevelNameCols = 20; int i; raw_notify(player, M_("Reality levels:")); for (i = 0; i < mudconf.no_levels; ++i) { UTF8 line[LBUF_SIZE]; UTF8 valbuf[32]; size_t pos = 0; pos = mux_table_append_bytes(line, sizeof(line), pos, " "); pos = mux_table_append_bytes(line, sizeof(line), pos, reinterpret_cast(M_("Level"))); pos = mux_table_append_bytes(line, sizeof(line), pos, ": "); pos = mux_table_append_ljust(line, sizeof(line), pos, mudconf.reality_level[i].name, kRlevelNameCols); pos = mux_table_append_bytes(line, sizeof(line), pos, " "); pos = mux_table_append_bytes(line, sizeof(line), pos, reinterpret_cast(M_("Value"))); pos = mux_table_append_bytes(line, sizeof(line), pos, ": "); mux_sprintf(valbuf, sizeof(valbuf), T("0x%08x"), mudconf.reality_level[i].value); pos = mux_table_append_bytes(line, sizeof(line), pos, reinterpret_cast(valbuf)); pos = mux_table_append_bytes(line, sizeof(line), pos, " "); pos = mux_table_append_bytes(line, sizeof(line), pos, reinterpret_cast(M_("Desc"))); pos = mux_table_append_bytes(line, sizeof(line), pos, ": "); pos = mux_table_append_bytes(line, sizeof(line), pos, reinterpret_cast(mudconf.reality_level[i].attr)); raw_notify(player, line); } raw_notify(player, M_("--Completed.")); } #endif // REALITY_LVLS // --------------------------------------------------------------------------- // do_list: List information stored in internal structures. // #define LIST_ATTRIBUTES 1 #define LIST_COMMANDS 2 #define LIST_COSTS 3 #define LIST_FLAGS 4 #define LIST_FUNCTIONS 5 #define LIST_GLOBALS 6 #define LIST_ALLOCATOR 7 #define LIST_LOGGING 8 #define LIST_DF_FLAGS 9 #define LIST_PERMS 10 #define LIST_ATTRPERMS 11 #define LIST_OPTIONS 12 #define LIST_HASHSTATS 13 #define LIST_BUFTRACE 14 #define LIST_CONF_PERMS 15 #define LIST_SITEINFO 16 #define LIST_POWERS 17 #define LIST_SWITCHES 18 #define LIST_VATTRS 19 #define LIST_DB_STATS 20 #define LIST_PROCESS 21 #define LIST_BADNAMES 22 #define LIST_GUESTS 24 #define LIST_MODULES 25 #define LIST_CACHE 26 #define LIST_LUA 27 #ifdef REALITY_LVLS #define LIST_RLEVELS 28 #endif NAMETAB list_names[] = { {T("allocations"), 2, CA_WIZARD, LIST_ALLOCATOR}, {T("attr_permissions"), 5, CA_WIZARD, LIST_ATTRPERMS}, {T("attributes"), 2, CA_PUBLIC, LIST_ATTRIBUTES}, {T("bad_names"), 2, CA_WIZARD, LIST_BADNAMES}, {T("buffers"), 2, CA_WIZARD, LIST_BUFTRACE}, {T("cache"), 2, CA_WIZARD, LIST_CACHE}, {T("commands"), 3, CA_PUBLIC, LIST_COMMANDS}, {T("config_permissions"), 3, CA_GOD, LIST_CONF_PERMS}, {T("costs"), 3, CA_PUBLIC, LIST_COSTS}, {T("db_stats"), 2, CA_WIZARD, LIST_DB_STATS}, {T("default_flags"), 1, CA_PUBLIC, LIST_DF_FLAGS}, {T("flags"), 2, CA_PUBLIC, LIST_FLAGS}, {T("functions"), 2, CA_PUBLIC, LIST_FUNCTIONS}, {T("globals"), 2, CA_WIZARD, LIST_GLOBALS}, {T("hashstats"), 1, CA_WIZARD, LIST_HASHSTATS}, {T("logging"), 1, CA_GOD, LIST_LOGGING}, {T("lua"), 2, CA_PUBLIC, LIST_LUA}, {T("modules"), 1, CA_WIZARD, LIST_MODULES}, {T("options"), 1, CA_PUBLIC, LIST_OPTIONS}, {T("permissions"), 2, CA_WIZARD, LIST_PERMS}, {T("powers"), 2, CA_WIZARD, LIST_POWERS}, {T("process"), 2, CA_WIZARD, LIST_PROCESS}, {T("site_information"), 2, CA_WIZARD, LIST_SITEINFO}, {T("switches"), 2, CA_PUBLIC, LIST_SWITCHES}, {T("user_attributes"), 1, CA_WIZARD, LIST_VATTRS}, {T("guests"), 2, CA_WIZARD, LIST_GUESTS}, #ifdef REALITY_LVLS {T("rlevels"), 3, CA_PUBLIC, LIST_RLEVELS}, #endif {static_cast(nullptr), 0, 0, 0} }; void do_list(dbref executor, dbref caller, dbref enactor, int eval, const int key, UTF8 *arg, const UTF8 *cargs[], const int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); string_token st(arg, T(" \t=,")); UTF8* s_option = st.parse(); int flagvalue; if (!search_nametab(executor, list_names, arg, &flagvalue)) { if (flagvalue == -1) { display_nametab(executor, list_names, T("Unknown option. Use one of"), true); } else { notify(executor, NOPERM_MESSAGE); } return; } switch (flagvalue) { case LIST_ALLOCATOR: list_bufstats(executor); break; case LIST_BUFTRACE: list_buftrace(executor); break; case LIST_ATTRIBUTES: list_attrtable(executor); break; case LIST_COMMANDS: list_cmdtable(executor); break; case LIST_SWITCHES: list_cmdswitches(executor); break; case LIST_COSTS: list_costs(executor); break; case LIST_OPTIONS: list_options(executor); break; case LIST_HASHSTATS: list_hashstats(executor); break; case LIST_SITEINFO: list_siteinfo(executor); break; case LIST_FLAGS: display_flagtab(executor); break; case LIST_FUNCTIONS: list_functable(executor); break; case LIST_GLOBALS: interp_nametab(executor, enable_names, mudconf.control_flags, T("Global parameters:"), T("enabled"), T("disabled")); break; case LIST_DF_FLAGS: list_df_flags(executor); break; case LIST_PERMS: list_cmdaccess(executor); break; case LIST_CONF_PERMS: list_cf_access(executor); break; case LIST_POWERS: display_powertab(executor); break; case LIST_ATTRPERMS: list_attraccess(executor); break; case LIST_VATTRS: s_option = st.parse(); list_vattrs(executor, s_option); break; case LIST_LOGGING: interp_nametab(executor, logoptions_nametab, mudconf.log_options, T("Events Logged:"), T("enabled"), T("disabled")); interp_nametab(executor, logdata_nametab, mudconf.log_info, T("Information Logged:"), T("yes"), T("no")); break; case LIST_DB_STATS: list_cache_stats(executor); break; case LIST_PROCESS: list_process(executor); break; case LIST_BADNAMES: badname_list(executor, T("Disallowed names:")); break; case LIST_GUESTS: Guest.ListAll(executor); break; case LIST_MODULES: list_modules(executor); break; case LIST_CACHE: list_cache_stats(executor); break; case LIST_LUA: if (nullptr != mudstate.pILuaControl) { size_t nCalls, nErrors, nInsnHits, nMemHits, nBytes; size_t nCacheHits, nCacheMisses, nCacheEntries; mudstate.pILuaControl->GetStats(&nCalls, &nErrors, &nInsnHits, &nMemHits, &nBytes, &nCacheHits, &nCacheMisses, &nCacheEntries); notify(executor, M_("Lua module loaded (Lua 5.4).")); notify(executor, tprintf(T(" Lua JIT: %s"), mudconf.lua_jit ? T("enabled (default)") : T("disabled"))); notify(executor, tprintf(T(" Calls: %zu"), nCalls)); notify(executor, tprintf(T(" Errors: %zu"), nErrors)); notify(executor, tprintf(T(" Insn limit hits: %zu"), nInsnHits)); notify(executor, tprintf(T(" Mem limit hits: %zu"), nMemHits)); notify(executor, tprintf(T(" Memory in use: %zu bytes"), nBytes)); notify(executor, tprintf(T(" Cache entries: %zu"), nCacheEntries)); notify(executor, tprintf(T(" Cache hits: %zu"), nCacheHits)); notify(executor, tprintf(T(" Cache misses: %zu"), nCacheMisses)); } else { notify(executor, M_("Lua module is not loaded.")); } break; #ifdef REALITY_LVLS case LIST_RLEVELS: list_rlevels(executor); break; #endif } } void do_assert(const dbref executor, const dbref caller, dbref enactor, const int eval, int key, int nargs, UTF8 *arg1, UTF8 *command, const UTF8 *cargs[], const int ncargs) { UNUSED_PARAMETER(key); UNUSED_PARAMETER(nargs); if (!xlate(arg1)) { break_called = true; if ( nullptr != command && '\0' != command[0]) { if (key & BREAK_INLINE) { process_command(executor, caller, enactor, eval, false, command, cargs, ncargs); } else { CLinearTimeAbsolute lta; wait_que(executor, caller, enactor, eval, false, lta, NOTHING, 0, command, ncargs, cargs, mudstate.global_regs); } } } } void do_break(const dbref executor, const dbref caller, const dbref enactor, const int eval, int key, const int nargs, UTF8 *arg1, UTF8 *command, const UTF8 *cargs[], const int ncargs) { UNUSED_PARAMETER(key); UNUSED_PARAMETER(nargs); if (xlate(arg1)) { break_called = true; if ( nullptr != command && '\0' != command[0]) { if (key & BREAK_INLINE) { process_command(executor, caller, enactor, eval, false, command, cargs, ncargs); } else { CLinearTimeAbsolute lta; wait_que(executor, caller, enactor, eval, false, lta, NOTHING, 0, command, ncargs, cargs, mudstate.global_regs); } } } } // do_icmd: Ignore or disable commands on a per-player or per-room basis. // Used with express permission of RhostMUSH developers. // Bludgeoned into MUX by Jake Nelson 7/2002. // void do_icmd(dbref player, dbref cause, dbref enactor, int eval, int key, UTF8 *name, UTF8 *args[], int nargs, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cause); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); CMDENT *cmdp; UTF8 *pt3, *atrpt; int aflags; dbref target = NOTHING, aowner; bool set; int loc_set = -1; if ( key == ICMD_IROOM || key == ICMD_DROOM || key == ICMD_CROOM || key == ICMD_LROOM || key == ICMD_LALLROOM) { if (key != ICMD_LALLROOM) { target = match_thing_quiet(player, name); } if ( key != ICMD_LALLROOM && ( !Good_obj(target) || !( isRoom(target) || isThing(target)))) { notify(player, M_("@icmd: Bad Location.")); return; } if (key == ICMD_CROOM) { atr_clr(target, A_CMDCHECK); notify(player, M_("@icmd: Location - All cleared.")); notify(player, M_("@icmd: Done.")); return; } else if (key == ICMD_LROOM) { { LBuf atrpt1 = LBuf_Adopt(atr_get("do_icmd.4060", target, A_CMDCHECK, &aowner, &aflags)); if (*atrpt1.get()) { notify(player, M_("Location CmdCheck attribute is:")); notify(player, atrpt1); } else { notify(player, M_("Location CmdCheck attribute is empty.")); } } notify(player, M_("@icmd: Done.")); return; } else if (key == ICMD_LALLROOM) { target = Location(player); if ( !Good_obj(target) || Going(target) || isPlayer(target)) { notify(player, M_("@icmd: Bad Location.")); return; } notify(player, M_("Scanning all locations and zones from your current location:")); bool bFound = false; { LBuf atrpt2 = LBuf_Adopt(atr_get("do_icmd.4086", target, A_CMDCHECK, &aowner, &aflags)); if (*atrpt2.get()) { notify(player, tprintf(T("%c --- At %s(#%d) :"), (Zone(target) == target ? '*' : ' '), Name(target), target)); notify(player, atrpt2); bFound = true; } } if (Zone(target) != target) { dbref zone = Zone(target); if ( Good_obj(zone) && ( isRoom(zone) || isThing(zone))) { LBuf atrpt3 = LBuf_Adopt(atr_get("do_icmd.4102", zone, A_CMDCHECK, &aowner, &aflags)); if (*atrpt3.get()) { notify(player, tprintf(T("%c z-- At %s(#%d) :"), '*', Name(zone), zone)); notify(player, atrpt3); bFound = true; } } } if (!bFound) { notify(player, M_("@icmd: Location - No icmds found at current location.")); } notify(player, M_("@icmd: Done.")); return; } else if (key == ICMD_IROOM) { loc_set = 1; } else if (key == ICMD_DROOM) { loc_set = 0; } } if (loc_set == -1 ) { target = lookup_player(player, name, false); if (!Good_obj(target) || God(target)) { notify(player, M_("@icmd: Bad player.")); return; } if ((key == ICMD_OFF) || (key == ICMD_CLEAR)) { s_Flags(target, FLAG_WORD3, Flags3(target) & ~CMDCHECK); if (key == ICMD_CLEAR) { atr_clr(target, A_CMDCHECK); } notify(player, M_("@icmd: All cleared.")); notify(player, M_("@icmd: Done.")); return; } else if (key == ICMD_ON) { s_Flags(target, FLAG_WORD3, Flags3(target) | CMDCHECK); notify(player, M_("@icmd: Activated.")); notify(player, M_("@icmd: Done.")); return; } else if (key == ICMD_CHECK) { if (CmdCheck(target)) { notify(player, M_("CmdCheck is active.")); } else { notify(player, M_("CmdCheck is not active.")); } { LBuf atrpt4 = LBuf_Adopt(atr_get("do_icmd.4166", target, A_CMDCHECK, &aowner, &aflags)); if (*atrpt4.get()) { notify(player, M_("CmdCheck attribute is:")); notify(player, atrpt4); } else { notify(player, M_("CmdCheck attribute is empty.")); } } notify(player, M_("@icmd: Done.")); return; } } else { key = loc_set; } auto message = T(""); LBuf buff1 = LBuf_Src("do_icmd"); for (int x = 0; x < nargs; x++) { // Lowercase the argument (Unicode-aware). // size_t nLower; UTF8 *pLower = mux_strlwr(args[x], nLower); if (nLower >= LBUF_SIZE) { nLower = LBUF_SIZE - 1; } memcpy(buff1, pLower, nLower); buff1[nLower] = '\0'; UTF8* pt1; UTF8* pt2; if ( buff1[0] == '!' && buff1[1] != '\0') { pt1 = buff1 + 1; set = false; } else { pt1 = buff1; set = true; } if (*pt1) { bool bHome; if (!string_compare(pt1, T("home"))) { bHome = true; cmdp = nullptr; } else { bHome = false; { size_t nPt1 = strlen(reinterpret_cast(pt1)); auto it_pt1 = mudstate.command_htab.find(std::vector(pt1, pt1 + nPt1)); cmdp = (it_pt1 != mudstate.command_htab.end()) ? static_cast(it_pt1->second) : nullptr; } } if (cmdp || bHome) { bool bColon = false; atrpt = atr_get("do_icmd.4223", target, A_CMDCHECK, &aowner, &aflags); if (cmdp) { aflags = static_cast(strlen(reinterpret_cast(cmdp->cmdname))); bColon = ( aflags == 1 && *(cmdp->cmdname) == ':'); } else { aflags = 4; } UTF8* pt5 = atrpt; while (pt1) { if (cmdp) { if (bColon) { pt1 = reinterpret_cast(strstr(reinterpret_cast(pt5), "::")); if (pt1) { pt1++; } } else { pt1 = reinterpret_cast(strstr(reinterpret_cast(pt5), reinterpret_cast(cmdp->cmdname))); } } else { pt1 = reinterpret_cast(strstr(reinterpret_cast(pt5), "home")); } if ( pt1 && (pt1 > atrpt) && (*(pt1 - 1) == ':') && ( mux_isspace(*(pt1 + aflags)) || !*(pt1 + aflags))) { break; } else if (pt1) { if (*pt1) { pt5 = pt1 + 1; } else { pt1 = nullptr; break; } } } if (set) { if (!pt1) { if (cmdp) { pt3 = tprintf(T(" %d:%s"), key + 1, cmdp->cmdname); } else { pt3 = tprintf(T(" %d:home"), key + 1); } const size_t natrpt = strlen(reinterpret_cast(atrpt)); const size_t npt3 = strlen(reinterpret_cast(pt3)); if ((natrpt + npt3) < LBUF_SIZE - 1) { mux_strncpy(atrpt + natrpt, pt3, LBUF_SIZE-natrpt-1); atr_add_raw(target, A_CMDCHECK, atrpt); if ( loc_set == -1 ) { s_Flags(target, FLAG_WORD3, Flags3(target) | CMDCHECK); } message = T("Set"); } } else { message = T("Command already present"); } } else { if (pt1) { pt2 = pt1 - 1; while ((pt2 > atrpt) && !mux_isspace(*pt2)) { pt2--; } size_t y = pt2 - atrpt + 1; // Save the char that'll be nulled by mux_strncpy. // (Even though it may be unneeded by this point.) const UTF8 cSave = buff1[y]; mux_strncpy(buff1, atrpt, y); buff1[y] = cSave; if (y == 1) { *atrpt = '\0'; } *(atrpt + y) = '\0'; pt2 = pt1 + aflags; if (*pt2) { while (*pt2 && mux_isspace(*pt2)) { pt2++; } if (*pt2) { const size_t natrpt = strlen(reinterpret_cast(atrpt)); mux_strncpy(atrpt + natrpt, pt2, LBUF_SIZE-natrpt-1); } } if ((y > 1) && !*pt2) { pt2 = atrpt + y; while ((pt2 > atrpt) && mux_isspace(*pt2)) { pt2--; } *(pt2 + 1) = '\0'; } if ((y == 1) && !*pt2) { atr_clr(target, A_CMDCHECK); if (loc_set == -1) { s_Flags(target, FLAG_WORD3, Flags3(target) & ~CMDCHECK); } message = T("Cleared"); } else { atr_add_raw(target, A_CMDCHECK, atrpt); message = T("Cleared"); } } else { message = T("Command not present"); } } free_lbuf(atrpt); } else { message = T("Bad command"); } notify(player, tprintf(T("@icmd:%s %s."), (loc_set == -1) ? T("") : T(" Location -"), message)); } } notify(player, M_("@icmd: Done.")); } // do_train: show someone else in the same room what code you're entering and the result // From RhostMUSH, changed to use notify_all_from_inside. // void do_train(const dbref executor, const dbref caller, const dbref enactor, const int eval, const int key, UTF8 *string, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(key); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); if (0 < mudstate.train_nest_lev) { notify(executor, M_("Train cannot be used to teach command, train.")); return; } mudstate.train_nest_lev++; const dbref loc = Location(executor); if (!Good_obj(loc)) { notify(executor, M_("Bad location.")); mudstate.train_nest_lev--; return; } if ( !string || !*string) { notify(executor, M_("Train requires an argument.")); mudstate.train_nest_lev--; return; } notify_all_from_inside(loc, executor, tprintf(T("%s types -=> %s"), Moniker(executor), string)); process_command(executor, caller, enactor, eval, true, string, nullptr, 0); mudstate.train_nest_lev--; } void do_moniker(const dbref executor, const dbref caller, const dbref enactor, const int eval, const int key, int nargs, UTF8 *name, UTF8 *instr, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); UNUSED_PARAMETER(nargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); const dbref thing = match_thing(executor, name); if (!Good_obj(thing)) { return; } if (!Controls(executor, thing)) { notify(executor, NOPERM_MESSAGE); return; } if ( instr == nullptr || instr[0] == '\0') { notify_quiet(executor, M_("Moniker cleared.")); s_Moniker(thing, nullptr); } else { s_Moniker(thing, instr); if ( !Quiet(executor) && !Quiet(thing)) { notify_quiet(executor, M_("Moniker set.")); } } set_modified(thing); } // do_hook: run softcode before or after running a hardcode command, or // softcode access. Original idea from TinyMUSH 3, code from RhostMUSH. // Used with express permission of RhostMUSH developers. // Bludgeoned into MUX by Jake Nelson 7/2002. // static void show_hook(UTF8 *bf, UTF8 *bfptr, int key) { if (key & CEF_HOOK_BEFORE) safe_str(T("before "), bf, &bfptr); if (key & CEF_HOOK_AFTER) safe_str(T("after "), bf, &bfptr); if (key & CEF_HOOK_PERMIT) safe_str(T("permit "), bf, &bfptr); if (key & CEF_HOOK_IGNORE) safe_str(T("ignore "), bf, &bfptr); if (key & CEF_HOOK_IGSWITCH) safe_str(T("igswitch "), bf, &bfptr); if (key & CEF_HOOK_AFAIL) safe_str(T("afail "), bf, &bfptr); if (key & CEF_HOOK_ARGS) safe_str(T("args "), bf, &bfptr); *bfptr = '\0'; } // @hook list row (#1667 Phase 4 C7): command 32 + " | " + mask freeform. // Single-character aliases keep a 1-letter tag and a 30-col description. // static const size_t kHookCmdCols = 32; static const size_t kHookTagCmdCols = 30; static void hook_loop(const dbref executor, CMDENT *cmdp, UTF8 *s_ptr, UTF8 *s_ptrbuff) { show_hook(s_ptrbuff, s_ptr, HOOKMASK(cmdp->flags)); const UTF8 *pCmd = cmdp->cmdname; UTF8 tag = '\0'; if ( pCmd[0] != '\0' && pCmd[1] == '\0') { switch (pCmd[0]) { case '"': tag = 'S'; pCmd = T("(‘\"’ hook on ‘say’)"); break; case ':': tag = 'P'; pCmd = T("(‘:’ hook on ‘pose’)"); break; case ';': tag = 'P'; pCmd = T("(‘;’ hook on ‘pose’)"); break; case '\\': tag = 'E'; pCmd = T("(‘\\\\’ hook on ‘@emit’)"); break; case '#': tag = 'F'; pCmd = T("(‘#’ hook on ‘@force’)"); break; case '&': tag = 'V'; pCmd = T("(‘&’ hook on ‘@set’)"); break; case '-': tag = 'M'; pCmd = T("(‘-’ hook on ‘@mail’)"); break; case '~': tag = 'M'; pCmd = T("(‘~’ hook on ‘@mail’)"); break; } } UTF8 line[LBUF_SIZE]; size_t pos = 0; if ('\0' != tag) { UTF8 tagbuf[4]; tagbuf[0] = tag; tagbuf[1] = ' '; tagbuf[2] = '\0'; pos = mux_table_append_bytes(line, sizeof(line), pos, reinterpret_cast(tagbuf)); pos = mux_table_append_ljust(line, sizeof(line), pos, pCmd, kHookTagCmdCols); } else { pos = mux_table_append_ljust(line, sizeof(line), pos, pCmd, kHookCmdCols); } pos = mux_table_append_bytes(line, sizeof(line), pos, " | "); pos = mux_table_append_bytes(line, sizeof(line), pos, reinterpret_cast(s_ptrbuff)); notify(executor, line); } void do_hook(const dbref executor, const dbref caller, const dbref enactor, const int eval, int key, UTF8 *name, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); bool negate; UTF8 *s_ptr, *s_ptrbuff, *p; const UTF8 *q; auto* cmdp = static_cast(nullptr); if ( ( key && !(key & CEF_HOOK_LIST)) || ( ( !key || (key & CEF_HOOK_LIST)) && *name)) { { size_t nName = strlen(reinterpret_cast(name)); auto it_name = mudstate.command_htab.find(std::vector(name, name + nName)); cmdp = (it_name != mudstate.command_htab.end()) ? static_cast(it_name->second) : nullptr; } if (!cmdp) { notify(executor, M_("@hook: Non-existent command name given.")); return; } } if ( (key & CEF_HOOK_CLEAR) && (key & CEF_HOOK_LIST)) { notify(executor, M_("@hook: Incompatible switches.")); return; } if (key & CEF_HOOK_CLEAR) { negate = true; key = ~CEF_HOOK_CLEAR & key; key = ~SW_MULTIPLE & key; } else { negate = false; } if (key & (CEF_HOOK_BEFORE|CEF_HOOK_AFTER|CEF_HOOK_PERMIT|CEF_HOOK_IGNORE|CEF_HOOK_IGSWITCH|CEF_HOOK_AFAIL|CEF_HOOK_ARGS)) { if (negate) { cmdp->flags = cmdp->flags & ~key; } else { cmdp->flags = cmdp->flags | key; } if (cmdp->flags) { LBuf hookbuf = LBuf_Src("@hook"); s_ptr = s_ptrbuff = hookbuf; show_hook(s_ptrbuff, s_ptr, HOOKMASK(cmdp->flags)); notify(executor, tprintf(T("@hook: New mask for ‘%s’ -> %s"), cmdp->cmdname, s_ptrbuff)); } else { notify(executor, tprintf(T("@hook: New mask for ‘%s’ is empty."), cmdp->cmdname)); } } if ( (key & CEF_HOOK_LIST) || !key) { if (cmdp) { if (cmdp->flags) { LBuf hookbuf = LBuf_Src("@hook"); s_ptr = s_ptrbuff = hookbuf; show_hook(s_ptrbuff, s_ptr, HOOKMASK(cmdp->flags)); notify(executor, tprintf(T("@hook: Mask for hashed command ‘%s’ -> %s"), cmdp->cmdname, s_ptrbuff)); } else { notify(executor, tprintf(T("@hook: Mask for hashed command ‘%s’ is empty."), cmdp->cmdname)); } } else { notify(executor, tprintf(T("%.32s-+-%s"), "--------------------------------", "--------------------------------------------")); { UTF8 header[LBUF_SIZE]; size_t pos = 0; pos = mux_table_append_ljust(header, sizeof(header), pos, M_("Built-in Command"), kHookCmdCols); pos = mux_table_append_bytes(header, sizeof(header), pos, " | "); pos = mux_table_append_bytes(header, sizeof(header), pos, reinterpret_cast(M_("Hook Mask Values"))); notify(executor, header); } notify(executor, tprintf(T("%.32s-+-%s"), "--------------------------------", "--------------------------------------------")); bool found = false; LBuf hookbuf3 = LBuf_Src("@hook"); s_ptr = s_ptrbuff = hookbuf3; { for (CMDENT_NO_ARG* cmdp2 = command_table_no_arg; cmdp2->cmdname; cmdp2++) { s_ptrbuff[0] = '\0'; s_ptr = s_ptrbuff; if (0 != HOOKMASK(cmdp2->flags)) { found = true; hook_loop(executor, reinterpret_cast(cmdp2), s_ptr, s_ptrbuff); } } } { for (CMDENT_ONE_ARG* cmdp2 = command_table_one_arg; cmdp2->cmdname; cmdp2++) { s_ptrbuff[0] = '\0'; s_ptr = s_ptrbuff; if (0 != HOOKMASK(cmdp2->flags)) { found = true; hook_loop(executor, reinterpret_cast(cmdp2), s_ptr, s_ptrbuff); } } } { for (CMDENT_TWO_ARG* cmdp2 = command_table_two_arg; cmdp2->cmdname; cmdp2++) { s_ptrbuff[0] = '\0'; s_ptr = s_ptrbuff; if (0 != HOOKMASK(cmdp2->flags)) { found = true; hook_loop(executor, reinterpret_cast(cmdp2), s_ptr, s_ptrbuff); } } } { for (CMDENT_TWO_ARG_ARGV* cmdp2 = command_table_two_arg_argv; cmdp2->cmdname; cmdp2++) { s_ptrbuff[0] = '\0'; s_ptr = s_ptrbuff; if (0 != HOOKMASK(cmdp2->flags)) { found = true; hook_loop(executor, reinterpret_cast(cmdp2), s_ptr, s_ptrbuff); } } } if (!found) { notify(executor, tprintf(T("%26s -- No @hooks defined --"), " ")); } found = false; /* We need to search the attribute table as well */ notify(executor, tprintf(T("%.32s-+-%s"), "--------------------------------", "--------------------------------------------")); { UTF8 header[LBUF_SIZE]; size_t pos = 0; pos = mux_table_append_ljust(header, sizeof(header), pos, M_("Built-in Attribute"), kHookCmdCols); pos = mux_table_append_bytes(header, sizeof(header), pos, " | "); pos = mux_table_append_bytes(header, sizeof(header), pos, reinterpret_cast(M_("Hook Mask Values"))); notify(executor, header); } notify(executor, tprintf(T("%.32s-+-%s"), "--------------------------------", "--------------------------------------------")); UTF8* cbuff = alloc_sbuf("cbuff_hook"); for (ATTR *ap = AttrTable; ap->name; ap++) { if (ap->flags & AF_NOCMD) { continue; } s_ptrbuff[0] = '\0'; s_ptr = s_ptrbuff; p = cbuff; safe_sb_chr('@', cbuff, &p); // Lowercase attr name code-point-at-a-time (Unicode-aware). // for (q = ap->name; '\0' != *q; ) { size_t n = utf8_FirstByte[static_cast(*q)]; if (n >= UTF8_CONTINUE) { break; } bool bValid = true; for (size_t j = 1; j < n; j++) { if ( '\0' == q[j] || UTF8_CONTINUE != utf8_FirstByte[static_cast(q[j])]) { bValid = false; break; } } if (!bValid) { break; } bool bXor; const string_desc *qDesc = mux_tolower(q, bXor); if (nullptr == qDesc) { for (size_t j = 0; j < n; j++) { safe_sb_chr(q[j], cbuff, &p); } } else if (bXor) { if (qDesc->n_bytes != n) { for (size_t j = 0; j < n; j++) { safe_sb_chr(q[j], cbuff, &p); } } else for (size_t j = 0; j < qDesc->n_bytes; j++) { safe_sb_chr(q[j] ^ qDesc->p[j], cbuff, &p); } } else { for (size_t j = 0; j < qDesc->n_bytes; j++) { safe_sb_chr(qDesc->p[j], cbuff, &p); } } q += n; } *p = '\0'; const size_t ncbuff = p - cbuff; { auto it_cb = mudstate.command_htab.find(std::vector(cbuff, cbuff + ncbuff)); cmdp = (it_cb != mudstate.command_htab.end()) ? static_cast(it_cb->second) : nullptr; } if ( cmdp && 0 != HOOKMASK(cmdp->flags)) { found = true; // Same C7 schema as the built-in command table. // hook_loop(executor, cmdp, s_ptr, s_ptrbuff); } } free_sbuf(cbuff); if (!found) { notify(executor, tprintf(T("%26s -- No @hooks defined --"), " ")); } notify(executor, tprintf(T("%.32s-+-%s"), "--------------------------------", "--------------------------------------------")); notify(executor, tprintf(T("The hook object is currently: #%d (%s)"), mudconf.hook_obj, ( ( Good_obj(mudconf.hook_obj) && !Going(mudconf.hook_obj)) ? "VALID" : "INVALID"))); } } } NAMETAB allow_charset_nametab[] = { {T("ascii"), 5, 0, ALLOW_CHARSET_ASCII}, {T("hangul"), 6, 0, ALLOW_CHARSET_HANGUL}, {T("hiragana"), 8, 0, ALLOW_CHARSET_HIRAGANA}, {T("iso8859-1"), 9, 0, ALLOW_CHARSET_8859_1}, {T("iso8859-2"), 9, 0, ALLOW_CHARSET_8859_2}, {T("kanji"), 5, 0, ALLOW_CHARSET_KANJI}, {T("katakana"), 8, 0, ALLOW_CHARSET_KATAKANA}, {T("latin-1"), 7, 0, ALLOW_CHARSET_8859_1}, {T("latin-2"), 7, 0, ALLOW_CHARSET_8859_2}, {static_cast(nullptr), 0, 0, 0} };