Improve modules and sandbox system

This commit is contained in:
Eduardo Bart 2012-07-23 21:22:38 -03:00
parent 61d64c7417
commit 1c3e630237
12 changed files with 238 additions and 145 deletions

View File

@ -12,7 +12,8 @@ local MaxHistory = 1000
local terminalWindow
local terminalButton
local logLocked = false
local commandEnv = newenv()
local commandEnv = {}
setmetatable(commandEnv, { __index = getfenv() } )
local commandTextEdit
local terminalBuffer
local commandHistory = { }

View File

@ -110,6 +110,46 @@ function extends(base)
return derived
end
function runinsandbox(func, ...)
if type(func) == 'string' then
func, err = loadfile(resolvepath(func, 2))
if not func then
error(err)
end
end
local env = { }
local oldenv = getfenv(0)
setmetatable(env, { __index = oldenv } )
setfenv(0, env)
func(...)
setfenv(0, oldenv)
return env
end
function loadasmodule(name, file)
file = file or resolvepath(name, 2)
if package.loaded[name] then
return package.loaded[name]
end
local env = runinsandbox(file)
package.loaded[name] = env
return env
end
local function module_loader(modname)
local module = g_modules.getModule(modname)
if not module then
return '\n\tno module \'' .. modname .. '\''
end
return function()
if not module:load() then
error('unable to load required module ' .. modname)
end
return module:getSandbox()
end
end
table.insert(package.loaders, 1, module_loader)
function export(what, key)
if key ~= nil then
_G[key] = what
@ -130,17 +170,6 @@ function unexport(key)
end
end
function sandbox(what)
what = what or 2
setfenv(what, newenv())
end
function newenv()
local env = { }
setmetatable(env, { __index = getfenv() } )
return env
end
function getfsrcpath(depth)
depth = depth or 2
local info = debug.getinfo(1+depth, "Sn")

View File

