来自哀冬大神4月新作,小编注:我知道大家看不懂,想要直接的拿来就按的一键傻瓜式,正在整理
喜欢研究的可以看哀冬这次的源码:https://cn.wago.io/0jyJ6PFcF
不喜欢研究等喂饭的一两天整理好发布引言插件系统是《魔兽世界》开放性的基石,游戏的设计师们在玩家创造力与游戏公平性之间,也构建了一道看似不可逾越的屏障。而虚空之花正扎根在这道屏障上的裂隙优雅绽放。本文将基于虚空之花的历代内核原理介绍逐步向大家揭示魔兽世界插件系统中的高级框体设计技巧,也作为第五代内核源码开放的序幕。第一章 移形换影-框体和安全框架隔离系统简介
魔兽世界的插件系统建立在严格的安全框架之上。而整个UI设计中的框体分为两类:普通框体(Frame)和安全框体(SecureFrame)。普通框体可以自由修改大小、位置、纹理等属性,但无法绑定任何战斗动作;安全框体(如SecureActionButtonTemplate)则能绑定技能、物品、宏命令,却受到“限制环境”(Restricted Environment)的严格约束——战斗中,任何来自“非安全代码”(通常指来自玩家的插件代码)的修改企图都会被系统拒绝。该安全框架系统自TBC开始上线一直持续至今,用以保护游戏的开放API不被滥用于破坏公平性的插件。
第一代虚空之花的方案十分的简单,因为当时安全框体虽然不允许修改绑定的技能但是居然可以在战斗中调用SetPoint修改位置。我们只需在战斗前,通过CreateFrame创建数十个绑定好技能的安全按钮,并将其预先放置于屏幕可视区域之外。而战斗中,当APL(输出优先级循环逻辑的缩写)计算出当前最佳释放技能时,直接将对应的按钮通过SetPoint函数瞬移到屏幕中的固定位置。玩家只需持续点击该位置,就会自动点中当前最优技能按钮。始于简单粗暴终于简单粗暴,在WLK怀旧服更新幻化的版本后,安全框体无法再在战斗中被非安全代码移动了。
第二章 光暗交织-安全框体回调系统简介
安全框体并不是完全无法在战斗中修改的,它只是不能被“非安全”代码修改,那么什么是安全代码呢?安全代码包括了游戏UI自带的代码以及,重点来了——还包括玩家在战斗之外通过开放API接口预先绑定在特定安全事件上的预设代码片段!看似复杂,但是我们参考一下安全回调注册API的解释(https://warcraft.wiki.gg/wiki/API_SecureHandlerWrapScript)。这个接口允许为安全框体的事件(如OnClick、OnEnter)注册一些代码片段以字符串形式注入安全框架内执行。唯一的限制是这串代码必须在战斗前预设,且安全区内与安全区外的执行环境完全不同,只允许有限的函数被玩家调用,具体参考(https://warcraft.wiki.gg/wiki/RestrictedEnvironment)。
同时,虽然在战斗中无法通过“非安全”代码修改安全框体属性,但是我们可以修改普通框体的属性来干扰玩家对安全框体的操作。比如将一个普通框体覆盖在安全框体的上层,通过控制普通框体的尺寸大小来控制玩家是否能点击到安全框体。结合函数列表查阅我们又注意到了SetPropagateMouseClicks,这个函数允许将框体设置成可以点击穿透。那么一切就绪,第二代方案思路已经构成。将一大堆框体按照“普通-安全-普通-安全”这样交叉叠放,其中普通框体是否穿透受APL控制,安全框体始终可以穿透。当一次玩家点击可以穿透多少层普通框体就代表可以触发几次安全框体的OnClick事件,然后我们记录这些事件的发生次数就可以在安全区内得到非安全区传递的信息。
该方案发布一个月后,游戏中修改了战斗中的点击穿透机制,从此在战斗中安全框体无法再被穿透了,为此不惜连带影响了一批UI插件。但这次经历让我们深刻理解了安全框体的事件回调机制,并证明安全框架并非绝对安全——只要存在交互,就存在信息传递的可能性!
第三章 量子纠缠-框体联动关系简介
前两代方案都依赖鼠标交互,战斗中不断点击鼠标在走位频繁的战斗中非常不便。这让我们思考是否存在非交互型的技术路线,而直接从安全框架设计的逻辑上找到突破口。我们仔细研究框体的每一个API(https://warcraft.wiki.gg/wiki/Widget_API)。
注意到CreateFrame接口的第三个参数叫做parent,这是给一个框体创建时指定它的父亲。这个设计的存在可以让新创建的框体继承父框体的属性并让他们建立依附关系。比如一个大界面下可以依附许多按钮,当你关闭界面时界面内依附的按钮也会一起消失,这种同进同退的联动关系极大的方便了界面开发,不再需要细枝末节的控制每一个框体的细节动作。你可以在游戏中输入/fstack来观察游戏界面内的每一个框体和他们的依附关系。那么能否将非安全框体和安全框体联动起来?SetParent接口进入了视野,这个接口允许我们在战斗中动态的修改框体父子关系。但它也存在巨大的限制,即当安全框体设定普通框体作为父节点时,系统为了防止非安全代码通过父节点影响安全子节点,会将整个父节点升格为安全框体。这种“安全提升”机制本意是为了防止玩家滥用,但实际上却成了一根筋两头堵。
第三代方案基于对此机制的利用,我们在战斗前创建普通父框体,设置其各种属性(比如以大小尺寸数值作为信息传递通道),然后让其领养一个安全按钮作为子节点。在父子绑定的瞬间,父框体被提升为安全框体,其当前所有属性被连带进入到安全环境中。战斗中我们就可以直接读取这个升格为安全框体的普通父框体属性作为信息传递通道在安全区内直接使用!
第三朵虚空之花在ICC开放前夜发布,由于其完全不依赖鼠标纯键盘驱动的良好体验,引发了社区剧烈震荡,乃至于全网猫德外挂都顶着哀冬的名号蹭热度。直到一个多月后,开放API的版本更新,战斗中任何修改安全框体父子关系的操作都会失败,但逻辑路线的突破口已经证明了所谓的“安全框架”表现并不理想。
第四章 无中生有-污染系统简介
第四套方案的故事开始之前,我们要先提到一个重要的概念“污染”。前面提到的“安全”和“不安全”,是面向框体这类数据结构的属性概念,它标记了一个框体能办到什么不能办到什么。而污染则是面向执行状态的概念,标记着当前执行流程能办到什么不能办到什么。插件系统总是从游戏原生代码开始执行,一直保持干净状态直到调用到玩家的第三方插件代码就会变成污染状态。污染状态下写入的数据也会被污染,而原本干净状态执行的代码读到这些被污染的数据又会被传染成污染状态,被污染的代码和数据将无法在战斗中正常工作。这套机制严格的保证了原生代码不会被第三方代码劫持从而实现一些不合规的操作。
过去的多个版本我们一直期待着有一个“信使”能够从非安全区出发把信息传递到安全区,或者反过来从安全区出发把非安全区信息带回,但是安全框架隔离机制阻止了这个过程,让“信使”总是无功而返。那么仔细想一想一个问题——没有信息本身是不是一种信息?结合前面提到的“污染”再思考一下呢?4.x内核的方案原理就基于【这段文字好像覆盖满了触手,所以我们决定暂不揭晓】,从而实现了信息的传递。
第四朵虚空之花的根深深的扎在了整个安全框架之上,犹如上古之神的根须已与艾泽拉斯的中枢纠缠在一起难解难分,为什么当年阿曼苏尔拔掉亚煞极之后不继续拔掉克苏恩、尤格萨隆、恩佐斯呢?
第五章 时空转换-原生UI之殇
之前第二代方案是基于玩家鼠标与框体空间上的互动来实现的,即一次操作多重触发。在版本更新后,一次用户操作(比如点击、移动)只能让一个安全框体响应一次了。那有没有办法让信息不经过用户操作而触发变化的呢?我们视野回到安全区内的函数列表(https://warcraft.wiki.gg/wiki/RestrictedEnvironment)的第一个函数SecureCmdOptionParse,它的作用是用来解析宏命条件的。宏的解析不需要玩家的实际操作,当你的鼠标指向敌人时候SecureCmdOptionParse("[@mouseover,harm]0;1")的结果就是0,反之就是1。所以我们以时间换空间,创造一个类型为“SecureUnitButtonTemplate”的敌对目标框体,再在它的之上覆盖一层不可指向的普通框体,从而控制玩家鼠标是否能指向这个敌对目标框体来影响宏条件判定进行一连串1和0的信息的传递。方案成功了?
不,还没有成功,上述方案还欠缺重要的一个环节,安全区内没有触发源来驱动SecureCmdOptionParse的宏判定。如果让人手工来狂按鼠标键盘进行触发的话体验太糟糕了(之前的B站up主@隐幻杀曾经尝试到了这一步)。我们需要找到一个在安全区可以稳定的触发源,那么一定有聪明人想到了SecureHandlerWrapScript不是可以注册OnHide和OnShow事件吗?我们让框体在触发OnShow回调的时候调用Hide函数,然后再在触发OnHide回调的时候调用Show函数。岂不是可以让一个安全框体一直闪烁,在闪烁的安全回调代码中判定SecureCmdOptionParse。方案成功了?
不,还没有成功,因为聪明的游戏设计师也考虑到了这一点。SecureCmdOptionParse的判定依赖于界面的真实绘制,凭空在lua代码里控制消失和显示并不会真的让框体马上消失和显示,会影响SecureCmdOptionParse的判定。所以第五朵虚空之花绽放的真正核心要素是要在安全区逻辑内找到一个稳定的异步触发源。于是我们把目光转向了原生UI代码,从中寻找机会。
我们发现原生UI接口中,为了解决安全框体自动隐藏接口,后台设定了一个叫做SecureHoverDriverManager的系统框体对注册自动隐藏框体的状态管理,当没有需要关注的状态时他会隐藏,于是我们利用一个诱饵框体在安全区内调用RegisterAutoHide引诱其现身后,利用它无事自动隐藏的特性绑定了SecureHoverDriverManager的OnHide回调作为我们的异步触发源。在经历了一段时间的原生UI渲染管线时序摸索后,我们找到了稳定每3帧渲染tick触发一次1/0bit位传输的窄信道。
至此第五朵虚空之花凑齐了宏命令判定、框体遮盖技巧、安全区异步触发源这套OTK卡组,实现了跨越安全框架的信息传递!
第六章 凋谢与新生-插件系统的设计哲学之变
回顾这几朵虚空之花的绽放之姿,我们看到的不仅仅是一连串代码的进化,更是一群业余插件开发者们在游戏划定的红线边缘试探的极致体现。结合近期正式服大量禁用战斗类插件API的动作,这类WA的存在引发了一个深层次的社区争论:虚空之花这到底算不算外挂?
对于普通玩家而言,此类WA实现了近乎完美的自动化输出,功能极其强大,在表现形式上与传统的按键精灵或内存挂十分接近,因此很多人将其视为破坏平衡的“外挂”。但从技术合规性的角度来看,该系列WA从头到尾没有读取任何非法的内存数据,没有注入任何外部程序,其每一行代码、每一个机制,都是利用魔兽世界原生提供的合法API和UI机制编写的。基于这种现实,虚空之花不是挂!
但插件真正的边界究竟应该划在哪里?这或许是游戏自身需要去解答的问题。我们很高兴看到在正式服中,插件系统的设计哲学已经做出了改变,游戏内推出的一键输出解决了新手上手困难问题,12.0插件开发API大幅度删减也几乎终结了BOSS难度和插件军备竞赛的螺旋。而怀旧服作为一段凝固的时光,其插件框架被锁定在过去。过时的设计和玩家的新需求错配之下,我们看到的是游戏生态的持续恶化,如果继续置之不理放任崩坏,那虚空之花也将永不凋谢~
点下方留言 分享你的观点
热门跟贴