mirror of https://github.com/OpenMW/openmw.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
298 lines
11 KiB
C++
298 lines
11 KiB
C++
#include "luastate.hpp"
|
|
|
|
#ifndef NO_LUAJIT
|
|
#include <luajit.h>
|
|
#endif // NO_LUAJIT
|
|
|
|
#include <filesystem>
|
|
|
|
#include <components/debug/debuglog.hpp>
|
|
#include <components/files/conversion.hpp>
|
|
#include <components/vfs/manager.hpp>
|
|
|
|
namespace LuaUtil
|
|
{
|
|
|
|
static std::string packageNameToVfsPath(std::string_view packageName, const VFS::Manager* vfs)
|
|
{
|
|
std::string path(packageName);
|
|
std::replace(path.begin(), path.end(), '.', '/');
|
|
std::string pathWithInit = path + "/init.lua";
|
|
path.append(".lua");
|
|
if (vfs->exists(path))
|
|
return path;
|
|
else if (vfs->exists(pathWithInit))
|
|
return pathWithInit;
|
|
else
|
|
throw std::runtime_error("module not found: " + std::string(packageName));
|
|
}
|
|
|
|
static std::filesystem::path packageNameToPath(
|
|
std::string_view packageName, const std::vector<std::filesystem::path>& searchDirs)
|
|
{
|
|
std::string path(packageName);
|
|
std::replace(path.begin(), path.end(), '.', '/');
|
|
std::string pathWithInit = path + "/init.lua";
|
|
path.append(".lua");
|
|
for (const auto& base : searchDirs)
|
|
{
|
|
std::filesystem::path p1 = base / path;
|
|
if (std::filesystem::exists(p1))
|
|
return p1;
|
|
std::filesystem::path p2 = base / pathWithInit;
|
|
if (std::filesystem::exists(p2))
|
|
return p2;
|
|
}
|
|
throw std::runtime_error("module not found: " + std::string(packageName));
|
|
}
|
|
|
|
static const std::string safeFunctions[] = { "assert", "error", "ipairs", "next", "pairs", "pcall", "select",
|
|
"tonumber", "tostring", "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable" };
|
|
static const std::string safePackages[] = { "coroutine", "math", "string", "table" };
|
|
|
|
LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf)
|
|
: mConf(conf)
|
|
, mVFS(vfs)
|
|
{
|
|
mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::bit32, sol::lib::string,
|
|
sol::lib::table, sol::lib::os, sol::lib::debug);
|
|
|
|
mLua["math"]["randomseed"](static_cast<unsigned>(std::time(nullptr)));
|
|
mLua["math"]["randomseed"] = [] {};
|
|
|
|
mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; };
|
|
|
|
// Some fixes for compatibility between different Lua versions
|
|
if (mLua["unpack"] == sol::nil)
|
|
mLua["unpack"] = mLua["table"]["unpack"];
|
|
else if (mLua["table"]["unpack"] == sol::nil)
|
|
mLua["table"]["unpack"] = mLua["unpack"];
|
|
if (LUA_VERSION_NUM <= 501)
|
|
{
|
|
mLua.script(R"(
|
|
local _pairs = pairs
|
|
local _ipairs = ipairs
|
|
pairs = function(v) return (rawget(getmetatable(v) or {}, '__pairs') or _pairs)(v) end
|
|
ipairs = function(v) return (rawget(getmetatable(v) or {}, '__ipairs') or _ipairs)(v) end
|
|
)");
|
|
}
|
|
|
|
mLua.script(R"(
|
|
local printToLog = function(...)
|
|
local strs = {}
|
|
for i = 1, select('#', ...) do
|
|
strs[i] = tostring(select(i, ...))
|
|
end
|
|
return writeToLog(table.concat(strs, '\t'))
|
|
end
|
|
printGen = function(name) return function(...) return printToLog(name, ...) end end
|
|
|
|
function createStrictIndexFn(tbl)
|
|
return function(_, key)
|
|
local res = tbl[key]
|
|
if res ~= nil then
|
|
return res
|
|
else
|
|
error('Key not found: '..tostring(key), 2)
|
|
end
|
|
end
|
|
end
|
|
function pairsForReadOnly(v)
|
|
local nextFn, t, firstKey = pairs(getmetatable(v).t)
|
|
return function(_, k) return nextFn(t, k) end, v, firstKey
|
|
end
|
|
function ipairsForReadOnly(v)
|
|
local nextFn, t, firstKey = ipairs(getmetatable(v).t)
|
|
return function(_, k) return nextFn(t, k) end, v, firstKey
|
|
end
|
|
local function nextForArray(array, index)
|
|
index = (index or 0) + 1
|
|
if index <= #array then
|
|
return index, array[index]
|
|
end
|
|
end
|
|
function ipairsForArray(array)
|
|
return nextForArray, array, 0
|
|
end
|
|
|
|
getmetatable('').__metatable = false
|
|
getSafeMetatable = function(v)
|
|
if type(v) ~= 'table' then error('getmetatable is allowed only for tables', 2) end
|
|
return getmetatable(v)
|
|
end
|
|
)");
|
|
|
|
mSandboxEnv = sol::table(mLua, sol::create);
|
|
mSandboxEnv["_VERSION"] = mLua["_VERSION"];
|
|
for (const std::string& s : safeFunctions)
|
|
{
|
|
if (mLua[s] == sol::nil)
|
|
throw std::logic_error("Lua function not found: " + s);
|
|
mSandboxEnv[s] = mLua[s];
|
|
}
|
|
for (const std::string& s : safePackages)
|
|
{
|
|
if (mLua[s] == sol::nil)
|
|
throw std::logic_error("Lua package not found: " + s);
|
|
mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mLua[s]);
|
|
}
|
|
mSandboxEnv["getmetatable"] = mLua["getSafeMetatable"];
|
|
mCommonPackages["os"] = mSandboxEnv["os"]
|
|
= makeReadOnly(tableFromPairs<std::string_view, sol::function>({ { "date", mLua["os"]["date"] },
|
|
{ "difftime", mLua["os"]["difftime"] }, { "time", mLua["os"]["time"] } }));
|
|
}
|
|
|
|
LuaState::~LuaState()
|
|
{
|
|
// Should be cleaned before destructing mLua.
|
|
mCommonPackages.clear();
|
|
mSandboxEnv = sol::nil;
|
|
}
|
|
|
|
sol::table makeReadOnly(const sol::table& table, bool strictIndex)
|
|
{
|
|
if (table == sol::nil)
|
|
return table;
|
|
if (table.is<sol::userdata>())
|
|
return table; // it is already userdata, no sense to wrap it again
|
|
|
|
lua_State* luaState = table.lua_state();
|
|
sol::state_view lua(luaState);
|
|
sol::table meta(lua, sol::create);
|
|
meta["t"] = table;
|
|
if (strictIndex)
|
|
meta["__index"] = lua["createStrictIndexFn"](table);
|
|
else
|
|
meta["__index"] = table;
|
|
meta["__pairs"] = lua["pairsForReadOnly"];
|
|
meta["__ipairs"] = lua["ipairsForReadOnly"];
|
|
|
|
lua_newuserdata(luaState, 0);
|
|
sol::stack::push(luaState, meta);
|
|
lua_setmetatable(luaState, -2);
|
|
return sol::stack::pop<sol::table>(luaState);
|
|
}
|
|
|
|
sol::table getMutableFromReadOnly(const sol::userdata& ro)
|
|
{
|
|
return ro[sol::metatable_key].get<sol::table>()["t"];
|
|
}
|
|
|
|
void LuaState::addCommonPackage(std::string packageName, sol::object package)
|
|
{
|
|
if (!package.is<sol::function>())
|
|
package = makeReadOnly(std::move(package));
|
|
mCommonPackages.emplace(std::move(packageName), std::move(package));
|
|
}
|
|
|
|
sol::protected_function_result LuaState::runInNewSandbox(const std::string& path, const std::string& namePrefix,
|
|
const std::map<std::string, sol::object>& packages, const sol::object& hiddenData)
|
|
{
|
|
sol::protected_function script = loadScriptAndCache(path);
|
|
|
|
sol::environment env(mLua, sol::create, mSandboxEnv);
|
|
std::string envName = namePrefix + "[" + path + "]:";
|
|
env["print"] = mLua["printGen"](envName);
|
|
env["_G"] = env;
|
|
env[sol::metatable_key]["__metatable"] = false;
|
|
|
|
auto maybeRunLoader = [&hiddenData](const sol::object& package) -> sol::object {
|
|
if (package.is<sol::function>())
|
|
return call(package.as<sol::function>(), hiddenData);
|
|
else
|
|
return package;
|
|
};
|
|
sol::table loaded(mLua, sol::create);
|
|
for (const auto& [key, value] : mCommonPackages)
|
|
loaded[key] = maybeRunLoader(value);
|
|
for (const auto& [key, value] : packages)
|
|
loaded[key] = maybeRunLoader(value);
|
|
env["require"] = [this, env, loaded, hiddenData](std::string_view packageName) mutable {
|
|
sol::object package = loaded[packageName];
|
|
if (package != sol::nil)
|
|
return package;
|
|
sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS));
|
|
sol::set_environment(env, packageLoader);
|
|
package = call(packageLoader, packageName);
|
|
loaded[packageName] = package;
|
|
return package;
|
|
};
|
|
|
|
sol::set_environment(env, script);
|
|
return call(script);
|
|
}
|
|
|
|
sol::environment LuaState::newInternalLibEnvironment()
|
|
{
|
|
sol::environment env(mLua, sol::create, mSandboxEnv);
|
|
sol::table loaded(mLua, sol::create);
|
|
for (const std::string& s : safePackages)
|
|
loaded[s] = static_cast<sol::object>(mSandboxEnv[s]);
|
|
env["require"] = [this, loaded, env](const std::string& module) mutable {
|
|
if (loaded[module] != sol::nil)
|
|
return loaded[module];
|
|
sol::protected_function initializer = loadInternalLib(module);
|
|
sol::set_environment(env, initializer);
|
|
loaded[module] = call(initializer, module);
|
|
return loaded[module];
|
|
};
|
|
return env;
|
|
}
|
|
|
|
sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res)
|
|
{
|
|
if (!res.valid() && static_cast<int>(res.get_type()) == LUA_TSTRING)
|
|
throw std::runtime_error("Lua error: " + res.get<std::string>());
|
|
else
|
|
return std::move(res);
|
|
}
|
|
|
|
sol::function LuaState::loadScriptAndCache(const std::string& path)
|
|
{
|
|
auto iter = mCompiledScripts.find(path);
|
|
if (iter != mCompiledScripts.end())
|
|
return mLua.load(iter->second.as_string_view(), path, sol::load_mode::binary);
|
|
sol::function res = loadFromVFS(path);
|
|
mCompiledScripts[path] = res.dump();
|
|
return res;
|
|
}
|
|
|
|
sol::function LuaState::loadFromVFS(const std::string& path)
|
|
{
|
|
std::string fileContent(std::istreambuf_iterator<char>(*mVFS->get(path)), {});
|
|
sol::load_result res = mLua.load(fileContent, path, sol::load_mode::text);
|
|
if (!res.valid())
|
|
throw std::runtime_error("Lua error: " + res.get<std::string>());
|
|
return res;
|
|
}
|
|
|
|
sol::function LuaState::loadInternalLib(std::string_view libName)
|
|
{
|
|
const auto path = packageNameToPath(libName, mLibSearchPaths);
|
|
sol::load_result res = mLua.load_file(Files::pathToUnicodeString(path), sol::load_mode::text);
|
|
if (!res.valid())
|
|
throw std::runtime_error("Lua error: " + res.get<std::string>());
|
|
return res;
|
|
}
|
|
|
|
std::string getLuaVersion()
|
|
{
|
|
#ifdef NO_LUAJIT
|
|
return LUA_RELEASE;
|
|
#else
|
|
return LUA_RELEASE " (" LUAJIT_VERSION ")";
|
|
#endif
|
|
}
|
|
|
|
std::string toString(const sol::object& obj)
|
|
{
|
|
if (obj == sol::nil)
|
|
return "nil";
|
|
else if (obj.get_type() == sol::type::string)
|
|
return "\"" + obj.as<std::string>() + "\"";
|
|
else
|
|
return call(sol::state_view(obj.lua_state())["tostring"], obj);
|
|
}
|
|
|
|
}
|