魔兽的UI插件结构
1.使用lua+XML作为配置
分析:XML虽然人机交互很好,但其实没有几个UI是真正用纯XML写的,大多还是用编辑器比较方便。速度很慢,但尚不清楚魔兽代码里是否进行优化
2. Interface\Addons为插件目录,文件夹可以堆叠
3. 每个插件组,需要一个toc文件来做文件读取列表描述,类似于:
# Libraries
embeds.xml
AceGUIWidget-DragLink.lua
Core.lua
# Localization
Locale-enUS.lua
Locale-zhCN.lua
Locale-zhTW.lua
AutoBarDB.lua
AutoBarOptions.lua
AutoBarSearch.lua
…
4.一个插件组里可以拥有多个lua文件,都共享一个独立的全局空间
5.WTF\Account\账号名\服务器名\角色名\AddOns.txt文件描述哪些插件需要读取
根据分析:每次魔兽启动时,都会扫描一次插件目录,并更新这个列表,但是原有的插件读取状态仍然保留,类似于:
Combuctor: enabled
Combuctor_Config: enabled
Parrot: disabled
BattleInfo: disabled
BigWigs: disabled
BigWigs_Extras: disabled
BigWigs_BlackTemple: disabled
leafZone: enabled
InFlight: disabled
…
6. WTF\下的很多SavedVariables目录都是用于保存插件状态的,没有对lua的扩展库进行研究(ACE2/3等等),但是这是一种很好的保存插件数据的方法
OZ_Config = {
{
["bottomCol"] = {
["a"] = 1,
["r"] = 0,
["g"] = 0,
["b"] = 0.6,
},
["maxBars"] = 40,
["barHeight"] = 16,
["titleHeight"] = 20,
["sort2"] = 0,
["fadeAlpha"] = 0.3999999761581421,
["textSize"] = 10,
["colour"] = 2,
["minBars"] = 1,
["heading"] = {
3, -- [1]
0, -- [2]
0, -- [3]
…
7.暂时没有找到魔兽UI的核心API是否用纯脚本提供的证据,但是可以推断,按照暴雪的实力,应该是全lua api写成。
构建安全的lua沙箱
所谓沙箱,就是每个插件拥有独立的_G全局环境,即便用户误将print修改,其他的插件也不会受到影响. 同时,考虑到沙箱的安全性和权限,需要对沙箱函数访问进行订制.以下是本人摸索出的一种方案:
先看下我的UI环境及lua嵌入架构:
1. C++层将必要的API注册到lua层.但都是基于id的全局函数(考虑到效率及便捷),但是实际使用时再在lua层进行OO封装,这和WINDOWS API及MFC的原理类似
2. C++层只提供4种原生控件: Button,Label, EditBox,MultiLineEditBox。其他的控件都是由这些组成。
3. 可以将整个系统分为内核模式和用户模式。
内核模式:可以使用完整的API访问及权限。
用户模式:被沙箱保护,无法访问一些危险的API,例如io
这里,我们使用lua_setfenv进行沙箱构建,首先我们必须将创建每个沙箱对应的table
// 放入沙箱名称
lua_pushstring( mLua, "mysandbox" );
// 将一个table压入栈
dotlua::table ts( mLua , false );
// 调用之前载入好的一个订制沙箱环境的函数
gt.call<void>("SetupSandBox", ts );
// 将沙箱以mysandbox的key保存在注册表中
lua_settable( mLua, LUA_REGISTRYINDEX );
之后使用lua_loadfile载入需要放进沙箱的代码
// 这里将本沙箱对应的环境取出来
lua_pushstring( mLua, "mysandbox" );
lua_gettable( mLua, LUA_REGISTRYINDEX );
// 栈内的情形为
// -1 沙箱table
// -2 chunk function
// 这里必须调用chunk函数
lua_setfenv( mLua, -2 );
调用pcall执行代码
这里的chunk函数,来源于lua_loadfile或者lua_loadbuffer,这2个函数将代码读入,但并不会执行,包括定义全局函数之类的操作。
沙箱订制函数必须提前载入,这里发一个做参考
function SetupSandBox( e )
e._G = e
-- system lib
e.print = print
e.printf = printf
e.table = table
e.string = string
e.debug = debug
e.math = math
e.assert = assert
e.getmetatable = getmetatable
e.ipairs = ipairs
e.pairs = pairs
e.pcall = pcall
e.setmetatable = setmetatable
e.tostring = tostring
e.tonumber = tonumber
e.type = type
e.unpack = unpack
e.collectgarbage = collectgarbage
-- class
e.TREENODE = WIDGET_TREENODE
e.SERIALIZE = WIDGET_SERIALIZE
-- function
e.CreateWidget = CreateWidget
e._Inherit = _Inherit
end
只有被订制的函数,才能被调用
扩展:
为了获得完整的内核模式开发,但又拥有独立的沙箱环境,可以使用setmetatable方式设置一个对全局table的引用,虽然速度慢了点。。
当然,可以支持一套可载入dll订制权限,并注册更多的api给自己的脚本用