Files
90CNServer/pvf/docs/MOD.md
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

79 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mod 格式扩展
## 分层
- `internal/mod` 定义中立包模型、格式注册表和独立的 `Reader` / `Writer` 接口,不依赖 Wails、归档服务或版本库。
- `Package` 保存名称和操作集合。`replace` 携带明确编码的文件内容,`merge-list` / `merge-strings` 携带键值条目,`delete` 表达整文件删除。
- `services.ExportService` 将选中文件、相对 HEAD 的工作区改动或提交前后状态适配为包模型;负责依赖补齐、预检、修订校验和目录发布。
- 前端四类导出入口只指定来源,不包含格式专属的目录或编码规则。
## 添加格式
向注册表注册稳定 ID、显示名称、支持的操作以及读取器或写入器。读取和写入能力根据实际接口计算,不使用空实现冒充支持。
写入器先通过 `Validate` 检查包是否可表达,`Files` 描述实际输出路径及对应的包条目,再通过 `Write` 写入隔离的临时目录。`Files` 中没有 `EntryPath` 的文件为必需的生成元数据,不可移除。写入器不负责目录选择、覆盖确认或修改归档。
导入通过 `Reader.Detect(fs.FS)` 识别格式,`Reader.Read(fs.FS)` 返回同一 `Package`,再由服务层执行导入预览和归档暂存事务。外部文件路径不能直接成为写入路径;导入器需要校验包内路径和操作,并复用现有暂存、索引重建及版本变更记录流程。当前已注册 110USextend 和 115CN-mod,现有“导入”弹窗中的“Mod”选项提供入口。实现 `ArchiveWriter` 的格式直接发布单个 ZIP,其余格式保留目录发布。
## 110USextend
导出结果是父目录下的 `<mod名称>-<版本号>/`,包含 `pack.json` 和实际用到的 `pvf/`、`merge/`。版本号可填写,默认或留空为 `1.0`,不强制 semver;最多 128 字节,不允许控制字符和首尾空格。优先级 `100`、启用,`types` 记录整文件替换项的 PVF 类型。文本使用 UTF-8,无 BOM。
普通脚本和文本整份放入 `pvf/<归档路径>`;`.lst`、`.str` 使用 `merge/<归档路径>`。版本导出只包含新增、修改的表条目(新增表以空表为基线);选中文件、文件集或未启用版本控制的内存改动没有准确基线,跳过显式选择的整表,不全量导出。列表按编号匹配,文字表按第一个 `>` 分割并保留空值及文字中的空格;忽略有缩进的 `//` 注释和空行。
该格式不支持删除,预检会提示跳过的文件或表条目删除。只有删除或没有可应用条目的包不能导出。无法解析的文字表行跳过;无法解析或读取的依赖表、无法准确提取改动的合并表及未能补齐的依赖也跳过,记录文件路径、行号或原因到 `[pvfine:export]` 日志,并在确认弹窗汇总警告。非法目标路径、不能表达的输出内容仍阻止导出,不以改名绕过安全校验。
依赖补齐默认关闭。开启后,从同一来源状态补齐脚本引用的文字表条目、表号映射及配置列表中匹配的登记项;显式内容优先,重复项去重。历史版本使用该提交快照,不读取当前工作区内容。不补齐任意脚本、sprite、sounds、npk 资源。
直接导出保留既有行为:版本新增或修改输出后状态,删除输出前状态。导出不会应用编辑器草稿、创建版本或保存 PVF。
未启用版本控制的“当前内存改动”从归档 overlay 枚举所有现存修改和新增文件;mod 模式跳过无版本基线的 lst/str,直接导出仍保留整文件;已删除文件不在该范围中。
所有确认弹窗列出完整输出树,勾选文件可控制最终输出范围。排除作用于已冻结的预览,不重新计算依赖,也不会自动补回被排除的依赖文件。已有预览刷新或切换模式时保留相同输出路径的排除,新弹窗重置;选空时不可导出。mod 元数据根据最终保留条目重新生成。
未选择合并来源时,只改名称或版本号会复用同一有效内容快照,不重新解码文件、计算差异或扫描依赖。快照过期时间不延长,归档、工作区或版本会话变化后不可复用。日志分别记录锁等待、来源读取、差异提取、依赖补齐和总耗时;普通脚本不读取不必要的修改前内容,无文字表引用时不读取表号映射或文字表。
## 导出时合并已有 MOD
导出为 mod 时,可在依赖开关后选择一个相同格式的已有 MOD。110USextend 选择目录;115CN-mod 可选择目录、ZIP 或 `.dnfmod`。普通文件同路径冲突时使用本次内容;同类型的 lst/str 合并操作按键合并,同键使用本次值,保留已有 MOD 的其余文件及键。选择已有 MOD 后,自动读取包内名称及版本号;纯数字分段版本递增最后一段(如 `1.0 → 1.1`、`1.2.3 → 1.2.4`),保留末段前导零,其他版本保留原值并提示手动修改。名称和版本号仍可编辑,ID 使用本次输入。输出目录名为 `<mod名称>-<版本号>/`,ZIP 为 `<mod名称>-<版本号>.zip`,不覆盖已有 MOD。版本号留空时包名采用格式默认版本;名称与版本号组合必须是合法文件名。
已有内容经过读取、合并和格式校验后进入同一预览树,可以取消勾选。每次准备预览都会重新读取合并来源;执行时使用冻结内容,不再读取源 MOD。切换格式或重新打开弹窗会清除来源;取消文件选择保留原来源。115CN 的全部组件合并为基础内容;互斥组件或组件目标冲突会阻止合并。
当前仅合并 PVF 内容,不复制 NPK/BK2 等客户端资源、包级依赖/冲突和其他包元数据。
## Mod 导入
工具栏“导入”中选择“Mod”和导入格式,再选择单个 mod 目录。格式选项由 `ModImportService.Formats()` 从注册表加载,只列出拥有 `Reader` 的格式;服务按显式传入的格式 ID 调用对应的 `Detect` / `Read`,目录与所选格式不匹配时拒绝预览,不回退到其他读取器。后续格式注册读取器后即可出现在导入选项中。110USextend 目录需包含 `pack.json`。当前支持读取本工具导出的 110USextend 包;`pvf/` 中每个替换文件必须在 `pack.json.types` 中声明类型(1 为脚本、3 为文本),声明的文件必须存在。仅处理 `pvf/` 和 `merge/`,不导入 sprite、sounds、npk 等游戏资源,不支持删除操作。
预览显示所有文件的新增/更新状态、整文件替换或表合并方式;展开表文件显示每个传入键的新增/覆盖状态、新值和原值。整文件替换使用包中声明的类型;`.lst` 按编号合并,`.str` 按第一个 `>` 拆分后按文字键合并,同键覆盖并保留无关条目。文字表保留空值、值中的空格和 `>`,以及当前表中未改动的行和注释。归档中不存在的合并表会新建;已有合并表类型必须匹配。
读取器拒绝非法路径、大小写路径冲突、文件/目录冲突、符号链接、非普通文件、敏感配置路径、无效编码、重复传入合并键及无法解析的合并行。包内文本必须是无 BOM 的 UTF-8;合并行忽略空行和有缩进的 `//` 注释。现有目标表无法准确解析时阻止导入,避免丢失条目。
`ModImportService` 在隔离归档上预检并冻结合并后的文件内容,确认时不重新读取目录。预览有效期为 10 分钟;归档、工作区修订或版本会话发生变化后必须重新预览。取消或重新选择会释放旧预览。确认导入复用文件导入暂存事务、索引更新及版本记录,预检或暂存失败不安装部分文件。启用版本控制时记录一组“导入 Mod”变更,可撤销;不会自动提交版本或保存 PVF。编辑器草稿仍由既有归档变更刷新流程处理。
## 115CN-mod
格式 ID 为 `115CN-mod`。配置使用 UTF-8 `mod.json`,要求 `schema: 1`、`client: "115CN"`、`kind: "resource"`。读取目录、`.zip`、`.dnfmod`,配置可在根目录或唯一的一层包目录内;也兼容旧 ZIP 的 GB18030 中文包目录。RAR、7z、JAR 不接受。不解压到游戏目录,不执行包内程序或脚本。
PVF 操作通过 `component` 关联组件,`source` 相对包根目录,`path` 为小写 PVF 路径:
- `add`:仅新增,目标存在时整批预检失败。
- `replace`:整份替换,目标不存在时新增。
- `merge`:仅支持 `.lst` 按编号合并、`.str` 按文字键合并。同键覆盖,其他条目保留。
- `type`:示例包使用的可选字段,1 为脚本、3 为文本。省略时 `.str` / `.nut` 推断为 3,其他为 1;合并表必须匹配对应类型。
- `patch`:提供的文档片段和示例包未包含标签补丁字段协议,当前明确拒绝,不猜测、忽略或降级成整文件替换。需补充登录器 `ModExamples/README.md` 中的完整协议后接入。
导入预览按 `defaultSelected` 初始化组件,勾选后从冻结的包内容重新预检。`requires` 必须同时选中,`exclusiveGroup` 同组最多选一项;未知组件、循环依赖、所选组件的重复目标及文件/目录冲突拒绝。全不选时禁止执行。切换不延长原预览的 10 分钟期限,源 ZIP 变化不影响已冻结的内容,归档修订变化后必须重新预览。现有 Unicode 文字表的尾部 NUL 终止符原样保留,其他无法解析的行仍阻止合并。
`files` 中的客户端资源仅校验声明的组件、路径、size 与 sha256 字段格式,并在预览警告中报告;不读取或校验资源正文摘要、不写入 PVF、不安装或备份 NPK/BK2。包级 `requires / conflicts` 提示用户在登录器核对,不模拟 MOD 管理器的安装、排序、关闭或恢复状态。资源安装、已有视频恢复和依赖管理仍由登录器负责。
导出复用现有来源、精确表差异和依赖补齐,生成 `<名称>.zip`,根目录为 `mod.json` 和 `pvf/`,单个默认组件 `base`。新增独立 MOD ID 输入框:初次生成唯一 ID,发布同一 MOD 的更新需填回原 ID;ID 只能为 1–64 个小写字母、数字、`-`、`_`。名称/版本/ID 更新复用有效内容快照。筛选文件后同步重建清单,不携带被排除的载荷。ZIP 写完后原子发布,拒绝覆盖已有同名包。MCP `export_prepare` 选择此格式时同样必须提供 `modId`。
ZIP 拒绝路径穿越、大小写路径冲突、符号链接、特殊文件和敏感配置路径;声明文件读取也检查父目录链接。限制 ZIP 最多 20,000 项、单项 64 MiB、总展开大小 512 MiB,清单 4 MiB。目录包同样限制 PVF 单文件和累计读取大小。不会读取未登记的 PVF 载荷。
## 验证
运行 `go test ./...`、前端 `npm test` 和 `npm run build`。真实归档的新增导出验证需要设置 `PVF_TESTFILE`;示例包和游戏目录不作为可写测试目标。
115CN 示例读取回归:设置 `PVF_MOD_TESTFILE` 为 ZIP 路径,运行 `go test ./internal/mod -run '^TestCN115RealMOD$' -v`。真实归档预检另外设置 `PVF_MOD_ARCHIVE` 为测试 PVF,运行 `go test ./services -run '^TestCN115RealMODPreflight$' -v -timeout 15m`。两者均只读源文件,不自动执行或保存 PVF。