关于 wow-api-lua-environment
面向 World of Warcraft 正式服插件开发者的 Lua 参考技能,涵盖沙箱、污染机制与安全执行,并整理了截至 Patch 12.0.0 的 API 和示例。
主要功能
- 01Lua 5.1 沙箱函数与限制
- 02污染检查与安全执行
- 03战斗锁定与受保护框体
- 04计时器、钩子与状态驱动器
- 05日志与调试工具
适用场景
- 01确认 Lua 函数是否可用
- 02诊断插件污染与受阻操作
- 03实现计时器或安全函数钩子
- 04处理战斗和副本限制
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jburlison/ wowaddonapiagents --skill wow-api-lua-environment安装后在 Claude Code 中输入/wow-api-lua-environment确认已加载。
详细介绍
Lua 环境与安全性(零售版 — 补丁 12.0.0)
WoW Lua 沙箱、安全模型、污染系统、安全执行、计时器、钩子、日志记录和受限操作的全面参考。
来源: https://warcraft.wiki.gg/wiki/World_of_Warcraft_API 安全执行: https://warcraft.wiki.gg/wiki/Secure_Execution_and_Tainting Lua 函数: https://warcraft.wiki.gg/wiki/Lua_functions 截至: 补丁 12.0.0(Build 65655)— 2026 年 1 月 28 日 范围: 仅限零售版。
范围
本技能涵盖:
- Lua 沙箱 — WoW 的 Lua 5.1 环境、受限标准库、被屏蔽的函数
- 污染系统 — 插件代码如何被污染,以及受污染的代码无法执行哪些操作
- 安全执行 — 受保护函数、安全框架、安全处理程序
- 战斗锁定 — 战斗期间插件可以和不可以执行的操作
- C_Timer — 计时器函数(After、NewTicker、NewTimer)
- 钩子 — hooksecurefunc、securecallfunction、securecallmethod
- C_RestrictedActions — 插件限制状态查询
- C_Log — 日志工具
- FrameScript — 框架脚本环境、秘密值、清理
- 调试 — 错误处理、堆栈跟踪、调试工具
何时使用此技能
在以下情况下使用此技能:
- 了解 WoW 中有哪些 Lua 函数可用、哪些被屏蔽
- 处理或调试污点问题
- 编写与安全/受保护框架交互的代码
- 使用计时器、延迟执行或定时循环模式
- 安全地挂钩现有函数
- 了解战斗锁定限制
- 处理插件限制状态(12.0.0 实例限制)
- 记录调试消息
- 使用秘密值和 FrameScript 沙箱
WoW Lua 5.1 沙箱
WoW 使用 Lua 5.1.4,并进行了大量修改。以下标准库函数被屏蔽或移除:
被屏蔽的标准函数
| 被屏蔽的函数 | 原因 |
|---|---|
loadfile() | 无法访问文件系统 |
dofile() | 无法访问文件系统 |
io.* | 无法访问文件系统 |
os.execute() | 无法访问 shell |
os.exit() | 无法关闭客户端 |
os.remove() | 无法访问文件系统 |
os.rename() | 无法访问文件系统 |
os.tmpname() | 无法访问文件系统 |
os.getenv() | 无法访问环境变量 |
package.* | 无软件包系统 |
require() | 无法加载模块 |
module() | 无模块系统 |
newproxy() | 已移除 |
getfenv() | 受限 — 返回只读环境 |
setfenv() | 受到严格限制 |
collectgarbage() | 可用模式受限 |
可用的标准函数
大多数 Lua 核心函数均可正常使用:
- 所有
string.*、table.*、math.*函数 type()、tostring()、tonumber()、rawget()、rawset()、rawequal()、rawlen()pairs()、ipairs()、next()、select()、unpack()pcall()、xpcall()、error()、assert()setmetatable()、getmetatable()coroutine.*(完整支持协程)os.time()、os.date()、os.clock()、os.difftime()print()— 输出到默认聊天框
WoW 新增的全局函数
| 函数 | 描述 |
|---|---|
strsplit(delimiter, str [, pieces]) | 按分隔符拆分字符串 |
strsplittable(delimiter, str [, pieces]) | 拆分为表 |
strjoin(delimiter, ...) | 拼接字符串 |
strtrim(str [, chars]) | 去除空白字符 |
tContains(table, value) | 表中是否包含该值? |
tInsert(table, value) | 插入到表中(别名) |
tDeleteItem(table, value) | 移除该值的首次出现 |
tInvert(table) | 反转键值对 |
wipe(table) | 清空表(保留引用) |
CopyTable(table [, shallow]) | 深拷贝或浅拷贝 |
MergeTable(dest, source) | 将 source 合并到 dest |
Mixin(object, ...) | 将 mixin 方法复制到对象 |
CreateFromMixins(...) | 通过 mixin 创建新对象 |
CreateAndInitFromMixin(mixin, ...) | 创建并调用 Init |
format(formatString, ...) | string.format 的别名 |
tostringall(...) | 将所有参数转换为字符串 |
DevTools_Dump(value, startKey) | 转储值以进行调试 |
污染系统
所有插件代码都以**“已污染”(不安全)状态运行。暴雪 UI 代码以“安全”**(未污染)状态运行。污染系统会阻止插件调用受保护函数或修改安全框架。
污染如何运作
- 插件代码设置的任何变量都会变为已污染
- 污染值会传播——如果污染数据流入暴雪代码,该路径就会受到污染
- 受保护函数会在执行前检查污染状态——如果执行路径受到污染,就会失败
- 安全框架会继承其创建上下文的安全状态
检查污染状态
-- Check if a global variable is tainted
local isTainted, source = issecurevariable("SomeGlobalVar")
-- isTainted: false = secure, true = tainted
-- source: string name of the addon that tainted it (or nil if secure)
-- Check table field
local isTainted, source = issecurevariable(someTable, "someKey")
常见的污染陷阱
-- WRONG — This taints the Blizzard settings table
Settings.RegisterAddOnCategory = myFunc -- TAINT!
-- WRONG — Modifying secure frame in insecure context
local btn = PlayerFrame -- This is a secure Blizzard frame
btn:SetAttribute("type", "spell") -- TAINT — can cause action blocked errors
-- RIGHT — Use hooksecurefunc for observation without tainting
hooksecurefunc("SomeBlizzardFunction", function(...)
-- Your code runs AFTER the original — doesn't taint
end)
安全执行与受保护函数
受保护函数的限制
标记为 #protected 的函数只能由以下代码调用:
- 安全(暴雪)代码
- 由硬件事件触发的安全点击处理程序
SecureActionButtonTemplate处理程序内部
受保护函数包括:
- 所有与战斗相关的施法操作:
CastSpellByName()、CastSpellByID()、UseAction() - 使用物品:
UseItemByName()、UseContainerItem()(战斗中) - 更改目标:
TargetUnit()、AssistUnit()、FocusUnit() - 移动:
MoveForwardStart()、JumpOrAscendStart() - UI 状态:对安全框架调用
SetAttribute()(战斗中)
战斗锁定
-- Check if in combat lockdown
if InCombatLockdown() then
-- Cannot: create/destroy secure frames, change secure attributes
-- Cannot: set points on secure frames, change parent/visibility of secure frames
-- Can: read attributes, modify non-secure frames, queue changes for later
return
end
-- Queue changes for after combat
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:SetScript("OnEvent", function()
-- Combat ended — safe to modify secure frames now
DoSecureFrameChanges()
end)
安全处理程序与模板
-- SecureActionButtonTemplate — allows protected actions via user clicks
local btn = CreateFrame("Button", "MySecureBtn", UIParent, "SecureActionButtonTemplate")
btn:SetAttribute("type", "spell")
btn:SetAttribute("spell", "Fireball")
-- When clicked by hardware event, this will cast Fireball
-- SecureHandlerBaseTemplate — run secure snippets
local frame = CreateFrame("Frame", nil, UIParent, "SecureHandlerBaseTemplate")
frame:SetAttribute("_onstate-combat", [[
-- This snippet runs in the secure environment
if newstate == "combat" then
self:Hide()
else
self:Show()
end
]])
RegisterStateDriver(frame, "combat", "[combat] combat; nocombat")
状态驱动器
-- Register a state driver for automatic secure attribute updates
RegisterStateDriver(frame, "stateName", "conditionalString")
-- e.g., RegisterStateDriver(frame, "visibility", "[combat] hide; show")
UnregisterStateDriver(frame, "stateName")
C_Timer — 计时器 API
计时器函数
| 函数 | 返回值 | 描述 |
|---|---|---|
C_Timer.After(seconds, callback) | — | 单次计时器 |
C_Timer.NewTimer(seconds, callback) | timer | 可取消的单次计时器 |
C_Timer.NewTicker(seconds, callback [, iterations]) | ticker | 重复计时器 |
计时器对象方法
local timer = C_Timer.NewTimer(5, function()
print("5 seconds elapsed")
end)
timer:Cancel() -- Cancel before it fires
local ticker = C_Timer.NewTicker(1, function()
print("Every second")
end, 10) -- Stop after 10 iterations
ticker:Cancel() -- Or cancel early
-- Simple delay (non-cancellable)
C_Timer.After(2, function()
print("2 seconds later")
end)
Hooks — 函数挂钩
hooksecurefunc
主要的安全挂钩机制。你的挂钩会在原函数之后运行,且不会污染原函数。
-- Hook a global function
hooksecurefunc("UseAction", function(slot, checkCursor, onSelf)
print("Action used:", slot)
end)
-- Hook a method on an object
hooksecurefunc(GameTooltip, "SetUnitAura", function(self, ...)
-- Runs after GameTooltip:SetUnitAura
end)
-- IMPORTANT: You CANNOT prevent the original from executing
-- IMPORTANT: You CANNOT modify the return values
-- IMPORTANT: Your hook does NOT taint the original function
securecallfunction / securecallmethod
-- Call a function in secure context (if possible)
securecallfunction(func, arg1, arg2)
-- Call a method in secure context
securecallmethod(object, "MethodName", arg1, arg2)
C_RestrictedActions — 插件限制状态
12.0.0 中新增。用于追踪插件限制何时生效(例如,在副本内)。
| 函数 | 返回值 | 描述 |
|---|---|---|
C_RestrictedActions.GetAddOnRestrictionState(type) | state | 当前限制状态 |
C_RestrictedActions.IsAddOnRestrictionActive(type) | active | 限制当前是否生效? |
C_RestrictedActions.CheckAllowProtectedFunctions(object [, silent]) | protectedFunctionsAllowed | 对象能否调用受保护函数? |
InCombatLockdown() | inCombatLockdown | 战斗锁定是否生效? |
限制事件
| 事件 | 描述 |
|---|---|
ADDON_RESTRICTION_STATE_CHANGED | 限制状态已更改(进入或离开副本) |
PLAYER_REGEN_DISABLED | 进入战斗 |
PLAYER_REGEN_ENABLED | 离开战斗 |
C_Log — 日志记录
| Function | Description |
|---|---|
C_Log.LogMessage(message) | 记录信息消息 |
C_Log.LogWarningMessage(message) | 记录警告 |
C_Log.LogErrorMessage(message) | 记录错误 |
C_Log.LogMessageWithPriority(priority, message) | 使用指定优先级记录 |
注意:
ConsolePrint()已在 12.0.0 中移除。请改用C_Log.LogMessage()。
FrameScript 函数
WoW 提供了用于处理安全值/秘密值系统的特殊 FrameScript 函数:
| Function | Returns | Description |
|---|---|---|
issecurevariable([table,] name) | isSecure, taintSource | 检查污染状态 |
issecretvalue(value) | isSecret | 值是否为秘密值? |
issecrettable(table) | isSecretOrContentsSecret | 表或其内容是否为秘密值? |
canaccessvalue(value) | isAccessible | 插件能否访问此值? |
hasanysecretvalues(values) | isAnyValueSecret | 是否有任意参数为秘密值? |
scrubsecretvalues(values) | scrubbed | 将秘密值替换为 nil |
secretwrap(values) | wrapped | 将值包装为秘密值 |
mapvalues(func, values) | mapped | 对值应用函数(可安全处理秘密值) |
securecallfunction(func, ...) | results | 在安全上下文中调用 |
securecallmethod(obj, method, ...) | results | 在安全上下文中调用方法 |
forceinsecure() | — | 强制以非安全方式执行 |
seterrorhandler(handler) | — | 设置全局错误处理程序 |
geterrorhandler() | handler | 获取当前错误处理程序 |
调试工具
错误处理
-- Set a custom error handler
seterrorhandler(function(msg)
-- msg is the error string
print("ERROR:", msg)
end)
-- Protected call with error handling
local success, err = pcall(function()
-- Code that might error
end)
if not success then
print("Error:", err)
end
-- xpcall with message handler
local success, err = xpcall(function()
error("something broke")
end, function(msg)
return msg .. "\n" .. debugstack(2)
end)
调试堆栈与信息
-- Get a stack trace
local stack = debugstack([thread,] [start [, count1 [, count2]]])
-- Get debug info
local info = debuglocals([thread,] [level])
-- Profile timing
debugprofilestart()
-- ... code to measure ...
local elapsed = debugprofilestop() -- microseconds
调试用斜杠命令
-- /dump expression — evaluates and prints
-- /run code — executes Lua code
-- /script code — same as /run
-- /console cvarName [value] — get/set console variables
常见模式
延迟初始化(等待登录)
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_LOGIN")
frame:SetScript("OnEvent", function(self, event)
-- Safe to initialize — player is logged in
self:UnregisterEvent(event)
InitializeAddon()
end)
安全的 OnUpdate 节流
local elapsed = 0
local THROTTLE = 0.1 -- 100ms
frame:SetScript("OnUpdate", function(self, dt)
elapsed = elapsed + dt
if elapsed < THROTTLE then return end
elapsed = 0
-- Do periodic work
end)
战斗结束后的操作队列
local pendingActions = {}
local function QueueAction(action)
if InCombatLockdown() then
tinsert(pendingActions, action)
else
action()
end
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:SetScript("OnEvent", function()
for _, action in ipairs(pendingActions) do
action()
end
wipe(pendingActions)
end)
优雅处理秘密值(12.0.0)
-- When values might be secret, pass them directly to widgets
local name = UnitName(unit) -- may be secret
myFontString:SetText(name) -- widgets accept secrets
-- Check if a value is secret before trying operations
if not issecretvalue(someValue) then
-- Safe to compare, do arithmetic, etc.
if someValue == "expected" then ... end
else
-- Cannot inspect — pass to UI widget directly
myWidget:SetText(someValue)
end
注意事项与限制
- 不支持
require()— WoW 没有模块系统。使用 TOC 文件控制加载顺序。库会直接嵌入。 setfenv()/getfenv()— 受到严格限制。不要依赖环境操作。collectgarbage()— 只有"count"模式可用。无法强制执行 GC。- 污染具有粘性 — 变量一旦被污染,就会一直保持污染状态。即使将其设回原始值,污染仍然存在。
print()会输出到聊天框 — 与标准 Lua 不同,print()会输出到默认聊天框,而不是 stdout。- 字符串库扩展 — WoW 将
strsplit、strjoin、strtrim和strmatch添加为全局函数(此外还有string.match)。 - 不支持
os.exit()— 无法通过程序终止客户端。 - 支持协程 — 提供完整的协程支持,且常用于异步模式。
- 秘密值(12.0.0) — 现在,某些 API 的返回值是不透明的“秘密”值,无法检查、比较或用于算术运算。完整详情请参阅
wow-api-important指南。 - 实例限制(12.0.0) — 在实例中会阻止
SendAddonMessage()。设计插件时,应确保在副本内容中无需玩家间通信也能正常工作。