@ -1,17 +1,52 @@
Skills = {}
skillsWindow = nil
skillsButton = nil
-- private variables
local skillsWindow
local skillsButton
function init()
connect(LocalPlayer, {
onExperienceChange = onExperienceChange,
onLevelChange = onLevelChange,
onHealthChange = onHealthChange,
onManaChange = onManaChange,
onSoulChange = onSoulChange,
onFreeCapacityChange = onFreeCapacityChange,
onStaminaChange = onStaminaChange,
onMagicLevelChange = onMagicLevelChange,
onSkillChange = onSkillChange
})
-- private functions
local function setSkillValue(id, value)
skillsWindow = g_ui.loadUI('skills.otui', GameInterface.getRightPanel())
skillsButton = TopMenu.addRightGameToggleButton('skillsButton', tr('Skills') .. ' (Ctrl+S)', 'skills.png', toggle)
skillsButton:setOn(true)
g_keyboard.bindKeyDown('Ctrl+S', toggle)
refresh()
end
function terminate()
disconnect(LocalPlayer, {
onExperienceChange = onExperienceChange,
onLevelChange = onLevelChange,
onHealthChange = onHealthChange,
onManaChange = onManaChange,
onSoulChange = onSoulChange,
onFreeCapacityChange = onFreeCapacityChange,
onStaminaChange = onStaminaChange,
onMagicLevelChange = onMagicLevelChange,
onSkillChange = onSkillChange
})
g_keyboard.unbindKeyDown('Ctrl+S')
skillsButton:destroy()
skillsWindow:destroy()
end
function setSkillValue(id, value)
local skill = skillsWindow:recursiveGetChildById(id)
local widget = skill:getChildById('value')
widget:setText(value)
end
local function setSkillPercent(id, percent, tooltip)
function setSkillPercent(id, percent, tooltip)
local skill = skillsWindow:recursiveGetChildById(id)
local widget = skill:getChildById('percent')
widget:setPercent(percent)
@ -21,69 +56,25 @@ local function setSkillPercent(id, percent, tooltip)
end
end
-- public functions
function Skills.init()
connect(LocalPlayer, {
onExperienceChange = Skills.onExperienceChange,
onLevelChange = Skills.onLevelChange,
onHealthChange = Skills.onHealthChange,
onManaChange = Skills.onManaChange,
onSoulChange = Skills.onSoulChange,
onFreeCapacityChange = Skills.onFreeCapacityChange,
onStaminaChange = Skills.onStaminaChange,
onMagicLevelChange = Skills.onMagicLevelChange,
onSkillChange = Skills.onSkillChange
})
skillsWindow = g_ui.loadUI('skills.otui', GameInterface.getRightPanel())
skillsButton = TopMenu.addRightGameToggleButton('skillsButton', tr('Skills') .. ' (Ctrl+S)', 'skills.png', Skills.toggle)
skillsButton:setOn(true)
g_keyboard.bindKeyDown('Ctrl+S', Skills.toggle)
Skills.refresh()
end
function Skills.terminate()
disconnect(LocalPlayer, {
onExperienceChange = Skills.onExperienceChange,
onLevelChange = Skills.onLevelChange,
onHealthChange = Skills.onHealthChange,
onManaChange = Skills.onManaChange,
onSoulChange = Skills.onSoulChange,
onFreeCapacityChange = Skills.onFreeCapacityChange,
onStaminaChange = Skills.onStaminaChange,
onMagicLevelChange = Skills.onMagicLevelChange,
onSkillChange = Skills.onSkillChange
})
g_keyboard.unbindKeyDown('Ctrl+S')
skillsButton:destroy()
skillsButton = nil
skillsWindow:destroy()
skillsWindow = nil
Skills = nil
end
function Skills.refresh()
function refresh()
local player = g_game.getLocalPlayer()
if not player then return end
Skills.onExperienceChange(player, player:getExperience())
Skills.onLevelChange(player, player:getLevel(), player:getLevelPercent())
Skills.onHealthChange(player, player:getHealth(), player:getMaxHealth())
Skills.onManaChange(player, player:getMana(), player:getMaxMana())
Skills.onSoulChange(player, player:getSoul())
Skills.onFreeCapacityChange(player, player:getFreeCapacity())
Skills.onStaminaChange(player, player:getStamina())
Skills.onMagicLevelChange(player, player:getMagicLevel(), player:getMagicLevelPercent())
onExperienceChange(player, player:getExperience())
onLevelChange(player, player:getLevel(), player:getLevelPercent())
onHealthChange(player, player:getHealth(), player:getMaxHealth())
onManaChange(player, player:getMana(), player:getMaxMana())
onSoulChange(player, player:getSoul())
onFreeCapacityChange(player, player:getFreeCapacity())
onStaminaChange(player, player:getStamina())
onMagicLevelChange(player, player:getMagicLevel(), player:getMagicLevelPercent())
for i=0,6 do
Skills.onSkillChange(player, i, player:getSkillLevel(i), player:getSkillLevelPercent(i))
onSkillChange(player, i, player:getSkillLevel(i), player:getSkillLevelPercent(i))
end
end
function Skills.toggle()
function toggle()
if skillsButton:isOn() then
skillsWindow:close()
skillsButton:setOn(false)
@ -93,11 +84,11 @@ function Skills.toggle()
end
end
function Skills.onMiniWindowClose()
function onMiniWindowClose()
skillsButton:setOn(false)
end
function Skills.onSkillButtonClick(button)
function onSkillButtonClick(button)
local percentBar = button:getChildById('percent')
if percentBar then
percentBar:setVisible(not percentBar:isVisible())
@ -109,33 +100,32 @@ function Skills.onSkillButtonClick(button)
end
end
-- hooked events
function Skills.onExperienceChange(localPlayer, value)
function onExperienceChange(localPlayer, value)
setSkillValue('experience', tr(value))
end
function Skills.onLevelChange(localPlayer, value, percent)
function onLevelChange(localPlayer, value, percent)
setSkillValue('level', tr(value))
setSkillPercent('level', percent, tr('You have %s percent to go', 100 - percent))
end
function Skills.onHealthChange(localPlayer, health, maxHealth)
function onHealthChange(localPlayer, health, maxHealth)
setSkillValue('health', tr(health))
end
function Skills.onManaChange(localPlayer, mana, maxMana)
function onManaChange(localPlayer, mana, maxMana)
setSkillValue('mana', tr(mana))
end
function Skills.onSoulChange(localPlayer, soul)
function onSoulChange(localPlayer, soul)
setSkillValue('soul', soul)
end
function Skills.onFreeCapacityChange(localPlayer, freeCapacity)
function onFreeCapacityChange(localPlayer, freeCapacity)
setSkillValue('capacity', freeCapacity)
end
function Skills.onStaminaChange(localPlayer, stamina)
function onStaminaChange(localPlayer, stamina)
local hours = math.floor(stamina / 60)
local minutes = stamina % 60
if minutes < 10 then
@ -147,12 +137,12 @@ function Skills.onStaminaChange(localPlayer, stamina)
setSkillPercent('stamina', percent, tr('You have %s percent', percent))
end
function Skills.onMagicLevelChange(localPlayer, value, percent)
function onMagicLevelChange(localPlayer, value, percent)
setSkillValue('magiclevel', value)
setSkillPercent('magiclevel', percent, tr('You have %s percent to go', 100 - percent))
end
function Skills.onSkillChange(localPlayer, id, level, percent)
function onSkillChange(localPlayer, id, level, percent)
setSkillValue('skillId' .. id, level)
setSkillPercent('skillId' .. id, percent, tr('You have %s percent to go', 100 - percent))
end

