面向接手本项目的人(或未来的自己)。README 讲"怎么用",这里讲"为什么这么做、改的时候会踩到什么"。
- 当前版本:v1.11.0(上游 0.4.0 适配、自动接管与绿色版发布)
- 后续仅发布 Windows x64 自包含单文件绿色版,统一使用
Portable发布配置;产物为publish/DLSSGManager.exe。 - v1.11.0 已适配上游 0.4.0:完整包下载、目录化运行负载、单代理升级、新配置与恢复规则见 0.4.0 适配记录。下面描述 0.3.x 待机共存与 310.1 下载的历史说明不适用于 0.4.0。
- 改进范围:见 UI 落地清单。
- 仓库:
github.com/BUNNY-19C/DLSSG-30s-manager,打 tag 即发版
给 RTX 30 系(SM86)装 DLSS 帧生成的管理器。上游 sdli1995/dlssg_for_sm86 提供代理 DLL(内嵌 DLSSG 运行时 + SM86 后端),本项目负责:
- 找游戏(扫描 Steam 库 / 任意目录)
- 按游戏配置(Enabled / Optimized 档位 / Preset / 倍率 / 日志级别 → 写进
dlssg_sm86.ini) - 部署与恢复(代理 DLL + INI 放入渲染目录;恢复时只删能证明是本项目的文件)
- 下载上游负载(多源、证书 pin、进度条、暂停/取消)
不做的事:不修改上游 DLL、不随管理器分发上游二进制、不引入第三方(如 pandaligx/RTX-FG-Manager)的文件。绿色版不含 mod,首次启动自动获取。
# 构建(需要 .NET 8 SDK)
dotnet build src/DLSSGManager/DLSSGManager.csproj -c Debug
# 下载 Mod 文件,使用程序自身的下载入口
dotnet run --project src/DLSSGManager -c Debug -- --fetch --silent
# 构建免安装绿色版(自带 .NET)
dotnet publish src/DLSSGManager/DLSSGManager.csproj -c Release -p:PublishProfile=Portable发版流程:git tag v1.9.4 && git push origin main --tags → GitHub Actions(.github/workflows/release.yml)自动构建绿色版与 SHA256SUMS 并挂到 Release。工作流会把 tag 号通过 -p:Version 注入程序集,所以界面里的版本徽章自动正确。
本地开发要点:DLSSGMANAGER_HOME 环境变量可把数据目录重定向到指定路径,与用户默认库(%APPDATA%\DLSSGManager)分开保存。
src/DLSSGManager/
├─ ModFetcher.cs 970 下载:多源链、DNS/证书校验、进度、暂停门、解压、版本探测
├─ DeploymentService.cs 939 部署/恢复/接管/状态判定——唯一写游戏目录的地方
├─ MainWindow.xaml 827 界面布局(顶栏/信息条/侧边栏/卡片详情/进度条/日志条)
├─ MainWindow.xaml.cs 854 窗口装配、语言与主题切换、下载面板、GPU 行
├─ MainWindow.Actions.cs 561 部署/恢复/接管/批量/扫描/路径选择处理器
├─ Gpu.cs 548 显卡探测、架构判定、注册表显示名读写、INF 间接字符串解析
├─ Strings.zh/en.cs 424 两张字符串表(必须逐键对齐)
├─ Models.cs 306 GameEntry / GameProfile / DeploymentInfo / AppData
├─ Detection.cs 283 游戏发现:双标记扫描、渲染目录解析、主程序挑选
├─ ModSource.cs 282 本地负载:入口名、版本读取、导入自定义代理
├─ AntiCheat.cs 282 内核级反作弊识别、隔离副本查找
├─ Store.cs 249 library.json 读写与归一化、INI 模板渲染
├─ Shell.cs 231 打开目录/文件、启动游戏、提权重启
├─ ModSourceLocator.cs 191 负载目录解析(仓库检出 / 便携 / 用户目录)
├─ Localization.cs 115 Loc.T、语言归一化
├─ Detection/Gpu 之外的工具:Native、OutputLog、Converters、Theme、ThemeKeys、Palette、Version
└─ Themes/Dark.xaml + Light.xaml 两套主题字典(键必须完全一致)
mod/ 下载得到的上游负载,不入库
分层原则:模型与领域层(Models / Gpu / Detection / DeploymentService / ModFetcher / AntiCheat / Store)不得引用 WPF 类型,界面依赖集中在窗口与展示层。
改代码前先读这几条,都是踩过坑才写下来的:
- 颜色一律
DynamicResource。StaticResource在元素创建时解析一次,切换主题后界面停在旧配色。 - 两套主题字典键必须一致,切换主题时才能正确解析全部资源。
- 列表状态色需要显式重发:
ThemeBrushConverter只在源属性通知时重算,切主题要调GameEntry.RaiseThemeColors()。 - 对话框有自己的资源作用域,主窗口的隐式样式传不进去,按钮/输入/滚动条样式要在每个 Window 里重声明。
- 文字从代码设置的(下拉项、GPU 建议行)不跟随语言绑定,必须走
BuildLocalizedCombos/RefreshCodeText。
早期版本用 Task.Run(...).ContinueWith(t => ... t.Result ...),两个真实缺陷:faulted 时异常被静默吞掉,且 _busy 标志卡死导致所有按钮失效直到重启。
现在的形态:所有耗时操作都是 async void 处理器 + try/catch/finally,_busy 在 finally 释放;重活 await Task.Run(...),结果回 UI 线程应用。新增操作请照 MainWindow.Actions.cs 的形状写。全局兜底:TaskScheduler.UnobservedTaskException 落日志。
DeploymentService.Evaluate(后台)与 Apply(UI)是分开的——Evaluate 会做哈希与 WinVerifyTrust(每个候选入口一次 ~30MB DLL),绝不能在 UI 线程调用。
状态检查在没有部署记录时自动识别手工安装,并通过 GameCheck.Adoption 返回待接管记录;Apply 在 UI 线程登记,调用方随后保存库。单个检查、启动/刷新全部、扫描和添加目录共用此流程。接管不改写游戏文件、不生成原始备份,也不覆盖已有部署记录和备份;界面不再提供手动接管按钮。
恢复时"删除"必须能证明文件是本项目的,判据三层:
- 记录里的 SHA-256(
DeploymentInfo.Files) - 已知社区构建哈希(
ModFetcher.KnownCommunityBuildHashes) - 项目 Authenticode 签名(
IsProjectSigned,走 WinVerifyTrust + 证书主题匹配)
备份必须结转:重新部署会重建 DeploymentInfo,若不把 prev.Backups 带过来,第一次部署置换的外部文件(别人的 INI)就永远回不来了。
代理入口集合:OwnedEntryNames(prev) = 已知入口名 ∪ 记录 Files(排除 INI)——自定义命名的代理只活在记录里,漏了它恢复就删不干净。
- 0.2.x 负载:同目录两个代理 = 两条推理管线 = 崩,所以
Deploy仍会删掉多余代理(判据是source.IsLegacySchema)。 - 0.3.3+ 负载:上游改成待机机制——先加载的干活,其余只转发导出,不装钩子。因此部署保留多余代理、日志逐个注明「保留待机」,并写入
DeploymentInfo.Files(不记录就清不掉)。
Evaluate 的状态判定不分 schema:只要发现多于一个自家代理,就在详情里附一句「另有 N 个待机转发」并保持 Deployed——0.3.3 之前那是故障,现在是正常态。要区分的话得看 IsLegacySchema,但当前没有这个需求。
异环(Neverness to Everness)就是要三个文件一起放(d3d12.dll + dinput8.dll + dlssg_sm86.ini),这是"不能收敛代理"的直接原因。
双标记扫描:nvngx_dlssg.dll(游戏自带 DLSS-G 负载)或 dlssg_sm86.ini(本项目独有文件名,识别手装副本)。只看前者会漏掉不自带 DLSS-G 的游戏——异环就是这样,它的目录还是深层 UE 结构 Client\WindowsNoEditor\HT\Binaries\Win64。
GenericFolderNames 用于跳过无意义的目录名(Win64/Binaries/Client/…)取游戏名;上游标记命中后向上找 3 层找主程序。
威胁模型是"镜像不可信":
- 仅 HTTPS + 主机白名单,且解析结果必须全为公网地址
- 代理感知:检测到系统代理时跳过本机 DNS 校验——否则 DNS 污染/hosts 加速的用户会被全部源拒绝(issue #1 的真实原因)
- 重定向逐跳复检、响应体积上限、归档条目不得逃逸目标目录
- 载荷校验:所有 DLL 必须带项目签名;镜像源还必须匹配证书 pin(
85BA6676…;GitHub 官方端点不匹配只记警告,容忍上游换证书) - 校验通过前不写 mod 目录——所以取消/失败都不会破坏已有负载
IniTemplate.Render 只覆写模板里已存在的键。上游换 schema 时(0.2.x 的 Router/KernelImage → 0.3.x 的 Enabled/Optimized/Preset),旧键自然不写、新键自然生效,不需要分支。IsLegacySchema(检测 Router=)决定界面显示哪组控件。
配置面板只显示当前 schema 的键——显示另一组是死控件。
0.3.0 起上游 INI 不再有 ; Native x.y.z. 横幅,所以版本号靠两级:INI 横幅 → README 标题 → 下载时探测到的版本写进 .manager-version 标记文件(--fetch 路径也必须探测,否则徽章停在旧版本——修过一次)。
上游更新时按这个清单逐项核对,不要凭版本号猜测:
# 1. 版本 + 提交历史
curl -s "https://api.github.com/repos/sdli1995/dlssg_for_sm86/commits?per_page=3" | python -c "..."
# 2. README 标题(版本探测依赖的正则)
curl -s "https://raw.githubusercontent.com/sdli1995/dlssg_for_sm86/main/README.md" | head -2
# 3. 文件树(比对本项目 Payload 表的 SourcePath)
curl -s "https://api.github.com/repos/sdli1995/dlssg_for_sm86/git/trees/main?recursive=1" | python -c "..."
# 4. INI 键(schema 是否变化)
curl -s "https://raw.githubusercontent.com/sdli1995/dlssg_for_sm86/main/dlssg_sm86.ini" | grep -E "^[A-Za-z]+="
# 5. 证书指纹(下载一个 DLL 验签)
curl -sL -o /tmp/v.dll "https://gh-proxy.com/https://raw.githubusercontent.com/sdli1995/dlssg_for_sm86/main/version.dll"
powershell -Command "(Get-AuthenticodeSignature 'C:\tmp\v.dll').SignerCertificate.Thumbprint"对照点:
| 项目 | 代码位置 | 变了要改 |
|---|---|---|
| 下载路径 | ModFetcher.Payload |
上游挪目录(如 0.3.0 的 altnative/→alternatives/)只改这张表 |
| 入口名 | ModSource.ProxyCandidates / KnownProxyNames |
新增/删除代理名 |
| 证书 pin | ModFetcher.PinnedCertThumbprint |
上游换证书(镜像源会直接拒绝) |
| 版本探测 | ModSource.ReadVersionFromText / ModFetcher.ReadVersionFromReadme |
横幅/标题格式变化 |
| INI 键 | IniTemplate.Values |
新键需要界面控件 |
| 默认值 | GameProfile 字段默认值 |
上游调默认(0.3.3 把倍率默认从 5 调到 3) |
历史经验:0.3.4 修了 0.3.3 在 RTX 30 上的驱动崩溃、0.3.5 修了帧生成重建后的花屏——这类纯运行时修复不需要改管理器代码,只需让用户更新负载。判断标准是:布局/schema/入口/证书都没变,就只更新负载。
- Mimosa hook 拒绝 Bash 写 .cs:所有 C#/XAML 改动必须用 Write/Edit 工具(hook 会在提交前做安全扫描)。
- .NET 8 SDK 会把 commit hash 拼进
InformationalVersion(1.9.3+abc1234),界面只取+前的部分。 ModSource是构造时快照:导入自定义代理后必须重建实例才能看到新入口。Stopwatch.GetTimestamp与TimeSpan不能混算,用Stopwatch.GetElapsedTime()。- 本地代理(Clash)抖动会导致 push 失败——
git push报 443 连接失败时,提交和 tag 是安全的,稍后重试即可(Git 配置里 http.proxy 指向 127.0.0.1:7897)。
按价值排序:
- 310.1 构建变体现已支持(当前工作区):DLL 下载路径加
310.1/,INI 仍取上游根目录;本地使用mod/variants/310.1与默认根构建分开。ManagementState.cs定义构建限制与配置快照,MainWindow.Management.cs负责保存状态与关闭流程,MainWindow.Batch.cs负责显式勾选、逐项结果与失败重试。旧记录没有应用快照时显示待应用,不臆测其配置。 - 安全审计缺失:Mimosa 扫描在 v1.8.4 之后多次不可用,那几次提交没有扫描结论。工具恢复后值得补跑一次深度扫描。
- 构造审查的 Optional 项(未做,风险低):
ZipFile.ExtractToDirectory不限制解压总量(恶意镜像可炸磁盘;签名校验仍在写盘前,属可用性问题)- 保存失败与关闭保护已落地:Save 返回结果;UI 保留内存记录和重试提示,批量保存失败停止后续项;Closing 等待操作完成或取消下载后退出。此流程不覆盖强制结束进程或断电。
- 反作弊规则表与 GPU 设备 ID 区间是经验数据,遇到误判按实际游戏样本增补。
- issue 回复是给外部用户看的:大白话讲原因、给可操作步骤、版本号只提最新;实现细节留在 commit message 与代码注释里。
- README 一切从简,游戏特有事项集中在页首的注意事项块。
- 不引入第三方二进制;用户手装的文件只做识别与接管,不打包、不改写内容。
- 提交信息写清"为什么"(多数缺陷是语义变化引发的,只写"改了什么"后人会改回去)。
- 改动上游适配时,先跑第 5 节的核对清单,再决定改代码还是只更新负载。