影之刃零 受伤减伤工具
给单机离线游戏《影之刃零》(Phantom Blade Zero / PBZ.exe) 做一个 Windows 小工具:
默认把玩家受到的伤害减半(50% 减伤),倍数可改为任意 1/N,并附带完整的逆向过程,方便在别的电脑上复现或改造。
> 只用于单机离线。全部逻辑只读写本机 PBZ.exe 进程,不联网、不改游戏文件、不改存档。
一、快速复现(3 步)
前提:Windows x64,游戏已启动并进入可操作角色的画面。
# 1. 编译(用 .NET Framework 自带的 csc,无需装 SDK)
& "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe" `
/nologo /codepage:65001 /optimize+ /platform:x64 `
/out:PBZX5.exe scripts\PBZX5.cs
# 2. 只读检查(确认能定位到你的角色)
.\PBZX5.exe --dry
# 3. 正式运行(默认 dr 模式:受伤减半,即 50% 减伤)
.\PBZX5.exe
二、使用说明(日常操作)
1. 启动
- 先启动游戏,进到能操作角色的画面(不要停在标题界面)
- 双击
PBZX5.exe - 程序扫描 10~60 秒,打印候选属性集,自动选中
BP_Renewal_Hun_C(主角「魂」) - 看到
>>> target 0x... (BP_Renewal_Hun_C)后,按回车开始生效 - 回去玩;想停止时按 F8
启动第一行会显示当前配置,用来确认读到了什么:
=== PBZX5 mode=dr HP x2 ===
x2 就是当前倍数(受伤 ÷2 = 减半)。
2. 改减伤比例(1/2、1/3、1/4、1/5…)
编辑同目录下的 PBZX5.ini:
mode=dr
multiplier=2 ← 改这个数字
| 写法 | 效果 |
|---|---|
multiplier=2 |
受伤变 1/2(50% 减伤,默认) |
multiplier=3 |
受伤变 1/3 |
multiplier=4 |
受伤变 1/4 |
multiplier=5 |
受伤变 1/5 |
multiplier=10 |
受伤变 1/10 |
取值范围 1.01 ~ 50,填错会自动回落到 2(减半)。
⚠️ 改完必须重启工具——配置只在启动时读一次。步骤:按 F8 停止 → 保存 ini → 重新双击 exe → 看首行的 HP xN 确认。
3. 怎么确认真的生效了
挨打时控制台会打印(最多 1.5 秒一行):
hit 100 -> net 50 (HP 782)
hit 100= 游戏原本扣你 100 血net 50= 实际只掉了 50(= 100 ÷ 2)- 想把
net调得更小,就把multiplier调大
如果一条都不出现,说明程序停在 Press ENTER to apply,回去按一下回车。
4. 命令行参数
PBZX5.exe --dry 只读检查,不改任何值
PBZX5.exe --mode=dr --mult=4 临时指定(注意:ini 里的值会覆盖命令行)
三、三种模式
| 模式 | 行为 | 风险 |
|---|---|---|
dr(默认) |
掉血后立刻补回 (N-1)/N,净伤害 = 实际伤害 ÷ N | 低。致命一击可能来不及补 |
max |
生命上限 ×N | 中。会让上限与当前值不一致,实测导致过闪退 |
heal |
当前生命始终回满 | 低,但等于无敌,不是减伤 |
dr 是唯一推荐的方案:它就是“受伤变成 1/N”,只是用补偿实现的。
四、逆向过程与结论(核心,可复用)
1. 游戏基础
D:\SteamLibrary\steamapps\common\PBZ\platform\steam\PBZ.exe,64 位,基址0x140000000,模块约 492MB- 引擎 UE5.7.4 + Denuvo;存档
.sav头是PBZS、日志头是PBZLOGE1,都是自定义加密,不要试图改文件 - 内容在 IoStore 容器(
global.utoc/.ucas),ProjectPBZ-Windows.pak尾部01 E1 12 6F 5A即bEncryptedIndex=1,索引加密 - 有两个同名进程 PBZ.exe,选内存占用最大的那个(另一个 ~35MB 是启动器)
2. UE5.7 的 FNamePool(关键差异)
名字表条目头两字节不是标准的 Len<<1,而是:
长度 = 头两字节 >> 6
验证:None→0x011E>>6=4、ByteProperty→0x0310>>6=12、IntProperty→0x02C0>>6=11,全部吻合。
- 条目 2 字节对齐,奇数长度补 1 字节 0
- FName 索引 =
(块号 << 16) | (块内字节偏移 >> 1) - Blocks 指针数组在模块数据区,本版本实测偏移
0xDD11AD0(相对模块基址);找不到时会退回全盘扫指针
3. 角色属性集(GAS)布局
每个属性占 16 字节:
[0] 固定哨兵 0x4B6592B0
[1] 0
[2] Base(基础值,float)
[3] Current(当前值,float)
连续 10 个属性、步长 16,从 +0x48 开始:
| 属性 | 偏移 |
|---|---|
| Level | 0x48 |
| Health | 0x58 |
| Health_Max | 0x68 |
| Health_Max_Lv1 | 0x78 |
| Shield | 0x88 |
| Shield_Max | 0x98 |
| Infliction_Max | 0xA8 |
| Infliction_Max_Lv1 | 0xB8 |
| Attack | 0xC8 |
| Attack_Lv1 | 0xD8 |
「10 个连续哨兵 + 步长 16」就是定位指纹,不依赖固定地址。
4. 如何确认是玩家
属性集是 UObject,+0x20 是 Outer(所属角色),Outer 的 +0x10 是类,类的 +0x18 是 FName。
主角的 Outer 类名是 BP_Renewal_Hun_C(其他是 BP_Monster_*、BP_CampFire_C、BP_Firewood_* 等)。
UObject 布局(本版本实测):
+0x00 vtable(模块内)
+0x08 flags(4) + InternalIndex(4)
+0x10 ClassPrivate
+0x18 NamePrivate(FName 索引,低 4 字节;高 4 字节是编号)
+0x20 OuterPrivate
5. FProperty / FField 链(用来读属性表)
+0x00 FFieldClass*
+0x10 Owner(UClass | 1,低位置 1)
+0x18 NamePrivate
+0x2C ElementSize
+0x3C Offset_Internal ← 字段偏移
+0x60 RepNotifyFunc
节点间距 0x80
6. 找不到的东西(避免后人踩坑)
PBZCharacterAttributeData(含Resistance_DefenceShachi等抗性)只作为 DataTable 模板存在,运行时没有被实例化到内存里
用玩家真实的MaxHP=850 / HP=332.5 / ATK=100做精确锚点全内存扫,命中 0 条- 所以“直接改防御/抗性字段实现真减伤”这条路在这版游戏里行不通,只能做补偿
PBZAttributeSetHandler只有 CDO,无实例
五、源码说明(scripts/PBZX5.cs)
单文件 C#,.NET Framework 4.x 可编译,无第三方依赖。主要函数:
| 函数 | 作用 |
|---|---|
FindBlock0 |
扫内存找 ByteProperty 条目,定位名字表块 0 |
FindBlocks |
先试静态偏移,失败则扫模块数据找 Blocks 指针数组 |
Resolve |
FName 索引 → 字符串(用 >>6 长度规则) |
FindAttributeSets |
扫哨兵指纹,收集候选属性集 |
LooksLikeSet |
校验 10 个哨兵 + 生命值合理性 + 主人是 *_C 蓝图类 |
OuterClassName |
取属性集所属角色的类名 |
dr 循环 |
每 8ms 读一次当前生命,掉血就补回 (N-1)/N |
FindResistanceStruct |
只读的失败尝试,保留作研究用途 |
六、跨机器适配
- 不依赖固定绝对地址:属性集靠指纹扫描,角色靠类名识别
- 唯一硬编码的是名字表 Blocks 的静态偏移
0xDD11AD0(仅作快路径),对不上会自动全盘扫 - 游戏大版本更新后:如果
--dry找不到角色,用--mode=findres或重新按第三节确认布局 - 换电脑、换存档、换角色装备都不影响(扫描是运行时的)
七、已知限制
- 致命一击救不回来:游戏先扣满血再判定死亡,补偿发生在扣血之后
max模式改上限,实测触发过闪退,不要用- 需要游戏进程在运行;工具关掉即失效
- 仅限单机离线,不要用于任何联网/竞技场景
八、推荐工作流
启动游戏 → 进入可操作画面
↓
PBZX5.exe --dry (只读确认能找到 BP_Renewal_Hun_C)
↓
PBZX5.exe (回车生效,F8 停止)
↓
正常游玩,控制台会打印 hit 100 -> net 20