View File

@ -3,13 +3,12 @@ Module
description: Manage skills window
author: baxnie, edubart
website: www.otclient.info
sandboxed: true
scripts:
- skills.lua
dependencies:
- game_interface
@onLoad: |
dofile 'skills'
Skills.init()
@onUnload: |
Skills.terminate()
@onLoad: init()
@onUnload: terminate()

View File

@ -3,7 +3,7 @@ SkillFirstWidget < UIWidget
SkillButton < UIButton
height: 21
margin-bottom: 2
&onClick: Skills.onSkillButtonClick
&onClick: onSkillButtonClick
SkillNameLabel < GameLabel
font: verdana-11px-monochrome
@ -35,7 +35,7 @@ MiniWindow
!text: tr('Skills')
height: 150
icon: skills.png
@onClose: Skills.onMiniWindowClose()
@onClose: onMiniWindowClose()
&save: true
MiniWindowContents

View File

@ -30,8 +30,7 @@
Module::Module(const std::string& name)
{
m_name = name;
g_lua.newEnvironment();
m_sandboxEnv = g_lua.ref();
m_sandboxEnv = g_lua.newSandboxEnv();
}
bool Module::load()
@ -49,12 +48,11 @@ bool Module::load()
stdext::throw_exception(stdext::format("dependency '%s' has failed to load", m_name, depName));
}
if(m_sandboxed)
g_lua.setGlobalEnvironment(m_sandboxEnv);
for(const std::string& script : m_scripts) {
g_lua.loadScript(script);
if(m_sandboxed) {
g_lua.getRef(m_sandboxEnv);
g_lua.setEnv();
}
g_lua.safeCall(0, 0);
}
@ -69,8 +67,13 @@ bool Module::load()
g_lua.safeCall(0, 0);
}
if(m_sandboxed)
g_lua.resetGlobalEnvironment();
g_logger.debug(stdext::format("Loaded module '%s'", m_name));
} catch(stdext::exception& e) {
if(m_sandboxed)
g_lua.resetGlobalEnvironment();
g_logger.error(stdext::format("Unable to load module '%s': %s", m_name, e.what()));
return false;
}
@ -93,20 +96,29 @@ void Module::unload()
{
if(m_loaded) {
try {
if(m_sandboxed)
g_lua.setGlobalEnvironment(m_sandboxEnv);
const std::string& onUnloadBuffer = std::get<0>(m_onUnloadFunc);
const std::string& onUnloadSource = std::get<1>(m_onUnloadFunc);
if(!onUnloadBuffer.empty()) {
g_lua.loadBuffer(onUnloadBuffer, onUnloadSource);
if(m_sandboxed) {
g_lua.getRef(m_sandboxEnv);
g_lua.setEnv();
}
g_lua.safeCall(0, 0);
}
if(m_sandboxed)
g_lua.resetGlobalEnvironment();
} catch(stdext::exception& e) {
if(m_sandboxed)
g_lua.resetGlobalEnvironment();
g_logger.error(stdext::format("Unable to unload module '%s': %s", m_name, e.what()));
}
// clear all env references
g_lua.getRef(m_sandboxEnv);
g_lua.clearTable();
g_lua.pop();
m_loaded = false;
//g_logger.info(stdext::format("Unloaded module '%s'", m_name));
g_modules.updateModuleLoadOrder(asModule());
@ -135,6 +147,12 @@ bool Module::hasDependency(const std::string& name)
return false;
}
int Module::getSandbox(LuaInterface* lua)
{
lua->getRef(m_sandboxEnv);
return 1;
}
void Module::discover(const OTMLNodePtr& moduleNode)
{
const static std::string none = "none";

View File

@ -43,7 +43,9 @@ public:
bool isLoaded() { return m_loaded; }
bool isReloadable() { return m_reloadable; }
bool isDependent();
bool isSandboxed() { return m_sandboxed; }
bool hasDependency(const std::string& name);
int getSandbox(LuaInterface *lua);
std::string getDescription() { return m_description; }
std::string getName() { return m_name; }

View File

@ -151,7 +151,7 @@ void ResourceManager::loadFile(const std::string& fileName, std::iostream& out)
PHYSFS_file* file = PHYSFS_openRead(fullPath.c_str());
if(!file) {
out.clear(std::ios::failbit);
stdext::throw_exception(stdext::format("failed to load file '%s': %s", fullPath.c_str(), PHYSFS_getLastError()));
stdext::throw_exception(stdext::format("unable to load file '%s': %s", fullPath.c_str(), PHYSFS_getLastError()));
} else {
int fileSize = PHYSFS_fileLength(file);
if(fileSize > 0) {
@ -167,7 +167,7 @@ void ResourceManager::loadFile(const std::string& fileName, std::iostream& out)
std::ifstream fin(fileName);
if(!fin) {
out.clear(std::ios::failbit);
stdext::throw_exception(stdext::format("failed to load file '%s': %s", fileName.c_str(), PHYSFS_getLastError()));
stdext::throw_exception(stdext::format("unable to file '%s': %s", fileName.c_str(), PHYSFS_getLastError()));
} else {
out << fin.rdbuf();
}

View File

@ -47,6 +47,12 @@ void LuaInterface::init()
{
createLuaState();
// store global environment reference
pushThread();
getEnv();
m_globalEnv = ref();
pop();
// check if demangle_type is working as expected
assert(stdext::demangle_type<LuaObject>() == "LuaObject");
@ -297,7 +303,7 @@ bool LuaInterface::safeRunScript(const std::string& fileName)
try {
runScript(fileName);
return true;
} catch(LuaException& e) {
} catch(stdext::exception& e) {
g_logger.error(stdext::format("Failed to load script '%s': %s", fileName, e.what()));
return false;
}
@ -322,13 +328,9 @@ void LuaInterface::loadScript(const std::string& fileName)
if(!boost::starts_with(fileName, "/"))
filePath = getCurrentSourcePath() + "/" + filePath;
try {
std::string buffer = g_resources.loadFile(fileName);
std::string source = "@" + filePath;
loadBuffer(buffer, source);
} catch(stdext::exception& e) {
throw LuaException(e.what());
}
}
void LuaInterface::loadFunction(const std::string& buffer, const std::string& source)
@ -515,7 +517,7 @@ int LuaInterface::signalCall(int numArgs, int numRets)
else {
throw LuaException("attempt to call a non function value", 0);
}
} catch(LuaException &e) {
} catch(stdext::exception& e) {
g_logger.error(stdext::format("protected lua call failed: %s", e.what()));
}
@ -529,16 +531,16 @@ int LuaInterface::signalCall(int numArgs, int numRets)
return rets;
}
void LuaInterface::newEnvironment()
int LuaInterface::newSandboxEnv()
{
newTable(); // pushes the new environment table
newTable(); // pushes the new environment metatable
getGlobalEnvironment(); // pushes the global environment
getRef(getGlobalEnvironment()); // pushes the global environment
setField("__index"); // sets metatable __index to the global environment
setMetatable(); // assigns environment metatable
return ref(); // return a reference to the environment table
}
///////////////////////////////////////////////////////////////////////////////
// lua C functions
@ -550,13 +552,13 @@ int LuaInterface::luaScriptLoader(lua_State* L)
try {
g_lua.loadScript(fileName);
return 1;
} catch(LuaException& e) {
g_logger.error(stdext::format("failed to load script file '%s': %s", fileName, e.what()));
return 0;
} catch(stdext::exception& e) {
g_lua.pushString(stdext::mkstr("\n\t", e.what()));
return 1;
}
}
int LuaInterface::luaScriptRunner(lua_State* L)
int LuaInterface::lua_dofile(lua_State* L)
{
std::string fileName = g_lua.popString();
if(!boost::ends_with(fileName, ".lua"))
@ -566,13 +568,14 @@ int LuaInterface::luaScriptRunner(lua_State* L)
g_lua.loadScript(fileName);
g_lua.call(0, LUA_MULTRET);
return g_lua.stackSize();
} catch(LuaException& e) {
g_logger.error(stdext::format("failed to load script file '%s': %s", fileName, e.what()));
} catch(stdext::exception& e) {
g_lua.pushString(e.what());
g_lua.error();
return 0;
}
}
int LuaInterface::luaScriptsRunner(lua_State* L)
int LuaInterface::lua_dofiles(lua_State* L)
{
std::string directory = g_lua.popString();
@ -583,13 +586,31 @@ int LuaInterface::luaScriptsRunner(lua_State* L)
try {
g_lua.loadScript(directory + "/" + fileName);
g_lua.call(0, 0);
} catch(LuaException& e) {
g_logger.error(stdext::format("failed to load script file '%s': %s", fileName, e.what()));
} catch(stdext::exception& e) {
g_lua.pushString(e.what());
g_lua.error();
}
}
return 0;
}
int LuaInterface::lua_loadfile(lua_State* L)
{
std::string fileName = g_lua.popString();
if(!boost::ends_with(fileName, ".lua"))
fileName += ".lua";
try {
g_lua.loadScript(fileName);
return 1;
} catch(stdext::exception& e) {
g_lua.pushNil();
g_lua.pushString(e.what());
g_lua.error();
return 2;
}
}
int LuaInterface::luaErrorHandler(lua_State* L)
{
// pops the error message
@ -623,7 +644,8 @@ int LuaInterface::luaCppFunctionCallback(lua_State* L)
while(g_lua.stackSize() > 0)
g_lua.pop();
numRets = 0;
g_logger.error(stdext::format("C++ call failed: %s", g_lua.traceback(e.what())));
g_lua.pushString(stdext::format("C++ call failed: %s", g_lua.traceback(e.what())));
g_lua.error();
}
return numRets;
@ -670,13 +692,24 @@ void LuaInterface::createLuaState()
rawSeti(5);
pop(2);
// replace loadfile
getGlobal("package");
getField("loaders");
pushCFunction(&LuaInterface::luaScriptLoader);
rawSeti(5);
pop(2);
// replace dofile
pushCFunction(&LuaInterface::luaScriptRunner);
pushCFunction(&LuaInterface::lua_dofile);
setGlobal("dofile");
// dofiles
pushCFunction(&LuaInterface::luaScriptsRunner);
pushCFunction(&LuaInterface::lua_dofiles);
setGlobal("dofiles");
// replace loadfile
pushCFunction(&LuaInterface::lua_loadfile);
setGlobal("loadfile");
}
void LuaInterface::closeLuaState()
@ -833,18 +866,13 @@ void LuaInterface::getWeakRef(int weakRef)
remove(-2);
}
void LuaInterface::getGlobalEnvironment()
void LuaInterface::setGlobalEnvironment(int env)
{
pushThread();
getEnv();
remove(-2);
}
void LuaInterface::setGlobalEnvironment()
{
pushThread();
insert(-2);
getRef(env);
assert(isTable());
setEnv();
pop();
}
void LuaInterface::setMetatable(int index)
@ -899,6 +927,24 @@ void LuaInterface::setTable(int index)
lua_settable(L, index);
}
void LuaInterface::clearTable(int index)
{
assert(hasIndex(index) && isTable(index));
pushNil();
bool stop = false;
while(!stop && next(index-1)) {
pop();
pushValue();
if(next(index-2))
pop();
else
stop = true;
insert(-2);
pushNil();
rawSet(index-3);
}
}
void LuaInterface::getGlobal(const std::string& key)
{
lua_getglobal(L, key.c_str());

View File

@ -186,7 +186,7 @@ public:
/// The new environment table is redirected to the global environment (aka _G),
/// this allows to access global variables from _G in the new environment and
/// prevents new variables in this new environment to be set on the global environment
void newEnvironment();
int newSandboxEnv();
template<typename... T>
int callGlobalField(const std::string& global, const std::string& field, const T&... args);
@ -200,9 +200,11 @@ private:
/// Load scripts requested by lua 'require'
static int luaScriptLoader(lua_State* L);
/// Run scripts requested by lua 'dofile'
static int luaScriptRunner(lua_State* L);
static int lua_dofile(lua_State* L);
/// Run scripts requested by lua 'dofiles'
static int luaScriptsRunner(lua_State* L);
static int lua_dofiles(lua_State* L);
/// Run scripts requested by lua 'dofiles'
static int lua_loadfile(lua_State* L);
/// Handle lua errors from safeCall
static int luaErrorHandler(lua_State* L);
/// Handle bound cpp functions callbacks
@ -240,8 +242,9 @@ public:
void getRef(int ref);
void getWeakRef(int weakRef);
void getGlobalEnvironment();
void setGlobalEnvironment();
int getGlobalEnvironment() { return m_globalEnv; }
void setGlobalEnvironment(int env);
void resetGlobalEnvironment() { setGlobalEnvironment(m_globalEnv); }
void setMetatable(int index = -2);
void getMetatable(int index = -1);
@ -253,6 +256,7 @@ public:
void getTable(int index = -2);
void setTable(int index = -3);
void clearTable(int index = -1);
void getEnv(int index = -1);
void setEnv(int index = -2);
@ -334,6 +338,7 @@ private:
int m_cppCallbackDepth;
int m_totalObjRefs;
int m_totalFuncRefs;
int m_globalEnv;
};
extern LuaInterface g_lua;

