# PVF 解析套件开发文档 > 覆盖 PVF 解析库(pvfine)、物品名称表导出工具(pvfexport)、以及与 GM 工具的离线名称显示集成。 > > 最后更新:2026-11-03 --- ## 一、背景 DNF 的 `Script.pvf` 是专有的二进制容器(加密 + 压缩),物品名称(`.equ`/`.stk` 的 `[name]` 段)只存在于 PVF 内。为了让 GM 工具**离线(服务端关闭)也能显示物品中文名**,我们引入了一个 PVF 解析方案。 --- ## 二、pvfine 项目(`pvf/`) `pvf/` 是开源项目 **pvfine**(GPL-3.0)的核心源码,一个 Go 编写的 PVF 编辑器/解析套件,支持 86/90/100/110/115/TW 全版本 `Script.pvf`。 ### 结构 ``` pvf/ ├── internal/pvf/ 核心解析包(Open / Find / Text / ScriptMetadata / ParseListText) ├── internal/ani/ buildmode/ rendering/ ... pvf 依赖的其他内部包 ├── internal/mcpserver/ npk/ mod/ script/ ... 编辑器相关 ├── config/ go:embed 的索引数据(paths.json 等,编译必需) ├── cmd/ │ ├── annotation-editor/ 注解编辑器 │ └── pvfexport/ 物品名称表导出工具(本仓库新增) ├── services/ frontend/ 编辑器后端/前端 ├── main.go 桌面 GUI 入口(Wails) └── go.mod module pvfine, go 1.26.0 ``` ### 核心 API(`internal/pvf`) ```go import "pvfine/internal/pvf" archive, err := pvf.Open("Script.pvf") // 打开归档(自动识别版本) defer archive.Release() idx, found := archive.Find("stackable/stackable.lst") // 找文件索引 text, err := archive.Text(idx) // 读脚本文本 pairs, err := pvf.ParseListText(text) // 解析 .lst -> [{ID, Path}] meta, err := archive.ScriptMetadata(idx) // 读物品元数据(Name/Rarity) // meta.Name 即物品名称 ``` > 注意:`internal/pvf` 依赖 `internal/ani`、`internal/buildmode`、`internal/rendering`、 > `internal/annotations`、`internal/ruleconfig`、`config` 等包,**不能单独抽出**, > 需要以整个 `pvfine` 模块作为依赖(或直接在其中添加 cmd 子命令)。 --- ## 三、pvfexport 工具(`pvf/cmd/pvfexport/`) 从 `Script.pvf` 提取 `item_id → 名称` 全量映射,导出 JSON。 ### 编译 ```bash cd pvf go build -o pvfexport.exe ./cmd/pvfexport/ ``` ### 用法 ```bash pvfexport -pvf Script.pvf -out items.json # 完整 JSON(含 path/rarity/kind) pvfexport -pvf Script.pvf -out dnf_items.json -compact # 紧凑 {item_id: name}(推荐) ``` - `-compact`:只输出 `{item_id: name}` 映射(仅保留有名称的项),体积约 5.7MB(14.6 万条)。 - 完整模式:输出 `{pvf_path, total, items:[{id,name,rarity,path,kind}]}`,约 35MB。 ### 实现逻辑 1. `pvf.Open` 打开归档。 2. 读 `stackable/stackable.lst`(堆叠道具)和 `equipment/equipment.lst`(装备),用 `ParseListText` 解析出 `item_id → 路径`。 3. 对每个 item,用 `resolveItemFile` 找文件(路径候选策略复刻服务端 `readDungeonDropText`): - 直接用 lst 里的路径 - 尝试 `root/` 前缀(`stackable/` / `equipment/`) - 尝试 `(r)` 修订文件名(90 版本特有) 4. `ScriptMetadata` 读 `[name]` 段得到名称。 --- ## 四、与 GM 工具的集成(离线名称显示) ### 名称表存放 名称表 `dnf_items.json` 与 `pvfexport.exe` 放在服务端根目录下的 **`GM配置/`** 目录: ``` 90server/ └── GM配置/ ├── pvfexport.exe └── dnf_items.json ``` ### 自动重导出 GM 工具(launcher)启动时,`db.py` 的 `_load_item_names()` 会: 1. 定位 `GM配置/dnf_items.json` 和 `Client/Script.pvf`。 2. 对比两者修改时间: - 名称表**已是最新** → 秒加载(约 0.09 秒)。 - **PVF 被更新**(或名称表缺失)→ 自动调 `pvfexport.exe -compact` 重导出(约 5 秒),再加载。 3. 加 `_EXPORT_LOCK` 锁防并发重复触发。 > 因此:**PVF 新增物品后,只需替换 `Client/Script.pvf`,GM 工具下次启动自动重导名称表, > 无需重新打包 GM 工具。** ### 注入方式 `server.py` 启动时调用 `db.set_gm_config(GM_CONFIG_DIR, PVF_PATH)` 注入目录与 PVF 路径; `db.py` 的 `item_name(item_id)` 据此惰性加载名称表。 --- ## 五、许可证与合规 - `pvf/` 遵循 **GPL-3.0**(见 `pvf/LICENSE`)。 - 若将 pvfine 与闭源的 server/launcher 合并发布,需注意 **GPL 传染性**: - 单独分发 pvfexport(独立 Go 进程)可隔离传染; - 若将 `internal/pvf` 直接 link 进闭源服务端,会触发 GPL 义务。 - 建议:保持 pvfine 作为**独立子模块/子仓库**,通过 CLI 工具(pvfexport)与主项目解耦。