# AGENTS.md > **给接续开发的 AI 的入口文件。** 先读完这一页再看代码。 > 这里只放三样东西:**哪些规则不能破**、**命令怎么跑**、**改完怎么验证**。 > 背景与完整说明看 [README.md](README.md),历史踩过的坑看 [docs/踩坑记录.md](docs/踩坑记录.md)。 --- ## 0. 三十秒认识这个仓库 《地下城与勇士》90 级版本的**本地单机模拟器**,非私服、不联网、不开服。monorepo,四个进程协作: | 组件 | 目录 | 技术栈 | 端口 / 产物 | |------|------|--------|------------| | 整合启动器 | `launcher/` | Python 3.15 **32 位** + pywebview + PyInstaller | `127.0.0.1:19527`,产物 `launcher/dist/DNF90启动器.exe` | | 服务端 | `server/` | Go 1.26.5(模块 `longheng.io/server`) | `127.0.0.1:18111`(Admin),产物 `DNF90Server.exe` 等三个 | | 游戏内 GM 控制台 | `gmweb/` | 纯 HTML/CSS/JS + Python 标准库 `serve.py` | `127.0.0.1:18112`,反代 18111 并注入 token | | 客户端补丁 | `client-patch/` | C++ / MSVC vcxproj | 产物 `90CN.dll`,接管游戏面板弹 WebView2 窗口 | 其余:`pvf/`(PVF 解析,Go,GPL-3.0)、`loadout-tool/`(换装工具,C#)、`gmtool/`(旧版 GM 工具,仅参考)。 **仓库只存源码**:exe / dll / 美术 / PVF 资源都不在库里,成品走网盘。 当前版本 **v2026.11.05**,远程 `origin` = `https://git.hanfuit.com/Lingbao/90CNServer.git`,分支 `main`。 --- ## 1. 开工前必读(按顺序,别跳) 1. 本文件(硬规则 + 验证方法) 2. [docs/踩坑记录.md](docs/踩坑记录.md) —— **至少读 P-001 / P-002 / P-011**,这三条是「不知道就会重新踩一遍」的 3. 然后按要改的地方选读: | 要改什么 | 读哪份 | |---------|--------| | 启动器 / GM 工具 / 打包 | [docs/开发文档.md](docs/开发文档.md) | | 服务端 GM 接口、拍卖行、背包 | [docs/服务端开发文档.md](docs/服务端开发文档.md) | | 客户端补丁 90CN.dll、CRC 自校验 | [docs/客户端补丁与CRC校验.md](docs/客户端补丁与CRC校验.md) | | PVF 解析、离线物品名 | [docs/PVF解析开发文档.md](docs/PVF解析开发文档.md) | | 发版 | [docs/release/README.md](docs/release/README.md) + [发布说明模板](docs/release/发布说明模板.md) | --- ## 2. 硬规则(不是代码风格,违反了就出故障) **R1|`90CN.dll` 与 `DNF90Server.exe` 是一对,必须同批构建、同批发布。** 服务端编译期把 DLL 的 SHA-256 写进常量 `boundDllSHA256`,DLL 一换指纹就不配套, 客户端能启动但**登录阶段被踢**,服务端日志没有明显报错。只改其中一半是最常见的翻车方式。 **R2|改完 DLL 必须先补 CRC32,再重编服务端。** `client-patch/tools/build_90cn.ps1` 第三步会自动补;带 `-SkipCrc` 出来的 DLL 不要发布。 自校验失败的表现是客户端弹框退出(详见 P-003 / P-004:CRC 种子别手填、槽位只在 `.data`/`.rdata` 扫)。 **R3|仓库是唯一真源,`Z:\90server` 只是部署副本。** 调试可以直接在部署目录改,但**改完必须回灌仓库**,并用 `diff -rq` 核对(P-011 就是这么漏过 `90CN_web.ini` 和整个 `gmweb/` 的)。提交前 `git status` 确认没有该进的 ini / 静态资源留在外面。 **R4|`gmweb/` 只能写 IE11 兼容代码。** 游戏内嵌的是老 WebView 控件:不用 flex、不用 CSS 变量、不用 `transform`。布局统一 `float` + `box-sizing:border-box`;垂直居中用 `position:absolute; top:50%` + `-ms-transform:translateY(-50%)`。 改完 CSS/JS **必须把 `?v=YYYYMMDDx` 加一版**,否则 IE11 缓存不刷新。 **R5|静态响应统一 `Cache-Control: no-store`。** 启动器侧已在 `server.py` 的 `_send` 里加,新增路由时别绕过它;否则 WebView2 会缓存旧页面, 现象是「改了没生效」,会浪费大量时间。 **R6|单文件打包会改变 `sys.executable` 的含义。** 源码运行时它是 `python.exe`,`--onefile` 打包后**是启动器自己**。任何 `Popen([sys.executable, ...])` 的地方打包前都要重新推一遍;拉起子进程后要**校验副作用**(端口真在监听)而不是校验返回值。 这就是 P-001 的整条成因链,别再犯。 **R7|不提交编译产物。** `*.exe`、`*.dll`、`launcher/dist/`、`launcher/build/`、`server/build_verify/`、`__pycache__/`、 `launcher/dnf_items.json`、`*.pvf`、`*.log` 已在 `.gitignore`。不要为了「方便别人」把它们塞进库。 **R8|版本号三处一致**:`launcher/server.py` 的 `CURRENT_VERSION`、发布包根目录 `更新日志.txt`、 客户端的版本显示。改一漏二会出现「首页还是旧版本号」。 **R9|品牌与声明不能动。** 「本模拟器资源禁止倒卖,请到官方微信公众号或社群下载」「禁止用于开服」这类声明必须保留; `pvf/` 源自 pvfine,**GPL-3.0 有传染性**,改它之前先确认许可证影响。 --- ## 3. 环境与构建 作者机器上的路径(其他环境自行替换,逻辑不依赖盘符): ``` Python 3.15 32 位 C:\Python315\python.exe # 32 位是硬要求,客户端/工具链都是 32 位 Go ≥ 1.26.5 MSVC Visual Studio(client-patch 用 vcxproj) 部署目录 Z:\90server # 只是副本 桌面发布包 90CN灵宝小竹整合20261105更新补丁\ ``` ```bash # 服务端(三个 exe) cd server go env -w GOPROXY=https://goproxy.cn,direct go env -w GOSUMDB=sum.golang.google.cn go build -buildvcs=false -trimpath -o DNF90Server.exe ./cmd/server/dnf90 go build -buildvcs=false -trimpath -o DNF90Control.exe ./cmd/server/control go build -buildvcs=false -trimpath -o DNF90Doctor.exe ./cmd/server/doctor # 启动器(单文件) cd launcher C:/Python315/python.exe -m pip install pywebview pythonnet clr-loader pyinstaller C:/Python315/python.exe -m PyInstaller --clean --noconfirm "DNF90启动器.spec" # → launcher/dist/DNF90启动器.exe # 客户端补丁(Release|Win32,自动补 CRC) client-patch\tools\build_90cn.bat # PVF 物品名导出 cd pvf && go build ./cmd/pvfexport/ && pvfexport -pvf Script.pvf -out dnf_items.json -compact ``` --- ## 4. 代码地图 ``` launcher/ ├── server.py 2809 行,本地 HTTP 服务 + 全部 GM 逻辑 + 窗口启动(核心,占 80% 工作量) │ ├── 202-360 日志(_logf) / 单实例互斥 / 多调用模式 run_script_instead() │ ├── 369-430 start_gmweb():拉起 18112 + 就绪校验 + 进程内兜底 ← 高危区,见 P-001 │ ├── 551-686 更新日志:仓库 Release + 本地 txt 合并去重 │ └── 2481-2800 main() / webview2_installed() / show_window() / 窗口清理 ├── index.html 234 行,六页骨架(开始/GM工具/一键换装/游戏设置/游戏更新/关于我们) ├── shell.js 804 行,页面路由、更新日志渲染、二维码灯箱 ├── shell.css 447 行,页面样式(WebView2 环境,可用现代 CSS) └── qr/ 四张二维码:alipay.png / wechat.png / gzh.jpg / qq.png gmweb/ 游戏内控制台,共约 1100 行 ├── serve.py 187 行,静态托管 + /api/* 反代 18111 + 注入 admin token + /qr/ 白名单 ├── index.html / style.css / app.js IE11 约束,别用现代语法 └── qr/ 同上四张 client-patch/ ├── 90CN.cpp 客户端补丁主体(极大,改前先搜函数名定位) ├── 90CN_web.ini width=800 height=720 close_action=1 ← 内嵌 GM 页尺寸,改这里不是改代码 └── tools/ build_90cn.ps1(构建+补CRC)/ patch_dll_crc.py(info|verify|patch) server/cmd/server/ dnf90(主体)/ control(控制台)/ doctor(自检)/ launcher / logtool / 等 ``` --- ## 5. 改完怎么验证(**别只看代码,必须跑**) ```bash # 进程与端口(改启动器后必查:正常只有 19527 + 18112 两个监听) tasklist | findstr DNF90 netstat -ano | findstr "127.0.0.1:19527 127.0.0.1:18112 127.0.0.1:18111" # 启动器现场日志(出问题先看这个,比猜快得多) type 启动器日志.txt # exe 同目录;应有 pid / argv / 端口 / 多调用模式 # DLL 自校验状态 C:/Python315/python.exe client-patch/tools/patch_dll_crc.py verify Z:/90server/Client/90CN.dll C:/Python315/python.exe client-patch/tools/patch_dll_crc.py info Z:/90server/Client/90CN.dll # 仓库 ↔ 部署目录一致性 diff -rq gmweb Z:/90server/gmweb diff -q client-patch/90CN_web.ini Z:/90server/Client/90CN_web.ini ``` **看界面的推荐做法**(比肉眼读像素可靠,本项目一直这么验): ```bash # 1) 起静态服务;2) 注入脚本切到目标页;3) 无头浏览器截图/量尺寸 cd gmweb && C:/Python315/python.exe -m http.server 18198 # 页面里注入:document.getElementById('pane-about').className='pane on'; # (启动器则是给对应 .shell-page 加 active) msedge --headless=new --disable-gpu --hide-scrollbars --window-size=800,720 \ --screenshot=shot.png --virtual-time-budget=3000 http://127.0.0.1:18198/_t.html # 量尺寸:把 getBoundingClientRect 结果写进 document.title,再用 --dump-dom | grep title ``` 尺寸口径:GM 页内嵌窗口 **800×720**;启动器窗口 **1204×717**。改完两边都看一眼有没有滚动条。 **分场景清单** | 改了什么 | 必须确认 | |---------|---------| | 启动器页面/样式 | 打包 → 杀旧进程 → 启动 → 六页都能切、无滚动条、`启动器日志.txt` 无异常 | | 启动器 Python 逻辑 | 先跑源码再打包:`cd launcher && DNF90_SERVER_ROOT=Z:/90server DNF90_NO_ELEVATE=1 python server.py`(`SERVER_ROOT` 靠 EXE 同级有 `Server/` 识别,脱离部署目录时用环境变量指定);确认没有新增 `sys.executable` 陷阱 | | gmweb 页面 | `?v=` 加一版;800×720 截图三列/四页无溢出;`/api/*` 仍能通 | | 服务端接口 | `go build` 通过;`DNF90Doctor.exe` 自检;启动器「GM工具」页能读到数据 | | 客户端补丁 | `build_90cn.bat` 全流程(含补 CRC)→ **重编服务端** → 两边一起替换 → 登录验证 | | 发版 | 按 [docs/release/README.md](docs/release/README.md) 七步走,见第 6 节清单 | --- ## 6. 发版检查清单(缺一项就会有人装不上) - [ ] `Client\90CN.dll` 与 `Server\runtime\bin\DNF90Server.exe` **成对且指纹一致** - [ ] `gmweb\` 整个目录(含 `qr\` 四张图) - [ ] `Client\90CN_web.ini`(缺它回退 1040 宽、`close_action=0`) - [ ] 启动器 exe + `更新日志.txt` - [ ] 版本号三处一致(`CURRENT_VERSION` / 客户端显示 / 更新日志) - [ ] **干净目录**(非中文、无空格)实跑一遍再对外发 - [ ] Release 说明用模板;附件传 `docs/release/网盘下载.url`(别直传十几 G 的包) --- ## 7. 当前状态(2026-11-05) - 版本 `v2026.11.05`;`main` 与 `origin/main` 同步 - 启动器:六页(开始/GM工具/一键换装/游戏设置/游戏更新[当前|历史]/关于我们),关于页三列二维码 - gmweb:四页,关于页三列(赞助我们 353 | 官方QQ群 178 | 团队公众号 179,面板宽 712) - 已修复:启动器打包后自我套娃开窗(P-001),带单实例互斥 + 运行日志 + WebView2 固定数据目录 - 已知待办:release 附件 `.url` 里的网盘链接换代时,要同步改 `发布说明模板.md` 表格;`gmtool/` 为旧版遗留,可择机归档 --- ## 8. 提交约定 1. 提交信息格式:`范围:做了什么`,例如 `启动器:游戏更新拆分为当前/历史双子页,新增关于我们页` 2. 一次提交一件事;改了接口就同步改对应文档(服务端接口 → `docs/服务端开发文档.md`) 3. 只提交源码与文档;产物走 `.gitignore` 4. 新踩的坑,追加到 `docs/踩坑记录.md`(编号延续 P-0xx,格式照抄现有条目) 5. 推送前 `git status -sb` 确认工作区干净、与 `origin/main` 无分叉 --- ## 9. 不要做的事 - ❌ 不要「为了省事」只替换 DLL 或只替换服务端 exe —— 见 R1 - ❌ 不要用 `!important` 硬压 gmweb 的样式问题 —— 先看是哪个选择器赢了(P-005) - ❌ 不要用现代 CSS/JS 写 `gmweb/` —— 见 R4 - ❌ 不要把 `Z:\90server` 当工作区改完就完事 —— 见 R3 - ❌ 不要在启动器里加「打包后会指向自己」的子进程调用而不做校验 —— 见 R6 - ❌ 不要提交 exe/dll/`dnf_items.json`/PVF 资源 - ❌ 不要把本机测试用的掉落改动带进对外发布的服务端 - ❌ 不要公开分发或倒卖本仓库代码与资源(`pvf/` 除外,但受 GPL-3.0 约束)