View File

@ -148,11 +148,13 @@ void Application::registerLuaFunctions()
g_lua.bindClassMemberFunction<Module>("canUnload", &Module::canUnload);
g_lua.bindClassMemberFunction<Module>("isLoaded", &Module::isLoaded);
g_lua.bindClassMemberFunction<Module>("isReloadble", &Module::isReloadable);
g_lua.bindClassMemberFunction<Module>("isSandboxed", &Module::isSandboxed);
g_lua.bindClassMemberFunction<Module>("getDescription", &Module::getDescription);
g_lua.bindClassMemberFunction<Module>("getName", &Module::getName);
g_lua.bindClassMemberFunction<Module>("getAuthor", &Module::getAuthor);
g_lua.bindClassMemberFunction<Module>("getWebsite", &Module::getWebsite);
g_lua.bindClassMemberFunction<Module>("getVersion", &Module::getVersion);
g_lua.bindClassMemberFunction<Module>("getSandbox", &Module::getSandbox);
g_lua.bindClassMemberFunction<Module>("isAutoLoad", &Module::isAutoLoad);
g_lua.bindClassMemberFunction<Module>("getAutoLoadPriority", &Module::getAutoLoadPriority);

View File

@ -312,6 +312,7 @@ void UIWidget::parseBaseStyle(const OTMLNodePtr& styleNode)
} else if(boost::starts_with(node->tag(), "&")) {
std::string fieldName = node->tag().substr(1);
std::string fieldOrigin = "@" + node->source() + "[" + node->tag() + "]";
g_lua.evaluateExpression(node->value(), fieldOrigin);
luaSetField(fieldName);
}