DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,188 @@
|
||||
# PVF 变体架构
|
||||
|
||||
## 分层
|
||||
|
||||
- `internal/pvf/detect.go`:已知标准格式、替代密钥格式、Paged110 解封依次探测,最后尝试未知格式的头部恢复。结果包含逻辑字节、头部、格式规则以及写回所需状态。
|
||||
- `internal/pvf/archive.go`:公共逻辑容器解析,不负责配套文件发现或分页解封。字符串池和 GRPI 无法解析时返回 `ErrInvalidSection`;HASH 无法恢复时保留读取能力并限制结构修改。
|
||||
- `internal/pvf/format.go`:格式标识、容器类型、内容规则和写入能力。格式标识用于诊断,功能分支依据内容规则。
|
||||
- `internal/pvf/save.go`:统一校验、字符串池修复、重建和状态接纳流程;`writeContainer` 仅负责普通写出或分页包装。
|
||||
- `services/list_registration.go`:依据 `RequiresIndexHash` 决定伴随索引维护及 uint32 ID 约束。界面依据内容能力控制注册和字符串引用入口。
|
||||
|
||||
目前标准与替代密钥格式共享内容规则;110US / 115US 使用分页容器,并启用 UTF-16 字符串策略、indexhash 注册和字符串引用入口。100CN 同样使用分页容器与 indexhash、字符串引用,但保留 ASCII / UTF-16 双池。容器与这些内容规则可以独立组合。
|
||||
|
||||
`IsPaged110` 和信息快照中的 `paged110` 保留兼容用途。新业务功能不应依据格式名称判断是否可用。
|
||||
|
||||
## 写入约束
|
||||
|
||||
`WriteCapabilities` 描述归档的基础写入能力。未知 HASH 时,已有内容替换仍可保存,但新增、删除、字符串池扩展或重映射会在保存前拒绝;`CanSave` 不意味着任意待保存修改都被支持。缺少分页写回状态时不可保存。
|
||||
|
||||
HASH 是否已知通过独立状态记录,数值为零的有效 seed 不再代表未知。格式规则及分页状态必须随批处理、脚本工作区暂存克隆一起保留。
|
||||
|
||||
未修改归档保持原有字节往返约定;启用 UTF-16-only 规则的历史异常归档仍会执行已有字符串池修复,这类修复可能改变输出。保存的结构限制也适用于该修复。
|
||||
|
||||
解析错误明确拒绝不可解码的必需段。当前只在打开时验证首个正文分块,其余分块仍按需解压;成功打开不等于完成整个归档的完整性扫描。
|
||||
|
||||
## 新变体接入
|
||||
|
||||
1. 确定差异属于段参数、物理容器还是内容规则。
|
||||
2. 只有段参数变化时添加已知探测器及参数,复用公共解析、编辑和重建。
|
||||
3. 容器变化时扩展解封和 `writeContainer`,保持公共解析接收逻辑字节。
|
||||
4. 内容行为变化时在 `ContentRules` 中明确表达,并集中配置;不要在服务、编辑器、字符串池中散布格式名称判断。
|
||||
5. 克隆必须复制新增的可变写回状态;不可把配套文件依赖带入普通内容编辑。
|
||||
6. 更新生成的前端绑定必须使用 Wails 生成命令。
|
||||
|
||||
未知密钥恢复只支持当前共享布局,不能自动推断未知容器结构或客户端内容规则。新变体仍需要样本确认,不能因头部恢复成功就声明完整支持。没有配套文件时,被封装的未知头部目前仍可能返回 `ErrBadSignature`,尚不能可靠区分所有缺失依赖与不支持格式。
|
||||
|
||||
## 验证
|
||||
|
||||
`format_test.go` 使用合成数据覆盖:
|
||||
|
||||
- 标准与替代格式的有/无 guard 探测、未修改字节一致性、增删后的保存重开。
|
||||
- Paged110 分页包装、解封后的公共解析、暂存编辑、新增及再次保存;使用合成分页状态,不覆盖真实配套文件解封。
|
||||
- 未知 HASH 的内容保存与结构修改拒绝、已知零值 seed。
|
||||
- 克隆格式状态、字符串池策略与容器的独立组合。
|
||||
- 字符串池长度错误的显式拒绝。
|
||||
|
||||
常规验证:`go test ./...`、`cd frontend && npm run build`。
|
||||
|
||||
真实客户端兼容性仍需使用对应变体样本运行 `PVF_TESTFILE=/path/to/Script.pvf go test ./...`,未匹配内置参数的分页版本还需对应配套文件。测试日志和示例不得包含配套文件内容或密钥;授权内置的客户端解封参数仅存于专用资源。
|
||||
|
||||
## 100CN 样本调查
|
||||
|
||||
100CN 样本的页首保护边界同样是每 10 MiB 页的前 10,240 字节。配套国服
|
||||
`DNF.exe` 的 PVF 加载流程明确读取同目录的 `sk.dat`,先解封页表,再解密各页
|
||||
保护区,最后按宽字符公式处理 `iNfO` 头部。程序内的 `sk.dat` 文件名经过
|
||||
字符串混淆,不能以普通字符串搜索未找到它作为不需要配套文件的依据。
|
||||
|
||||
分页解封现在依次尝试 `DFO.exe` 和 `DNF.exe`;前一个程序不存在或不能解封时
|
||||
仍会继续尝试后一个。只在配套文件和归档头部完整校验通过后接受结果,不凭
|
||||
可执行文件名认定客户端版本。100CN 按成功解封候选的公钥身份登记为
|
||||
`paged100cn`,`ClientVersion()` 返回 `100CN`;未登记身份继续使用通用 `paged`。
|
||||
|
||||
合成测试覆盖两种程序名、两种 PEM 编码、错误 `DFO.exe` 与有效 `DNF.exe`
|
||||
同时存在、无修改字节一致,以及内容编辑和新增文件后的保存重开。
|
||||
|
||||
100CN 的 ASCII 池约 145 MB,非 ASCII 文本使用 UTF-16 池。不能对它执行
|
||||
110US 的 ASCII 池迁移,否则未修改保存也会重建整个归档。装备 indexhash
|
||||
的 227,634 条原始记录均使用 ASCII 池数字引用,数值符合现有哈希算法;
|
||||
更新和新增记录沿用双池策略,现有 ASCII 引用不再被误判为需要修复。
|
||||
|
||||
真实样本验证通过:2,522,676 个文件、30,402 个分块全部解压和数据范围检查,
|
||||
未修改输出 SHA-256 与源文件一致,编辑、新增、删除后保存重开,重建 HASH
|
||||
覆盖当前文件表,未编辑载荷抽样保持一致,并验证新增装备的 LST/indexhash
|
||||
注册。验证范围为离线读写,未进行游戏客户端实机加载。
|
||||
|
||||
真实测试可通过 `PVF_TEST_SIDECAR_DIR` 指定配套目录,避免移动源文件或将
|
||||
配套文件放进仓库。`PVF_TEST_FULL_SCAN=1` 启用全分块及文件数据范围检查:
|
||||
|
||||
```sh
|
||||
PVF_TESTFILE=/path/to/100CN.pvf \
|
||||
PVF_TEST_SIDECAR_DIR=/path/to/client \
|
||||
PVF_TEST_CLIENT_VERSION=100CN PVF_TEST_FULL_SCAN=1 \
|
||||
go test ./internal/pvf -run '^TestRealPagedClient(Compatibility|EditRoundTrip)$' -count=1
|
||||
```
|
||||
|
||||
110US、100CN 和 115US 的完整解封数据统一放在 `internal/pvf/assets/clients/`,
|
||||
各用一个 `go:embed` 二进制资源,包含 PKCS#8 客户端参数、metadata 和加密
|
||||
`sk.dat`。格式为 `PVFC` 签名、uint32 版本号(当前为 1)、uint32 DER 长度、
|
||||
uint32 密封表长度、DER、32 字节 metadata、密封表;整数均为小端。
|
||||
110US 原先的源码参数常量、独立 `sk.dat` 和专用加载函数已移除。
|
||||
|
||||
`paged_embed.go` 使用共用解码器校验版本、精确长度、公钥身份与密封表,
|
||||
统一注册三种客户端,并以 `sync.Once` 缓存校验后的客户端和 RSA 解封结果。
|
||||
身份识别只比较公钥指纹,不再依赖私钥加载。参数不会出现在诊断错误或测试
|
||||
输出中;每次打开仍需校验归档签名与完整布局,不能仅凭资源身份采用内容规则。
|
||||
|
||||
打开时先依次尝试完整的内置 110US、100CN、115US 参数,无需读取同目录文件;
|
||||
内置参数不匹配才查找归档目录中的 `sk.dat`,并尝试 `DFO.exe` / `DNF.exe`
|
||||
内的候选。外部 `sk.dat` 仅作为兜底表,不会阻止已匹配的内置客户端打开。
|
||||
后端也可调用 `OpenWithSidecars` 指定客户端目录。所有候选失败后,错误提示
|
||||
用户将该 PVF 对应版本的 `sk.dat` 和客户端程序与归档放在同一目录后重试。
|
||||
|
||||
真实测试额外覆盖完全没有外部配套文件的打开、从内存 `Parse` 打开、末尾
|
||||
分块读取,以及错误外部文件不覆盖有效内置参数。合成测试覆盖内置参数不
|
||||
匹配时的外部客户端兜底和缺少配套文件的中文提示。
|
||||
|
||||
115US 样本的内置数据包含 81 页的解封参数;已验证独立打开、抽样读取、
|
||||
未修改保存的字节一致性,以及编辑、新增、删除和 LST/indexhash 注册后重开。
|
||||
默认压缩重建字符串池可能让归档超过页表容量,保存时会在超限情况下尝试
|
||||
更高压缩率的字符串池,并保持字符串偏移与未编辑分块不变。仍然超限时,
|
||||
返回 `ErrPagedCapacity` 和所需/可用页数,在写出前拒绝保存;`SaveAs` 保留
|
||||
原目标文件及待保存修改,不扩展客户端配套页表。
|
||||
|
||||
## TW 独立目录格式
|
||||
|
||||
TW 的格式标识为 `tw`,客户端代号为 `TW`。它使用长度前缀 UUID、版本号、
|
||||
目录长度/校验值/文件数,以及变长目录记录;目录和每个文件独立进行
|
||||
CRC 派生的 XOR 与 32 位旋转加解密,没有 S4A21 的 HASH、字符串双池和
|
||||
压缩正文分组。`tw.go` 将它适配为公共 Archive、字符串池和脚本表示,
|
||||
服务层可继续使用现有目录树、搜索、编辑、批处理和字符串引用接口。
|
||||
|
||||
目录路径沿用参考读取器的 CP949 编码;游戏字符串表、`.str` 文本和 `.nut`
|
||||
源码按 TW
|
||||
配置使用 Big5。脚本带 `B0 D0` 前缀,原生 token 编号在读写边界转换;
|
||||
`9 + 10` 字符串引用对适配为公共 `<index::key>` 引用。打开时校验目录和
|
||||
非空文件 CRC、字符串索引及脚本尾部对齐,并建立不可变引用映射。
|
||||
|
||||
未修改保存逐字节保留原包;修改保存复制未编辑文件的密文及已有路径字节,
|
||||
保留 UUID、版本和原有文件编号,向 `stringtable.bin` 追加新字符串,已有
|
||||
字符串编号不变。新增、删除和 `.str` / `.nut` 编辑均写回 TW 格式。字符串表由编辑器
|
||||
维护,不支持直接替换;Big5 无法表示的新字符会在写出前报错。
|
||||
|
||||
`tw_test.go` 覆盖合成归档的繁体文本、引用、二进制文件、增删、克隆、保存
|
||||
重开和损坏拒绝。真实样本及服务层验证使用独立变量,避免其他格式专用测试
|
||||
对 TW 产生不适用的假设:
|
||||
|
||||
```sh
|
||||
PVF_TW_TESTFILE="$PWD/testdata/TW.pvf" \
|
||||
go test ./internal/pvf ./services -run '^TestTW' -count=1
|
||||
```
|
||||
|
||||
真实样本含 951,559 个文件、1,137,629 个字符串;验证范围为离线读写,
|
||||
未进行游戏客户端实机加载。
|
||||
|
||||
### 基础掉率的版本兼容
|
||||
|
||||
“基础掉率”工具支持 90US、90CN、TW、110US 和 100CN。五种版本使用
|
||||
相同的四个掉率文件及 `[basis of rarity dicision]`;TW 每组 6 个值,
|
||||
90 系列常见每组 7 个值,100CN 每组 8 个值,110US 每组 9 个值。
|
||||
读写均按各文件的 section 总长度验证分组宽度,只接受完整的 6/7/8/9 值分组。
|
||||
调整概率只更新前四个累计阈值,保留地狱文件前置值、终止阈值和附加字段。
|
||||
|
||||
真实 TW 的四类掉率读取、调整、TW 格式保存重开和附加字段保留已验证。
|
||||
可使用 `PVF_TW_TESTFILE` 运行 `TestDropRateServiceTWRealRoundTrip`。
|
||||
|
||||
110US 样本的翻牌、小怪、精英段长度分别为 9、36、36,地狱段为
|
||||
1 个前置值加 2 组 9 个值,共 19 个值。100CN 对应长度为 8、32、32、17。
|
||||
本次仍编辑五档基础概率,后续新增阈值不重新解释;前五档终止阈值不在
|
||||
既有兼容范围内时继续拒绝,避免把其他档位的份额归入现有五档。
|
||||
`item drop rarity control`、其他 section 和其他组的内容均原样保留。
|
||||
|
||||
真实高版本验证包括四个文件的读取、第一组概率调整、逐 token 确认仅四个
|
||||
累计阈值变化,以及分页格式保存重开后的完整载荷一致性。仍保留 90US、
|
||||
90CN 和 TW 的原有支持,未验证的 115US 和通用分页格式暂不开放。
|
||||
|
||||
```sh
|
||||
PVF_TESTFILE="$PWD/testdata/110US.pvf" \
|
||||
go test ./services -run '^TestDropRateServicePagedRealRoundTrip$' -count=1
|
||||
PVF_TESTFILE="$PWD/testdata/100CN.pvf" \
|
||||
go test ./services -run '^TestDropRateServicePagedRealRoundTrip$' -count=1
|
||||
```
|
||||
|
||||
### 掉率字段按版本换行
|
||||
|
||||
`config/rendering.json` 中现有的 `section.basis of rarity dicision` 和
|
||||
`section.basis of rarity dicision.hell` 增加 `format.tokensPerLineByVersion`:
|
||||
90US/90CN 为 7、TW 为 6、100CN 为 8、110US 为 9。深渊规则继续使用
|
||||
`offset: 1`,首个数量值独占一行。未配置或未知版本保留 `tokensPerLine: 7`
|
||||
的回退行为,不推测尚未验证的 115US 分组宽度。
|
||||
|
||||
`tokensPerLineByVersion` 是可选的版本→正整数行宽映射;文件级和 section
|
||||
级规则均可使用。解析时先选择现有优先级规则,再按 `ClientVersion()` 取得
|
||||
行宽;如另有 `tokensPerLineIndex`,token 中的动态宽度仍优先。旧规则无需
|
||||
迁移,规则管理界面可通过 JSON 字段调整此映射。
|
||||
|
||||
五个版本的真实样本已检查四类掉率的行数和每行宽度;合成 token 测试覆盖
|
||||
版本映射、未知版本回退、深渊首值单行、普通段分组及原始 token 字节往返。
|
||||
仓库规则修改后,运行中的应用仍需重新加载渲染规则;生产应用须包含更新后
|
||||
的内置配置,不会自动读取当前仓库的 JSON。
|
||||
Reference in new issue
Block a user