简介:面向冒险岛 WZ 修改零基础玩家的一份入门图文教程,由尐静制作,旨在帮助新手建立对 WZ 文件读写与修改的基本认识,并走完从工具使用到补丁分享的完整入门流程。内容围绕魔法无敌、闹钟不缩头、蛋蛋无延迟、物品过滤、代码提取、辅助技能全屏化与无延迟、界面美化等常见修改方法展开,配合 deMSwz、HR、Winpatch 等工具的操作图示,并反复提示修改前备份 WZ 文件、避免与游戏同时运行等注意事项,读者可据此独立完成小型修改,并自行排查常见问题。包内共 1 个 doc 文件,约 890KB,以图文步骤排版为主,篇幅精简,便于对照操作。目前已有 4374 人学习,适合刚接触 WZ 修改、希望先完整走通一遍基础流程再深入的学习者。
1. 客户端资源包改坏三次之后:WZ 修改到底在改什么
我改坏第三个 mob.wz 之后才承认,WZ 这类资源包不是压缩包,而是一棵带类型的树:.wz是大容器,里面按8500001.img、0400.img这种节点切开,节点下再挂 int、string、canvas 属性,所谓“修改”其实是改树上某个叶子。这篇拆的是初级 WZ 修改的完整链路——批量刷属性、按节点删动作、做差分补丁,工具是 deMSwz、HeRepacker(下称 HR)、Winpatch,再配合 String.wz 反查代码。它适合已经能打开包、但一改就崩或改完没反应的人。所有操作只在自有客户端副本和离线环境里做,线上客户端有资源校验,别拿正式账号试。
2. 先认清 WZ 的树形结构:三件工具的分工与运行环境
多数人第一次打开 mob.wz 会懵:左边一长串数字命名的 img 节点,右边一堆叫attack1、die1、info的属性,看不出“文件”在哪。原因在于 WZ 不是归档格式,是序列化的属性树,工具做的事是解析这棵树、让你在树上增删改,最后再序列化回去。搞清楚节点模型再动手,能少踩掉一半的坑。
2.1 img 节点、属性类型与 canvas
一个.wz文件顶层通常是一批xxx.img节点,编号即对象 id,例如8500001.img对应某个 BOSS,0400.img是某类物品的一个分卷。img 之下是属性树,常见类型有 int(4 字节有符号整型)、short、string、float/double、vector(坐标对)、uol(对另一节点的引用)、sub(子节点容器)和 canvas(内嵌一张 PNG,附带 origin 坐标)。
类型决定你能怎么改。int 属性可以塞进一个大数,编辑器按二进制写回;string 属性你写一串数字进去,客户端读的时候仍按字符串处理,表现就是“改了但没反应”。canvas 属性没有“部分修改”这回事,只能整张导出、改完、整张回填。我见过有人想调一个图标的不透明度,直接在属性面板里找 alpha,找了半天——那东西根本不在属性里,在 PNG 像素里。
还有一点容易忽略:同名属性在不同 img 下可能类型不同。同样是攻击间隔类的键,在某些怪上是 int,在另一些怪上是 short。short 只有 2 字节,写 2147483647 会直接截断,表现是数值变成完全不相干的另一个数。所以批量改之前,先抽三五个节点看看类型是否一致。
2.2 三件工具各管一段活
它们不是互相替代的关系,硬要拿一个干全部的活,结果一般是包被写坏。
| 工具 | 擅长 | 不擅长 | 依赖 |
|---|---|---|---|
| deMSwz | 打开大包、按属性名批量刷值、导出文本清单 | 编辑图像、精细删节点 | 独占文件,客户端不能同时运行 |
| HeRepacker(HR) | 树形浏览、删节点、PNG 导出与回填、跨版本读包 | 大批量按名刷值 | 需要 .NET Framework 3.5 |
| Winpatch | 两个目录做差分、生成补丁包 | 不理解 WZ 语义,只认文件字节 | 无特殊依赖 |
deMSwz 的核心能力是“自定义值 + Process All”:你给一个键名和一个值,它对包内所有同名节点统一写入。改 mob.wz 这种每个怪物 img 下都可能有同名键的包,这是唯一现实的做法,手点几千个节点不现实。HR 的核心能力是树操作,删动作、删图标、导出 PNG 都靠它,它的“读取模式”还决定了解析规则,选错就是一堆乱码。Winpatch 完全不关心 WZ 是什么,它只比较两个目录的文件差异,所以它既能做出干净的补丁,也能一步把你客户的整个目录清空——后面单独说。
2.3 运行环境与备份纪律
HR 依赖 .NET Framework 3.5,这个运行时在较新的系统上默认不装,属于“按需启用”的可选功能。直接双击报缺少运行时的,用管理员权限开命令行启用:
# 以管理员身份运行,启用 .NET Framework 3.5 可选功能 # /Online 表示从系统源获取,/All 表示包含父功能 DISM /Online /Enable-Feature /FeatureName:NetFx3 /All # 启用后确认状态,State 应为 Enabled DISM /Online /Get-FeatureInfo /FeatureName:NetFx3这里两个参数值得说:/FeatureName:NetFx3是 3.5 的功能名,别写成 NetFx4;/All会把依赖的父功能一起带上,漏了它有时会报 0x800f0906 之类的错误。启用完不需要重启,但正在运行的客户端要关掉。
备份纪律比工具重要。我自己的目录长这样:mob.wz.orig是原始包,mob.wz.wip是当前改到一半的工作副本,mob.wz才是工具实际读写的文件。每完成一个独立改动(比如只改了攻击间隔),就把 wip 另存一份带时间戳的,再继续下一步。这样任何一步翻车都能退回上一个可用状态,而不是从原始包重来。
# 改包前固定跑一遍的备份脚本 set -e SRC="$1" # 传入要改的 wz 文件路径 TS=$(date +%Y%m%d_%H%M%S) cp -n "$SRC" "$SRC.orig" # -n 保证原始备份只写一次,不会被后续覆盖 cp "$SRC" "$SRC.$TS.wip" # 每轮改动前留一个带时间戳的工作副本 ls -lh "$SRC"* # 确认备份落盘,大小与源文件一致cp -n这个参数是关键:原始备份只允许写一次,避免手滑把已经改坏的包当成“原始包”再备份一遍,那就彻底没有后悔药了。最后那行ls -lh不是多余动作——WZ 包动辄几十上百 MB,磁盘满导致的半截文件,在工具里打开时会报各种莫名其妙的解析错误,先看一眼大小能省掉半小时排查。
3. 批量属性注入:用自定义键值把 mob.wz 的改动一次刷完
mob.wz 是怪物数据包,节点多、单节点属性杂,适合用 deMSwz 的批量写入能力一次性处理。拿“攻击后的行为间隔”这类键举例:它在大量怪物 img 下都存在,逐个点开改不现实,而批量写入只需要给键名和值,剩下的交给工具遍历。
3.1 批量写入靠的是“按名匹配”,不是“按路径匹配”
deMSwz 左下角有一个自定义值区域,前面是键名输入框,后面是数值输入框,旁边一个勾选框,再加一个 Process All 按钮。它的行为是:遍历当前打开包内的所有节点,凡是属性名与输入键名完全一致的,把值写成你给的那个数。注意是完全一致——大小写敏感,多一个空格都不算命中。
这就带来两个前提。第一,这个键在包里必须已经存在,批量写入是覆盖不是新增,键名写错的结果是零命中,而工具不会报错,进度条照样跑完。第二,同名键的类型要一致,前面说过 short 和 int 混用会截断。稳妥的做法是先在树上手动翻两三个节点,确认键名拼写和类型,再开批量。
提示:改之前先把客户端进程完全退出。deMSwz 打开包时会独占文件,客户端在跑的情况下,要么打开失败,要么保存时写不进去,最坏的情况是客户端正好在读这个文件,两边一起把包写花。
3.2 判断“包已经完整读入”的两个信号
mob.wz 体积大,打开要等。判断是否读完看的不是界面能不能点,而是主窗口下方那行reading WZ的进度提示:它消失,并且没有弹出任何错误对话框,才算完整载入。这两个条件缺一不可——有时候进度提示消失了,但弹了一个解析警告,你点了忽略继续操作,后面保存出来的包在某个节点上是坏的,而且坏的位置跟你改的地方可能毫无关系。
载入过程中不要点任何按钮,也不要切换窗口去干别的然后回来狂点。我遇到过在进度条没走完时点了下树节点,工具直接卡死,强杀进程后原包被占用锁住,得重启资源管理器才能删。
打不开的常见原因就三类:文件被客户端或其他编辑器占用;包的版本比工具新,节点类型表里没有对应项;文件本身已经是半截损坏状态(就是上一节说的磁盘满或强杀进程留下的)。第三类没法救,只能从.orig备份恢复。
3.3 Process All 到 Save to 的完整操作序列
按这个顺序走,中间不要跳步:
- 打开 deMSwz,载入
mob.wz,等到reading WZ消失且无报错。 - 把界面上所有默认勾选项全部取消。这一步最容易被跳过——默认勾选代表“处理全部属性”,你只想改一个键,它会把整棵树上能处理的属性都翻一遍,慢,而且会顺带重写一批你没打算动的节点。
- 在自定义值的前框输入键名,例如
attackAfter,严格保持大小写;后框输入数值2147483647。 - 勾上自定义值旁边的勾选框,让工具只处理这一条。
- 点
Process All,等进度条走完。进度条时长与包体积成正比,中途不要点取消——取消后已经写入的部分不会回滚。 - 点
Save to..,在弹出的保存对话框里选择覆盖原mob.wz。
第 6 步的保存对话框里有一个容易看错的勾选项,作用是“保存前备份原文件”之类。我一开始勾了它,结果目录里多出一份旧包,后续用 HR 读包时选错文件,读了半天发现改动没生效。这个勾按需处理,如果你已经在外面做了.orig备份,这里就别再生成一份,避免文件名混淆。
保存完成后的第一件事不是开客户端验证,而是重新用编辑器打开刚保存的包,随便找几个目标节点,看键值是不是真的变成了你写的数。批量写入零命中的情况,界面上完全看不出来,只有回读才能确认。这一步用脚本更快:
# 把 deMSwz 导出的属性文本清单做一次命中校验 # 清单格式假设为每行 "路径|属性名|值",导出时按这个格式整理 import sys, collections path, key, expect = sys.argv[1], sys.argv[2], sys.argv[3] hits = collections.Counter() with open(path, encoding="utf-8", errors="ignore") as f: for line in f: parts = line.rstrip("\n").split("|") if len(parts) != 3: continue node, name, value = parts if name == key: # 大小写严格相等,不做 lower() hits[value] += 1 total = sum(hits.values()) print(f"命中 {total} 处,取值分布:{dict(hits)}") if hits.get(expect, 0) != total: # 有任何一个没写成目标值就判失败 print("存在未写入目标值的节点,检查类型是否被截断") sys.exit(1)这段脚本只做一件事:从导出的清单里按属性名精确匹配,统计取值的分布。sys.exit(1)那行是给自己留的门禁——只要有节点的值不等于目标值,说明该节点的属性类型不是 int(比如 short 被截断),需要单独处理。用Counter而不是简单计数,是因为取值分布能直接告诉你“是全部没改成功”还是“改成功了但有几个被截断”,这两种情况的排查方向完全不同。
3.4 2147483647 这个数为什么不能随便加
2147483647 是 32 位有符号整型的上限,也就是 2³¹−1。客户端读这类属性时按有符号 int 解析,超过这个数会绕回负数。写 2147483648 的结果是客户端读到一个很大的负数,行为可能反向——本来是“无延迟”,变成“卡到超时”。
也有些字段客户端不直接当数值用,而是做掩码或除法。这类字段写满值会触发完全不同的分支,表现是游戏内表现异常但程序不崩。遇到这种情况,把值退到百万级(比如 1000000)再试,通常就够了。判定方法很简单:同一个键,分别写 2147483647 和 1000000,看客户端表现是否一致。一致说明字段是纯数值比较,不一致说明有位运算参与。
注意:不要图省事把值写成 -1。-1 在二进制上是全 1,很多语言里确实代表“最大值”,但客户端如果对这个字段做了
> 0的判断,-1 会被直接跳过,等于没改。
4. 节点级删改:闹钟缩头、起身动作与物品图标的三类删法
批量写入解决“值”的问题,删节点解决“行为”的问题。删对了是流程简化,删错了是动画直接崩或者物品无法操作。这一章的四类操作,用的都是 HR,前提是先把读取模式选对。
4.1 读取模式选错,后面全是无用功
HR 读包前要先选模式,模式决定了节点命名规则和部分属性类型的解析方式。不同来源的客户端包,节点命名和结构不完全一样,选错模式的表现是:包能打开,节点树也能展开,但导出的 PNG 花屏、部分属性显示成乱码、按名字搜索搜不到。
判定方法不复杂:打开包后,先抽查一个你确认存在的节点,比如某个 BOSS 的 img 是否在预期位置,再随便展开一个 canvas 属性点一下预览。预览正常出图,说明模式对了。出图是全黑或者花屏,别急着怀疑包坏了,先换模式重开。
打开路径是File → Load,选中目标包。加载完成后不要立刻改,先在树上找到你要动的节点,把完整路径记下来(比如mob.wz → 8500001.img → attack1),改的时候按路径走,不按界面上的显示顺序走。
4.2 删动作节点:缩头动作和起身动作
以某个 BOSS 的缩头行为为例,路径是mob.wz → 8500001.img → attack1。展开attack1后能看到一串子节点,这些子节点对应技能释放过程中的各个动作帧序列。要走“不缩头”的效果,思路是把缩头开始之后的那些动作节点整体删掉,让动作序列在缩头前就结束。
关键点在这里:子节点在界面上的排列顺序,和节点名字里的数字大小不是一回事。你看到列表里第 21 项,它的实际名字可能是24,第 22 项可能是21。如果按“从第 21 行开始往下全选删除”操作,删掉的是错位的节点,结果是动作序列中间断了一截,客户端表现是怪物卡在某个动作上不动,或者直接不刷新。
正确做法是逐个点开子节点,看右侧属性面板里显示的真实节点名,确认名字落在目标区间之后再删。删除操作是选中节点后Property → remove Property,快捷键Ctrl+R。删完先File → Save另存成一个新文件名,不要直接覆盖,回游戏里验证过再决定要不要替换。
起身动作的改法思路相同但更干脆:找到对应怪物 img 下的die1节点,整项删掉。die1对应的是死亡后逐帧站起的那段动画,删掉后死亡状态直接跳到下一阶段。这里没有区间问题,只有一个节点,风险低得多,适合拿来练手熟悉 HR 的删节点手感。
4.3 物品图标过滤:删 iconRaw,不删整项
物品相关的包是Item.wz。过滤某件物品的思路是让它不出现在正常的展示流程里,做法是删掉它的图标资源节点。
路径是Item.wz → 0400.img → 04000000 → iconRaw。0400.img是分卷,04000000是具体物品的 id 节点,iconRaw是这个物品的原始图标(canvas 类型)。删掉iconRaw,客户端拿不到图标数据,渲染为空。
这里有个边界必须说清楚:删iconRaw影响的是展示,不是数据。物品本身的属性节点(info、spec之类)还在,所以背包里原有的该物品仍然可以出售、可以存仓,但所有依赖图标渲染的交互会失效——拖拽、丢弃、交换这类操作会失败,因为客户端在拖拽时要画一个跟随鼠标的图标。想恢复就把iconRaw加回去,别去动info。
提示:删之前把
iconRaw用 HR 的导出功能存一份 PNG 到本地。后面想还原的时候,新建一个同名 canvas 节点把这个 PNG 回填进去就行,比从备份包里翻快得多。
4.4 用 String.wz 反查代码
前面所有操作都要先知道“代码”——怪物 id、物品 id、技能 id。这些 id 不是猜出来的,String.wz就是那张对照表,它按版本同步记录了 id 与名称的映射。
做法是用 deMSwz 打开String.wz,在树里按名称找目标,双击对应项,右侧会显示 id,右键全选复制到记事本存档。我习惯按类别整理成几个文本文件,比如怪物 id 一张表、物品 id 一张表,后面改包时直接查表,不用每次重开 String.wz——这个包也不小,反复开关很浪费时间。
| 包文件 | 主要用途 | 常改的节点 |
|---|---|---|
| mob.wz | 怪物数据与动作序列 | attack1 下的动作子节点、die1 |
| Item.wz | 物品定义与图标 | iconRaw |
| skill.wz | 技能等级数据 | 各等级节点的数值与范围 |
| String.wz | id 与名称对照 | 只读,用于反查 |
| Ui.wz | 界面图片资源 | canvas 节点下的 PNG |
4.5 技能节点:职业码加序号,还要看符号位
技能相关的包是skill.wz,节点 id 由两部分拼成:职业代码 + 4 位技能序号。冒险家系职业代码是 3 位,扩展职业是 4 位。战士线 100、法师线 200、弓手线 300、飞侠线 400、海盗线 500,转职后在百位后追加序号,比如 110 是战士一转分支、120 是另一分支、130 是第三分支,再往后 111、121、131 是三转,112、122、132 是四转。
改范围类效果(比如让某个技能的作用范围覆盖屏幕)时,要动的是技能等级节点下的范围属性,通常是左上和右下两个坐标对。这两个值的符号是相反的:一个改成正的大数,另一个要改成负的大数,才能把范围从一个点向两侧撑开。只改一边的结果是范围偏向一侧,看起来像“半屏”。
还有一个必须接受的现实:技能是按 id 独立存储的,改了 1121000 只影响那一个技能,不会“全职业生效”。同一个效果在不同职业线上是不同 id,得一个一个来。这也是为什么这类补丁很少有人做全量——工作量按职业数乘技能数增长。
4.6 Ui.wz:PNG 导出、修改、回填
界面类资源全在Ui.wz,本质上就是一堆 canvas 节点套着 PNG。改法三步:选中 canvas 节点,点导出按钮存成 PNG;用图像工具改(改颜色、调透明度都行,不建议动整体尺寸);保存时必须存成 PNG 格式,文件名保持原样,然后回填覆盖原节点,最后保存包。
透明度这类效果往往不止一个文件。比如底部状态栏的透明化,除了主背景图,还要处理状态栏背景那一张(路径大致在StatusBar/base/backgrnd.png这一层),只改一张会出现“背景透了但边条还是实心”的割裂感。改之前先把相关路径下的 canvas 全部导出一遍,看看哪些是同一块区域的组成部分,一次改完再回填。
5. 五条踩坑记录:从目录被删光到属性大小写翻车
这些坑有一个共同点:操作时界面都显示“成功”,问题要到验证阶段才暴露。按现象、原因、解决三步记下来,比记操作步骤有用。
5.1 补丁装完,客户目录里其他文件全没了
现象:生成的补丁在自己机器上测试正常,发出去之后对方反馈“装完补丁整个客户端只剩补丁里那几个文件”。
原因:差分工具的逻辑是比对两个目录,把“只在旧目录存在”的文件判定为需要删除。制作补丁时如果新旧目录里混进了无关文件,或者排除规则没做全,这些“只在一边存在”的文件就会被写进删除列表。更隐蔽的情况是新旧目录的层级不一致,工具按相对路径比对,路径对不上就当成“旧文件已不存在”。
解决:制作补丁前,把要发布的文件单独复制到两个干净目录里,一个旧版一个新版,目录层级严格一致,除此之外一个文件都不放。然后在工具里对每个文件路径逐个确认排除状态,构造之前再核对一遍文件清单数量。这条我现在的习惯是:补丁目录里的文件数必须等于改动文件数,多一个都不构造。
5.2 Process All 跑完了,进游戏一点变化没有
现象:批量写入进度条正常走完,保存也成功,回读发现值没变,或者只有一部分节点变了。
原因:两种情况。一是键名大小写或拼写不匹配,工具按名精确匹配,attackAfter写成AttackAfter就是零命中,但界面不会提示。二是该节点的属性类型不是 int,写进去的值被截断或拒绝。
解决:先用 3.2 节的脚本做一次命中统计,看取值分布。全部未命中就是键名问题,回树上抄一遍原始拼写;部分未命中就是类型问题,抽那几个节点单独看类型,改用 HR 手工改。
5.3 保存时报错,或者保存出来的包打不开
现象:点保存后工具报写入失败,或者保存成功但下次打开提示解析错误。
原因:客户端进程还在跑,文件被占用;或者保存过程中磁盘空间不足,写了一半;或者上一次异常退出留下了文件锁。
解决:改包全程关闭客户端。保存前看一眼目标分区剩余空间,WZ 包保存时需要临时空间,剩余空间小于包体积两倍就容易出问题。已经写坏的包不要试图修复,直接从.orig恢复,这也是为什么备份要用cp -n只写一次。
5.4 改完数值,游戏里的表现和预期完全相反
现象:想做出“无间隔”的效果,结果变成了超长等待或者直接卡住。
原因:写入的值超过了字段类型的上限,被截断成负数;或者该字段在客户端里参与位运算,满值触发了另一个分支。
解决:先确认字段类型是 int 还是 short,short 上限只有 32767。确认是 int 之后,把值退到百万级再测一次,两次表现一致说明是纯数值比较,不一致就换成保守值。
5.5 按界面顺序删节点,动画直接崩
现象:删完动作节点,怪物在某个动作上定住不动,或者整个技能放不出来。
原因:子节点在界面上的排列顺序不等于节点名的数字顺序。按行号区间删,删掉的是错位的节点,动作序列中间被挖空。
解决:删之前逐个点开子节点,按属性面板显示的真实节点名确认范围,删完先用另存的文件在离线环境验证一遍动作是否完整,再覆盖正式包。这条没有捷径,只能慢。
6. 差分补丁制作与验证:让改动只落在一个文件上
回到 Winpatch。它不理解 WZ 语义,只做字节级目录比对,所以能不能做出干净的补丁,全看你喂给它的目录有多干净。流程是:开专家模式,分别指定旧文件目录和新文件目录,然后逐个文件确认排除状态——要发布的保留,不发布的全部排除。每个文件路径下面都有排除按钮,点一下加入排除列表,旧目录和新目录两边都要做,不能只做一边。补丁说明文本可以自己写,构造前顺手填上改了什么,方便对方核对。点构造补丁,等生成完成。
体积和正确性要一起看。一个只改了 mob.wz 里几十个字节的补丁,体积应该接近压缩后该文件的增量,如果生成出来比整个包还大,说明差分粒度没对上,多半是新旧目录里混进了未排除的大文件。反过来,补丁只有几百字节也要警惕,可能改动根本没被包含进去。
验证我固定走三步。先比哈希:把打完补丁的目录和手工改好的新目录各算一次,逐个文件比对。
# 对比两个目录下同名文件的哈希,只输出不一致的项 # -r 递归,-q 只输出差异,缺文件也会列出 diff -rq old_dir new_dir # 对关键文件单独做哈希比对,确认补丁落点正确 sha256sum new_dir/mob.wz patched_dir/mob.wzdiff -rq会把“只在一边存在”和“内容不同”都列出来,前者往往就是 5.1 节那个坑的源头,看到就说明目录没清干净。sha256sum那行用来确认补丁打完后的文件与手工改好的文件完全一致,哈希对不上但功能正常的情况也存在(比如补丁工具做了对齐填充),这时候以功能验证为准。
第二步在离线环境实际加载一次,重点看改动点附近的行为,别只看你改的那一处——删节点类操作容易连带影响相邻动作。第三步留退路:补丁包里附一份反向补丁或者原始文件备份,让对方能在装错时退回去。
从那以后我每次构造补丁都强制走一遍“目录清点—哈希比对—离线加载”,三道全过才发出去,宁可多花十分钟,也不愿意再收到一句“装完就没了”。希望帮到你。
本文还有配套的精品资源,点击获取