#include "scripting/scripting.h" #include "scripting_doc.h" #include "scripting/lua/LuaUtil.h" extern "C" { #include "scripting/lua/lua_ext.h" #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #include #else #include #endif } /** * IMPORTANT! * * For various compiler reasons, all scripting API classes and libraries need to be referenced here! The easiest way to * do this is to declare the API elements in the header with the DECLARE_* macros and then include them here */ #include "scripting/api/objs/asteroid.h" #include "scripting/api/objs/background_element.h" #include "scripting/api/objs/beam.h" #include "scripting/api/objs/camera.h" #include "scripting/api/objs/cockpit_display.h" #include "scripting/api/objs/control_info.h" #include "scripting/api/objs/debris.h" #include "scripting/api/objs/enums.h" #include "scripting/api/objs/event.h" #include "scripting/api/objs/eye.h" #include "scripting/api/objs/file.h" #include "scripting/api/objs/font.h" #include "scripting/api/objs/gameevent.h" #include "scripting/api/objs/gamestate.h" #include "scripting/api/objs/hudgauge.h" #include "scripting/api/objs/mc_info.h" #include "scripting/api/objs/message.h" #include "scripting/api/objs/model.h" #include "scripting/api/objs/object.h" #include "scripting/api/objs/option.h" #include "scripting/api/objs/order.h" #include "scripting/api/objs/particle.h" #include "scripting/api/objs/model_path.h" #include "scripting/api/objs/physics_info.h" #include "scripting/api/objs/player.h" #include "scripting/api/objs/promise.h" #include "scripting/api/objs/rpc.h" #include "scripting/api/objs/sexpvar.h" #include "scripting/api/objs/shields.h" #include "scripting/api/objs/ship.h" #include "scripting/api/objs/ship_bank.h" #include "scripting/api/objs/shipclass.h" #include "scripting/api/objs/shiptype.h" #include "scripting/api/objs/sound.h" #include "scripting/api/objs/species.h" #include "scripting/api/objs/streaminganim.h" #include "scripting/api/objs/subsystem.h" #include "scripting/api/objs/team.h" #include "scripting/api/objs/texture.h" #include "scripting/api/objs/texturemap.h" #include "scripting/api/objs/time_obj.h" #include "scripting/api/objs/vecmath.h" #include "scripting/api/objs/waypoint.h" #include "scripting/api/objs/weapon.h" #include "scripting/api/objs/weaponclass.h" #include "scripting/api/objs/wing.h" #include "scripting/api/libs/audio.h" #include "scripting/api/libs/async.h" #include "scripting/api/libs/base.h" #include "scripting/api/libs/bitops.h" #include "scripting/api/libs/cfile.h" #include "scripting/api/libs/controls.h" #include "scripting/api/libs/engine.h" #include "scripting/api/libs/globals.h" #include "scripting/api/libs/graphics.h" #include "scripting/api/libs/hookvars.h" #include "scripting/api/libs/hud.h" #include "scripting/api/libs/mission.h" #include "scripting/api/libs/multi.h" #include "scripting/api/libs/options.h" #include "scripting/api/libs/parse.h" #include "scripting/api/libs/tables.h" #include "scripting/api/libs/testing.h" #include "scripting/api/libs/time_lib.h" #include "scripting/api/libs/ui.h" #include "scripting/api/libs/utf8.h" // End of definitions includes using namespace scripting; using namespace scripting::api; namespace { const char* ScriptStateReferenceName = "SCP_ScriptState"; } // *************************Housekeeping************************* static void *vm_lua_alloc(void*, void *ptr, size_t, size_t nsize) { if (nsize == 0) { vm_free(ptr); return NULL; } else { return vm_realloc(ptr, nsize); } } //kind of fake, prevents true file access (only allows pipes and stuff) and also returns nil on fail instead of error handling string static int io_open_limited (lua_State *L) { const char *filename = luaL_checkstring(L, 1); const char *mode = luaL_optstring(L, 2, "r"); if (filename == nullptr) return 0; //We allow fifo-pipes, and direct character access. Neither should be too risky, and they allow for features such as communicating with speedrun tools or specialized hardware //This explicitly blocks access to files, directories, and block devices #ifdef WIN32 auto handle = CreateFileA(filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); bool file_allowed = false; if (handle != INVALID_HANDLE_VALUE) { auto file_type = GetFileType(handle); CloseHandle(handle); file_allowed = (file_type == FILE_TYPE_PIPE) || (file_type == FILE_TYPE_CHAR); if (file_type == FILE_TYPE_PIPE) { //For pipes only, we should make sure that the handler has actually closed before continuing. WaitNamedPipeA(filename, 30000); } } #else struct stat file_stat_buffer; int query_stat = stat( filename, &file_stat_buffer ); bool file_allowed = (query_stat == 0) && (S_ISFIFO(file_stat_buffer.st_mode) || S_ISCHR(file_stat_buffer.st_mode)); #endif //Check that our requested file is nice and not evil if (file_allowed) { FILE **pf = (FILE **) lua_newuserdata(L, sizeof(FILE *)); *pf = nullptr; /* file handle is currently `closed' */ luaL_getmetatable(L, LUA_FILEHANDLE); lua_setmetatable(L, -2); *pf = fopen(filename, mode); if (*pf != nullptr) { return 1; } lua_pop(L, 1); } return 0; } //Inits LUA //Note that "libraries" must end with a {NULL, NULL} //element int script_state::CreateLuaState() { mprintf(("LUA: Opening LUA state...\n")); lua_State *L = lua_newstate(vm_lua_alloc, nullptr); if(L == NULL) { Warning(LOCATION, "Could not initialize Lua"); return 0; } //*****INITIALIZE AUXILIARY LIBRARIES mprintf(("LUA: Initializing base Lua libraries...\n")); luaL_openlibs(L); //*****INITIALIZE EXTENSION LIBRARIES mprintf(("LUA: Initializing extended Lua libraries...\n")); luaL_openlibs_ext(L); //*****INITIALIZE OUR SUPPORT LIBRARY luacpp::util::initializeLuaSupportLib(L); // Store our script state pointer in this Lua state so that it can be retrieved from the Lua API without depending // on global state lua_pushlightuserdata(L, this); lua_setfield(L, LUA_REGISTRYINDEX, ScriptStateReferenceName); //*****DISABLE DANGEROUS COMMANDS lua_pushstring(L, "os"); lua_rawget(L, LUA_GLOBALSINDEX); int os_ldx = lua_gettop(L); if(lua_istable(L, os_ldx)) { lua_pushstring(L, "execute"); lua_pushnil(L); lua_rawset(L, os_ldx); lua_pushstring(L, "remove"); lua_pushnil(L); lua_rawset(L, os_ldx); lua_pushstring(L, "rename"); lua_pushnil(L); lua_rawset(L, os_ldx); } lua_pop(L, 1); //os table lua_pushstring(L, "io"); lua_rawget(L, LUA_GLOBALSINDEX); int io_ldx = lua_gettop(L); if(lua_istable(L, io_ldx)) { lua_pushstring(L, "popen"); lua_pushnil(L); lua_rawset(L, io_ldx); //Instead of just removing open alltogether. Make sure to copy over the original fenv, as to allow lua to have the correct close already associated lua_pushstring(L, "open"); // [io, "open"] lua_pushstring(L, "open"); // [io, "open", "open"] lua_rawget(L, io_ldx); // [io, "open", open()] lua_pushcfunction(L, io_open_limited); // [io, "open", open(), open_limited()] lua_getfenv(L, -2); // [io, "open", open(), open_limited(), open_fenv] lua_setfenv(L, -2); // [io, "open", open(), open_limited()] lua_remove(L, -2); // [io, "open", open_limited()] lua_rawset(L, io_ldx); // [io] } lua_pop(L, 1); //io table //*****INITIALIZE ADE uint i; mprintf(("LUA: Beginning ADE initialization\n")); for(i = 0; i < ade_manager::getInstance()->getNumEntries(); i++) { //WMC - Do only toplevel table entries, doi if(ade_manager::getInstance()->getEntry(i).ParentIdx == UINT_MAX) //WMC - oh hey, we're done with the meaty point in < 10 lines. ade_manager::getInstance()->getEntry(i).SetTable(L, LUA_GLOBALSINDEX, LUA_GLOBALSINDEX); //Oh the miracles of OOP. } //*****INITIALIZE ENUMERATION CONSTANTS mprintf(("ADE: Initializing enumeration constants...\n")); enum_h eh; for(i = 0; i < Num_enumerations; i++) { eh.index = Enumerations[i].def; eh.value = Enumerations[i].value; eh.is_constant = true; eh.setName(Enumerations[i].name); ade_set_args(L, "o", l_Enum.Set(eh)); lua_setglobal(L, Enumerations[i].name); } //*****ASSIGN LUA SESSION mprintf(("ADE: Assigning Lua session...\n")); SetLuaSession(L); //***** LOAD DEFAULT SCRIPTS mprintf(("ADE: Loading default scripts...\n")); load_default_script(L, "forwarders.lua"); load_default_script(L, "cfile_require.lua"); load_default_script(L, "cfile_include.lua"); load_default_script(L, "dkjson.lua"); return 1; } static bool sort_table_entries(const ade_table_entry* left, const ade_table_entry* right) { const char* leftCmp = left->Name != nullptr ? left->Name : left->ShortName; const char* rightCmp = right->Name != nullptr ? right->Name : left->ShortName; if (leftCmp == nullptr) { return false; } if (rightCmp == nullptr) { return false; } return stricmp(leftCmp, rightCmp) < 0; } static bool sort_doc_entries(const ade_table_entry* left, const ade_table_entry* right) { if (left->Instanced == right->Instanced) { // Same type -> compare as normal return sort_table_entries(left, right); } if (left->Instanced) { return true; } return false; } void script_state::OutputLuaDocumentation(ScriptingDocumentation& doc, const scripting::DocumentationErrorReporter& errorReporter) { SCP_vector table_entries; //***Everything for (uint32_t i = 0; i < ade_manager::getInstance()->getNumEntries(); i++) { auto ate = &ade_manager::getInstance()->getEntry(i); if (ate->ParentIdx == UINT_MAX) table_entries.push_back(ate); } std::sort(std::begin(table_entries), std::end(table_entries), sort_doc_entries); for (auto entry : table_entries) { doc.elements.emplace_back(entry->ToDocumentationElement(errorReporter)); } //***Enumerations for (uint32_t i = 0; i < Num_enumerations; i++) { if (is_deprecated_enum_name(Enumerations[i].name)) { continue; } DocumentationEnum e; e.name = Enumerations[i].name; e.value = Enumerations[i].def; auto group = get_enum_group_info(Enumerations[i].name); if (group) { e.group_id = group->id; e.group_title = group->title; e.group_description = group->description; } else { Warning(LOCATION, "Enumeration '%s' has no group entry in Enum_groups. Add a matching prefix entry to get_enum_group_info().", Enumerations[i].name); } const char* desc = get_enum_description(Enumerations[i].name); if (desc != nullptr) { e.description = desc; } doc.enumerations.push_back(std::move(e)); } auto& optionsList = options::OptionsManager::instance()->getOptions(); for (auto& option : optionsList) { const options::OptionBase* thisOpt = option.get(); DocumentationOption o; o.title = thisOpt->getTitle(); o.description = thisOpt->getDescription(); o.key = thisOpt->getConfigKey(); doc.options.push_back(std::move(o)); } } script_state* script_state::GetScriptState(lua_State* L) { lua_getfield(L, LUA_REGISTRYINDEX, ScriptStateReferenceName); Assertion(lua_islightuserdata(L, -1), "Function called for Lua state that is not properly set up!"); auto scriptStatePtr = lua_touserdata(L, -1); lua_pop(L, 1); return static_cast(scriptStatePtr); }