Skip to content

Latest commit

 

History

History
2835 lines (2051 loc) · 199 KB

README-完整开发日志.md

File metadata and controls

2835 lines (2051 loc) · 199 KB

Fluxion

Windows 游戏优化 + DLSS 帧生成管理(由 GameBoost v1.0.5 与 DLSSG-Tool v1.1.0 合并重构,现迭代至 v3.3.x)

一句话:一键把系统调到适合游戏的状态,并给 RTX 20/30 系游戏接上 NVIDIA 只给 40 系开的帧生成。


1. 它解决什么问题

问题 本工具的答案
3A 游戏帧数低 电源/调度/GPU/网络/内存全方位优化 + DLSS 帧生成(3060 Ti 也能开)
竞技 FPS 手感黏、延迟高 驱动配置自动化(低延迟=超高、电源=最高性能)+ 0.5ms 定时器(仅 FPS 档)+ 网络/网卡优化
二游/3A 着色器编译卡顿 NVIDIA 着色器缓存=无限制 + 游戏期电源联动
黑屏/掉帧找不到原因 nvlddmkm 崩溃取证、C 盘守护、网络哨兵、覆盖层规避
帧生成装了没用 运行诊断:日志 + 进程模块双证据,直接告诉你代理有没有真正激活

2. 架构(v2.0 重构后)

Fluxion/
├── src/
│   ├── Core.cs      逻辑层:配置、优化执行、监控看门狗、游戏联动、NVAPI、备份/还原
│   ├── Dlssg.cs     帧生成模块:游戏扫描、代理装卸、入口选择、日志诊断、参数、下载
│   ├── Ui.cs        界面层:简约浅色 UI(系统标题栏 + 侧栏导航 + 白色分区 + 原生控件)
│   └── app.manifest UAC 清单:requireAdministrator(安装版必需)
├── compile.ps1      csc 编译脚本(三文件 + icon + manifest)
├── rebuild.bat      一键:编译 → 停旧实例 → 部署到 <部署目录>(便携版)
├── make_installer.bat  一键:构建安装包 Setup.exe(需 Inno Setup 6)
├── installer/       Inno Setup 安装脚本(Fluxion.iss)
├── check.bat        静态预检(不依赖编译器,编译前先跑这个)
├── config.json      全部开关与参数(优化项分节 + dlssg 段)
├── tools/           静态校验脚本(Python)
│   ├── static_check.py   语法合规/括号平衡/符号引用/重复成员/隐式转换/CS0136/CS0108
│   ├── member_check.py   自定义控件的成员访问(CS1061 风险,作用域感知 + 按基类自动推导)
│   ├── type_ref_check.py 静态类型成员引用(Theme./Native./Program./Dlssg.,CS0117 风险)
│   ├── layout_check.py   绝对坐标越界与兄弟控件相交(旧布局机制的兜底)
│   ├── orphan_check.py   孤儿控件与空分区(新布局机制的兜底)
│   └── iss_check.py      安装脚本校验(BOM/段名/引号转义/Source/Tasks/提权守卫)
├── legacy/          重构前的旧源码备份(仅存档,不参与编译)
└── icon/icon.ico

分层原则:UI 只管画和点击,所有系统操作都在 Core/Dlssg 里;UI 通过 Program.ApplyAll() 这一类编排方法调用逻辑,不再像 v1.x 那样把 13 个优化调用散在窗体的点击处理器里。


3. 编译与部署

双击 rebuild.bat

它会做四件事:编译 → 停掉正在运行的实例 → 部署到 <部署目录>\ → 提示完成(首次部署时同时复制 config.json)。

  • 编译失败会在 compile_result.txt 里留下完整 csc 报错,把这个文件发给助手即可定位。
  • 启动需要管理员权限(要改注册表/电源/网卡)。编译产物带 UAC 清单(src/app.manifest,requireAdministrator),由系统在进程创建时弹一次提权确认、快捷方式显示盾牌;代码里原有的自提升逻辑现在只作兜底(已提权则自动跳过)。
  • 配置文件已存在时不会被覆盖,你的自定义设置会保留。

编译前先跑一次静态预检(可选,30 秒)

双击 check.bat

它不需要编译器,只做静态扫描,能提前发现绝大多数低级编译错误:

检查项 对应真实编译错误
C# 6+ 语法($"" 插值、?.、表达式体成员、nameof、using static) CS1026 / CS1525
括号 / 花括号 / 方括号平衡 CS1513 / CS1022
跨文件符号引用(Program.X / Dlssg.X / Theme.X 是否存在) CS0117 / CS1061
同一类型内签名完全相同的重复成员(合法重载不报) CS0102 / CS0111
new byte[long表达式] 这类隐式转换(长→整型) CS0266
自绘控件上的成员访问是否真的存在(作用域感知,避免同名局部变量误报) CS1061
局部变量作用域冲突(外层变量 vs 嵌套块内同名变量 / foreach·for·using 头部变量重名) CS0136
自定义类成员与 WinForms 基类成员重名(遮蔽 Control.Right / Panel.Tag 等) CS0108
调用本项目自己的方法时实参个数不匹配 CS1501 / CS1729

两个脚本的判定规则都做过反向测试(人为注入已知错误,确认全部命中;再对真实源码跑出 PASS),所以 PASS 是有意义的信号,不是空转。反向测试不只是"能抓到",还要零多余告警 —— 探针应恰好报出注入的那几条。

  • 用 python tools/static_check.py src / python tools/member_check.py src / python tools/iss_check.py installer/Fluxion.iss . 单独跑也行,退出码 0 = 通过。
  • python tools/syncheck.py = 编译冒烟(编成一个临时 library,只看编译器怎么说,不产也不替换任何产物)。 它同时跑一段「runner 收尾自检」:19 个 run_*.py 必须统一用 tools/_probe_runner.py 的 finish(<探针进程>, verdict=True|False) 收尾,且声明 verdict=True 的那些探针源码里必须有 「按 FAIL 返回非 0」的那一行。为什么值得单独盯:这些 runner 曾经无论探针红不红都退出 0 ("跑一遍全绿"其实是"跑一遍没看结果"),第一版补丁靠在模块全局里猜结果变量名 —— 谁改个 变量名就又失效了。现在收尾只有一份实现,且必须显式传参。
  • 注意:这是辅助手段,不能替代编译器。类型推断、重载决策、泛型约束等仍以编译器为准。

3.1 打包成安装包(要 setup.exe,不要便携版)

双击 make_installer.bat

产物:installer_out\Fluxion-Setup-<版本号>.exe(带命令行的静默打包可用 ISCC.exe /O<输出目录> installer\Fluxion.iss 直接跑,本机 ISCC 在 %LocalAppData%\Programs\Inno Setup 6\)。

前置:先跑过一次 rebuild.bat(安装包直接用编译好的 Fluxion.exe)。机器上没有 Inno Setup 时,脚本会打印安装指引并询问是否用 winget 自动装(winget install --id JRSoftware.InnoSetup -e),也可以自己去 https://jrsoftware.org/isdl.php 下载。

安装包行为:

项 行为
安装位置 C:\Program Files\Fluxion(64 位,安装时提权)。建议保持默认,别指到 rebuild.bat 的部署目录(见下方排错)
快捷方式 开始菜单 +「创建桌面快捷方式」可选任务
开机自启 可选任务 → 建提权计划任务(schtasks /SC ONLOGON /RL HIGHEST),不在每次登录弹 UAC;与应用内「开机自启」开关共用同一任务名
数据目录 安装时创建 C:\ProgramData\Fluxion\ 并放入 config.json(已存在则不覆盖)
卸载 控制面板/「应用」里可见;配置、日志、备份不会被删除,卸载完成会提示路径
升级 AppId 固定,再次运行新版本安装包即识别为升级覆盖
运行中安装 靠互斥体 Fluxion_SingleInstance 检测到程序在跑,会先要求关闭

便携版 vs 安装版(同一个 exe,运行时自动判断)

便携版 安装版
判定条件 exe 同目录有 config.json 没有,且 %ProgramData%\Fluxion 存在
数据位置 exe 同目录 %ProgramData%\Fluxion\(config / logs / backup / dlssg)
侧栏底部 显示「便携版」 显示「安装版」

之所以要分流:Program Files 是只读的程序目录。把备份和日志写在那里,卸载会被一并删除、升级也可能被覆盖 —— 而备份正是用来还原系统的,丢了就没意义了。

排错:安装末尾报「CreateProcess 失败;错误代码 740」

Fluxion.exe 带 requireAdministrator 清单,而 Inno Setup 的 postinstall 运行步骤故意以原用户(未提权)身份执行 —— 直接 CreateProcess 一个要求提权的 exe 会被系统拒绝,740 就是 ERROR_ELEVATION_REQUIRED。修法:给该 [Run] 条目加 shellexec 标志,改走 ShellExecuteEx,由系统接管 UAC 提示。

同一轮复查还修掉两个自己埋的隐患:

问题 原写法 现在
每次登录弹 UAC 写注册表 Run 键(由 shell 启动,命中提权清单 → UAC) 建提权计划任务,无提示;名字与应用内开关一致
卸载可能删掉真实备份 [UninstallDelete] 删 {app}\logs、{app}\backup 整段删除(万一安装目录被指到便携版目录,那段会连真实备份一起删)

tools/iss_check.py 会在出包前把这些点全查一遍(BOM / 行尾 / 段名 / 引号转义 / 续行 / [Files] Source 存在性 / Tasks 引用 / AppMutex 一致性 / postinstall 提权守卫),并把转义后的真实命令行打印出来核对:

python tools/iss_check.py installer/Fluxion.iss .

⚠️ 不要把安装目录改成 rebuild.bat 的部署目录(<部署目录>)。那是便携版/开发的输出目录:装进去会得到「已安装 + 便携」混合状态 —— 目录里同时有 unins000.exe 和便携版 config.json,程序会按便携版模式就地读写,而下次 rebuild.bat 部署又会覆盖已安装的 exe。安装包保持默认的 C:\Program Files\Fluxion 即可。


4. 优化项清单(全部真实生效,且可一键还原)

模块 优化项 依据
电源 卓越性能计划、处理器 100%、关 USB 选择性暂停/PCIe ASPM/睡眠休眠硬盘超时 消除低负载降频与链路节能毛刺
电源 游戏期临时电源联动(进游戏切档、退出还原) 办公零影响
调度 Win32PrioritySeparation=38(短量子+前台增强) 前台游戏输入响应
调度 异类线程调度策略(混合架构首选 P 核) Intel Thread Director 官方做法(v2.0 新增,替代 v1.x 的硬绑)
GPU 关闭 GameDVR、Windows 游戏模式、HAGS 开关 消除录制开销 / 前台优先 / 降帧时间波动
GPU NVAPI 驱动配置自动化(竞技档 / 3A 档双档) 等价 NVIDIA 面板手动设置,全自动
网络 关 Nagle、关流量节流、SystemResponsiveness=10、网卡节能全关 小包即时发送 + 前台优先
内存 DisablePagingExecutive(内核不换页) 减少缺页卡顿
服务 SysMain 禁用(SSD 场景) 减少后台 IO 抖动
输入 关闭鼠标加速 读系统鼠标的游戏更跟手
后台 游戏加速包:挂起后台进程 + 内存整理 + 暂停 Windows 更新下载 参考成熟产品做法,退出完整恢复
定时器 游戏期 0.5ms 定时器(仅 FPS 档,退出还原) 降低输入延迟
安全 Defender 游戏进程排除(默认关,安全权衡项) 避免实时扫描 IO 尖峰
桌面 关拖拽实时渲染 / 透明 / 动画 DWM 合成开销
监控 nvlddmkm 崩溃取证、C 盘守护、网络哨兵、NVIDIA 覆盖层规避 故障定位

检测但不修改:内核隔离/VBS 状态(调研结论:收益不确定且明显降低安全性,交由你自行决定)。

4.1 取值细调(v3.6.0):具体档位交给你自己选

一键优化给的是「程序认为的最优值」,但很多项的最优值本来就因人而异(着色器缓存上限、DLSS 强制预设、 量子长度……)。所以 v3.6.0 起,凡是「能给出具体档位」的项,档位全部摊开让你自己选。

三个入口(都在「性能优化」页):

入口 位置 用途
双击带 ⚙ 的行 优化项明细表 直接跳到该项的档位选择
「调整选中项」 表格上方按钮 同上,键盘 / 鼠标都顺手
「取值细调…」 表格上方按钮 /「自定义项」对话框 完整清单,18 项一次看完

体检表总行数随环境变化(驱动层要能读到 nvdrsdb、异类策略只在混合架构 CPU 上出现; 本机是 51 行)。其中 19 行带 ⚙(可调),其余是只读核验 —— 表下方直接写明这个区分。

18 个可调项:着色器缓存开关 / 缓存大小上限(12 档:0、128MB、256MB、512MB、1GB、4GB、 5GB、8GB、10GB、16GB、100GB、无限制)/ DLSS 模型覆盖 / 强制预设字母 / 预设档 / 竞技档·预渲染帧数 / 竞技档·电源管理 / 竞技档·垂直同步 / 竞技档·纹理过滤质量 / 3A·MMO·二游档取值 / 后台帧率上限 / HAGS / Win32PrioritySeparation / 处理器最小状态 / 处理器最大状态 / 前台响应优先级 / Nagle / 网络流量节流。 另有「HAGS × 竞技游戏(A/B 建议)」一行也带 ⚙ —— 它与「硬件加速GPU计划(HAGS)」共用同一个旋钮。

默认值 = 改造前的硬编码值,所以升级后行为零变化 —— 只有你主动改了才会变。

哪些坚决不给旋钮:HPET 强制、GPU 中断绑核、MSI 强制转换、rBAR 全局强开、异类策略。 理由是多来源明确警告:这类项改错的代价是设备从系统里消失或系统起不来。 「给用户权限」不等于「给用户自毁按钮」—— 只读核验的价值是让你知道现状,不是让你去动它。

⚠️ 边界:驱动层取值写进 nvdrsdb 后重启游戏才生效;HAGS 与量子长度需重启系统; 把某项改回默认不会自动撤销已写入系统的值(例如 Nagle 已写进网卡)—— 整体回退用「恢复备份」。

4.2 v3.6.1:状态分档 + 表格观感(用户指出的两处)

① 「HAGS × 竞技游戏(A/B 建议)」这一行为什么没反应、为什么顶着红徽章?

它是建议,不是缺陷:只在「RTX 30/20 系 + HAGS 已开 + 有竞技档游戏」时出现,意思是「现状已是推荐值,但值得你自己 A/B 验一次」。v3.6.0 之前它有两个毛病:状态写的是 🟡,而界面只认 ✅/⚠️ 两种开头 —— 开头不是这两个的一律渲染成红色「需处理」,于是这一行里写着「HAGS 已开」、徽章却在报错;而且它没挂可调键,双击没有任何反应。

现在状态分成四档,并且这一行也挂上了 hags.enable(看到「可以 A/B」就能当场切,切完要重启系统):

徽章 含义
已生效(绿) 当前值与优化目标一致
未生效(橙) 该生效却没生效(系统更新 / 驱动重置)
待确认(灰) 不是缺陷:可选 / 不适用 / 读不到(DPC 计数器不可用、HAGS 的 A/B 建议……)
需处理(红) 读取失败或需要管理员权限

② 滚动时「有的色块更白、更割裂」

三个原因叠在一起,都已修掉:

原因 修法
行底色是逐格铺的(一格一个 DrawSubItem),格与格之间的 1px 接缝、最右列右侧的空档没人画 改到 DrawItem 整行铺一次
斑马纹是 #FFFFFF / #F8FAFC(只差 3%):看不出「这是交替条纹」,只看得出「这些行颜色不一样」 去掉斑马纹,所有行同色 + 底部一条极浅分隔线
ListView 是原生控件,Control.DoubleBuffered 对它无效 —— 滚动时先擦成白底再逐行重绘,中间那一帧就是一片更白 打原生扩展样式 LVS_EX_DOUBLEBUFFER(探针读回确认:扩展样式 0x00010020)

顺带修掉一个自 3.x 就存在的隐性问题:自绘里判断「是否选中」用的是 e.State / e.ItemState,而实测在部分绘制路径下它会把 Selected 位给到每一行(tools/ui_snapshot.py light lst 1 可复现:控件里没有任何一行被选中,绘制回调里的 Selected 位却行行都有)—— 按它画,整个列表会被涂成「选中态」底色并给每行加一条绿色强调条,看起来就是「这些行为什么跟别的不一样」。现在改成读行自己的 e.Item.Selected。


5. 帧生成(DLSSG)模块

5.1 原理

NVIDIA 官方帧生成只给 40 系(SM90)编译了可执行内核;20/30 系(SM86/SM75)运行时里没有对应 cubin。本模块接入开源项目 dlssg_for_sm86:以 DLL 代理方式接管游戏对 nvngx_dlssg.dll 的加载请求,换成补齐 SM86/SM75 内核的运行时(AI 模型仍是 NVIDIA 原厂)。

5.2 使用流程

  1. 设置页确认下载代理(国内需 Clash/FlClash 的 7890 端口),点「下载运行时」
  2. 帧生成页点「扫描游戏」,列表会标出每个游戏是否集成 DLSS 帧生成(nvngx_dlssg.dll / sl.dlss_g.dll)以及是否自带 FSR3
  3. 点选游戏 → 「开启选中」;不想要了「关闭选中」完全还原
  4. 进游戏 → 画面设置 → 帧生成 → 选 DLSS Frame Generation → 关垂直同步、帧率上限设无限

5.3 参数说明

参数 取值 建议
架构 Router SM86(30 系)/ SM75(20 系) 按显卡选
内核类型 PTX(驱动 JIT,通用)/ Auto / Cubin 默认 PTX,异常再试 Auto
光流精度 0 精确 / 1 近似 画质优先 0
最大生成帧 1/2/3 实测这是上限,实际倍率由游戏请求决定;显存占用随输出分辨率而非倍率变化(2.10.2 修正)
日志级别 0~3 排错调 2~3,日志在游戏目录 dlssg_sm86\logs\*.jsonl

5.4 实测结论(写进代码里的经验)

  • Cyberpunk 2077 (2.31) 必须用 winmm.dll 入口:该游戏导入 version.dll 但运行中从不调用其函数 → 代理的延迟初始化永不触发,表现为「DLL 在进程里、零日志、帧生成无效且掉帧」。换 winmm 后立即激活,实测基准 135 → 190 帧。本模块内置该映射并自动处理。
  • 判定代理是否真的在工作,唯一可靠依据是日志目录出现 runtime_redirect / feature_created / evaluate 事件 —— 「运行诊断」按钮会读日志 + 查进程模块,直接给结论。
  • 代理加载了但没日志 → 点「换入口重试」换一个入口 DLL(游戏只加载不调用某个入口时,代理永不初始化);日志出现但没补帧 → 等游戏内开启帧生成。
  • ⚠ 本模块不是把 DLSS 帧生成转成 FSR3:代理装上的是 NVIDIA 原厂 DLSS-G 运行时 + 自研 SM86/SM75 内核。若游戏内只出现「FSR 帧生成」,那是代理没被激活(见上一条),不是它本来就只能给 FSR。
  • 带内核反作弊的游戏(绝区零 / 鸣潮 / 终末地 等)自动改用 d3d12.dll 入口(v2.1.0):这类反作弊会按【文件名】拦掉 version.dll / winmm.dll 等常规入口(上游 issue #111),而 d3d12.dll 是 DX12 游戏必须加载的系统库,拦不掉。工具会落三件套(代理 + 系统原件 + 载体),框架细节见第 ⑮ 节。可在帧生成页用「反作弊游戏自动改用 d3d12 入口」开关关掉。
  • 反作弊若把代理改名隔离(形如 version.dll.1271877294),文件不再被加载却白占 15 MB —— 点「清理残留」回收(「关闭选中」也会一并清)。
  • 扫描识别到但被忽略清单挡住的游戏会灰显在列表里,选中点「开启选中」即可恢复收录(不会再静默消失)。

5.5 使用须知

  • 必须开启 HAGS(设置→系统→显示→图形→硬件加速 GPU 计划),本工具会检测并提示
  • 基础帧低于 40 不建议开:会明显发糊、延迟增大;先用 DLSS 超分把基础帧拉到 60 再开
  • 竞技射击不要开(CS2/Valorant/三角洲):帧生成引入额外延迟,得不偿失 —— 这些游戏用优化模块即可
  • 8GB 显存:1080p 约需 320MB、1440p 约 490MB 显存余量(占用随输出分辨率变化,与倍率基本无关)
  • 游戏更新覆盖代理文件后需要重新开启;代理只往游戏目录放 1~2 个文件(d3d12 入口为 3 个 + 1 个 ini),删除即还原,也可用 Steam「验证文件完整性」

5.6 判据与可见性:换一次包要改哪里(2026-09-21)

代理包每发一版,"这一版是什么"这件事在本工具里分成三层,缺一层就会出现 "代码说 0.3.5、磁盘还是 0.3.2" 而无人发现:

层 位置 谁会读它
判据(体积档 / 入口名单 / 签名白名单 / 版本结论) %ProgramData%\Fluxion\catalog.json Pack.Classify、Dlssg030.IsProxySize/CodeOf、ProxySign.Of、Plan.OwnerOf/EntryNames
磁盘事实(资源包里那份到底多大、是哪一版) 量出来的,不存 帧生成页「运行时状态」第三行;Dlssg030.PackActual()
导入账本(哪来的、什么时候、每个文件体积 + sha + 指纹) %ProgramData%\Fluxion\dlssg030-pack\pack.json 只由「拖入导入」写;PackActual() 读它比对
  • 判据外置:catalog.json 缺失或读不动时一律回落代码里的内置默认,并在启动日志写一行 判据来源:… —— 读坏了静默用默认值是最难查的那类错,所以必须留痕。 这份文件没有界面入口(它属于开发期手段,不该占用户界面):要改就照 Catalog.DefaultJson() 输出的形状手写一份,存盘即生效、不用重新编译;删掉它就回到内置默认。 字段:proxy.ver / proxy.bands[](体积→哪一版,闭区间)/ proxy.windows[](是不是本方案, 开区间)/ proxy.legacy、proxy.sourceSize(旧版 0.2.x 的两个窗口)/ proxy.entries[] / proxy.signs(作者与 NVIDIA 两张证书的指纹)/ knowledge[](只在某些版本成立的上游实测结论)。 给新版本加档时 bands 与 windows 要一起加:IsProxySize 只看 windows, 只加细档会出现"代号认得、体积不认"。
  • 知识条目集中在 knowledge[]:UI 里那句"绝区零要不要伪装"以前是按版本手改文案, 同一个结论在 功能缺口与路线图.md 里还有几处。现在由 Catalog.Knowledge(id) 按当前 proxy.ver 挑那一条成立的,挑不出来就退回中性说法(宁可少说一句,不要说错一句)。 其中一条就是升级路径:别按版本号顺序升级 —— 0.3.3 让 NVIDIA 自家 NGX 模型也吃到 架构改写导致 GPU 挂起(上游 #535/#538/#540/#542),0.3.4 才修;可升级路径只有 0.3.2 或 0.3.5+。
  • 入口巡检:帧生成页「停放遗留项」旁边那行会说清"这个目录里有几套代理并存"。 两代代理同时在场 = 两套帧生成抢同一个入口(插件打架那一类故障),以前只有点部署才会顺带处理, 平时看不见。巡检是只读的;停放只把文件改名成 <原名>.parked.<方案>,不删文件。
  • 备份与回收:部署 / 换包 / 卸载会往 dlssg030-pack\_backup、xess-pack\_backup、 xess-pack\_removed 里堆东西(2026-09-21 实测 1.48 GB / 15 项)。设置页「备份与回收」 按归属统计占用,回收一律送回收站,并且每个归属(整包一份 / 每个游戏一份)里 最新那一份永久保留 —— 卸载和换方案回滚要读的就是它。
    • 「回收范围」下拉的每一项后面会回填当前统计出的条数与体积。这不是装饰:默认档是 30 天, 而刚换完包留下的备份通常只有几天 —— 不回填的话用户看到"点了没反应",只会以为功能坏了。 真要清那 1 GB 得选「7 天前」或「不限时长」(保留策略保护的那 484 MB 仍然不动)。
    • 两个不可逆感最强的确认框(「送回收站」「停放遗留代理」)默认键是取消, 手快连按两下不会把东西清掉;两个动作本身都可逆(回收站可还原 / 停放只是改名)。
    • 刻意没做的事:日志目录(logs\,本机 931 个文件 / 1.3 MB)不做回收入口 —— 为 1.3 MB 多一个按钮 + 一套接口不值得,这是"别把工具做冗余"的一条。
  • dlssg\source\ 只放旧版 0.2.x 的下载源件(体积 15.5~15.8 MB,配套 altnative\)。 0.3.x 走 dlssg030-pack\{common,alts},不读这个目录。SourceReady() 现在除了 File.Exists 还校验体积:源位上躺着 0.3.x 的件时不再误报"运行时已就绪", 并且界面上会写明为什么不算。

6. v2.0 相对 v1.0.5 的变更

合并:DLSSG-Tool 全部能力(扫描/装卸/入口选择/日志诊断/参数/下载)以原生 C# 模块并入,不再是第二个程序。

界面:完全重做 —— 系统标题栏 + 侧栏导航(仪表盘/性能优化/帧生成/实时监控/设置)+ 白色分区 + 纵向滚动,配色为浅色极简(底 #F3F5F7、卡片白、1px 描边 #E2E5E9、主色 #0969DA)。 控件全部用系统原生:浅色主题下原生外观本来就是正确外观,不需要再靠自绘去对齐色板 —— 这也正是深色版反复出现"色准不一致"的根源(复选框、下拉框、滚动条、提示框在深色下永远是浅色皮肤)。 详见 7.1 ⑦。

纠错(依据官方文档/白皮书):

  1. P 核硬绑默认关闭 → 改用「异类线程调度策略」。Intel Thread Director 白皮书明确不建议混合架构手动钉核,硬绑会破坏动态调度决策。保留开关仅供对比测试。
  2. 一键优化编排下沉到逻辑层(Program.ApplyAll),不再散落在 UI 点击处理器里;备份序列与执行序列绑定在一处,避免以后加项漏备份。
  3. 字体 GDI 句柄缓存:自绘控件每帧取字体改为缓存复用,避免长时间运行后句柄耗尽。

新增:异类线程调度策略、内核隔离/VBS 状态检测、帧生成运行时与接入数状态项、帧生成运行诊断(日志+进程双证据)、配置写回支持字符串/整数型。

精简:移除了窗体上的 CLI 按钮(体检/监控改为界面内原生展示,不再弹 PowerShell 窗口)。

静态审查中当场修掉的 2 个真实编译错误(都是同类:long 不能隐式转 int 做数组长度,会报 CS0266):

  1. Dlssg.cs 读超长日志(>4MB 只读尾部)时 new byte[fs.Length - fs.Position] → 改为先 int tail = (int)(...) 再建数组
  2. Ui.cs 日志跟随(FollowLog)里 new byte[len - lastLogLen] → 同样改为先截断为 int

首次真机编译后修掉的 3 个问题(csc 实际报出,说明静态审查有盲区,已补进 tools/ 与 check.bat):

严重性 问题 修法
🔴 CS0136 中断构建 Core.cs 里 diskGuard 段的 dg 在 if 子作用域中,之后 dlssg 段又在方法体里声明 var dg → 外层声明会改变内层 dg 的含义 dlssg 段改名 dlssgSec(只动新增代码)
🟡 CS0108 StatBar.Right 遮蔽 Control.Right(右边缘 X 坐标),基类语义被静默改变 改名 RightText
🟡 CS0108 RowItem.Tag 遮蔽 Panel.Tag(object 通用载荷) 改名 TagText(自定义载荷走已有的 Data 字段)

7. 备份与还原

  • 所有注册表/电源/网卡修改写入前自动备份到 backup\
  • 「恢复备份」= 还原到优化前;「恢复默认」= 恢复 Windows 出厂值(两者不同)
  • 帧生成:dlssg\state.json 记录安装信息,卸载时用「入口名 + 体积 + SHA256」三重校验后才删除,不会误删别人的文件;被占用的同名文件会先备份再替换

7.1 v2.0 首版的六个真实缺陷(已修复,务必重新编译)

① 满屏白块 + 红叉 —— 共享缓存字体被 using 释放(真凶)

现象:启动后左栏导航、右侧 CPU/内存/GPU 进度条、若干卡片出现白底方块(WinForms 的绘制失败标记)。

根因:Theme.F()/FB()/Mono() 返回的是全局共享的缓存 Font 实例(缓存本身是为了不泄漏 GDI 句柄), 但首版有 10 处写成 using (var f = Theme.FB(10f)) { … DrawString … } —— using 结束会 Dispose 掉这个共享字体。 此后所有同字号的绘制都在已销毁的 GDI 句柄上操作,GDI+ 返回 InvalidParameter,抛出 ArgumentException: 参数无效。; WinForms 对 UserPaint 自绘控件绘制失败的兜底,是把该控件区域填成系统色并打叉。

证据:程序自己的日志每次启动都有恰好 14 条 UNCAUGHT_THREAD: ArgumentException: 参数无效。; 而同时刻旧版 v1.0.5 的日志里一条都没有 —— 直接锁定是界面层新引入的,与此前怀疑的"显示管线伪影"无关。

修法:10 处全部改为普通局部变量(缓存字体永不 Dispose);Theme 字体缓存处加了醒目警告注释; tools/static_check.py 新增 CACHED-FONT-USING 规则兜底(已做反向测试),以后再犯会在编译前被拦下。

影响面 说明
10 处 Card 卡片标题、NavBtn 导航图标、StatBar 标题与数值、Chart 标题、RowItem 徽标/副标题/右侧标签、自绘标题栏主副标题
连带受害者 所有 Font = Theme.F(8.5f) 等被赋值过的 Label / ComboBox / TextBox(字体实例被别人的 using 销毁)

② 运行本程序会把旧的 GameBoost 1.0.5 一起拉起来

不是本程序去启动它,而是 v1.0.5 自己的看门狗还在:

环节 事实
启动项 %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\GameBoostGuard.vbs(登录时静默拉起 Guard.ps1)
判据 每 30 秒检查一次「进程名 = GameBoost」,不在就 Start-Process <旧版部署目录>\GameBoost.exe --minimized``
实况 19:07:53 旧版被退出 → 19:07:58 / 19:08:29 / 19:08:59 被看门狗连拉 3 次

处置(已做,全部可逆):<旧版部署目录>\guard.config.json 的 keepAlive.enable 改为 false (原文件备份为 guard.config.json.bak-20260911);启动项先改名 GameBoostGuard.vbs.disabled, 2026-09-12 又从启动文件夹移出至 backup\GameBoostGuard.vbs.disabled(.disabled 扩展名会导致 Windows 登录时每次弹「选择应用」对话框,故必须移走,未删除可随时放回)。 v2.0 自带同等的看门狗模块(黑屏取证 / 磁盘守护 / 网络哨兵 / 覆盖层规避),关掉旧版不丢能力。

⚠️ 那个已经在运行的看门狗进程需要你自己结束一次(它只在启动时读配置):重启电脑即可; 或在任务管理器里结束那个隐藏的脚本宿主进程。重启后旧版不会自启(其「开机自启」已在 19:08 从旧版界面里取消)。

③ rebuild.bat 对安装版会误判成便携版

rebuild.bat 原来无条件把 config.json 复制到部署目录。若该目录正是安装版 (存在 unins000.exe,例如你现在的 <部署目录>),这一步会让程序下次判定为「便携版」, 把配置/日志/备份从 %ProgramData% 挪进程序目录。已加判断:检测到 unins000.exe 就跳过复制。

④ 界面「色彩与美观」问题:深色分层失效(已修复)

反馈截图的现象是:整屏糊成一坨黑、输入框像挖了个洞、复选框是系统白方块。 根因不是「配色不好看」,而是层次丢失 —— 旧色板里 窗口底(13,17,23) / 侧栏(17,22,30) / 卡片(22,27,34) 三层亮度只差 4~5 级,等于没有分层;描边 (33,38,45) 也几乎与卡片同色,圆角轮廓看不见 = 卡片"没有形状"。

现象 根因 修法
整屏一坨黑、卡片没轮廓 三层背景只差 4~5 级;描边与卡片几乎同色 重做色板:底 #090C11 < 侧栏 #0E1219 < 卡片 #181E27,描边提到 #2E3846;卡片加顶部 1px 内高光
输入框像"黑洞" 原生 TextBox 写死纯黑底 (11,15,20) + FixedSingle 白边 新增 FlatInput:Panel 外壳自绘圆角凹槽 + 无边框 TextBox,聚焦时描边转主色
复选框是系统白方块 原生 CheckBox 在深色下仍按系统皮肤绘制 新增 FlatCheck:整块自绘,14px 圆角框 + 手绘对勾,选中态主色实心
导航图标高低不齐 ◈ ⚡ ◉ ▤ ⚙ 在 Microsoft YaHei UI 里缺字,会静默 fallback 到 Segoe UI Symbol,字号/基线/笔画全不同 改为 16×16 几何自绘图标(NavBtn.Icon16),统一 1.5px 圆头笔画
实心按钮像"发光虚边" Primary 描边 (88,166,255) 比填充 (31,111,235) 亮太多 实心按钮改上下微渐变 + 同色系描边,顶部略亮、底部略暗
正文偏灰、状态栏字小 TextDim(139,148,158) 在卡片上对比度约 4.6:1 提到 (150,161,175) ≈ 5.7:1;状态栏 8.5 → 9pt

上一轮遗留的「ComboBox 仍是系统方角」已在 ⑤ 中解决(FlatCombo)。目前仍为系统外观的只剩: 托盘右键菜单(ContextMenuStrip,需要自定义 ToolStripRenderer)与 MessageBox 弹窗 —— 都是系统级控件, 换肤成本高于观感收益,暂不做。

工具同步:tools/static_check.py 的 CS0108 规则增加 override 豁免 —— 覆写基类 virtual 成员是合法行为, 不该按"遮蔽"报错(FlatInput.Text 覆写 Panel.Text 属此类)。已用「注入真实遮蔽 → 应报错」反向验证规则未被削弱。

⑤ 各页面 UI 错位 + 色偏(已修复,根因是 DPI)

反馈截图:标题栏出现 Fluxion优化 这样的重影、各页文字压在一起 / 被裁掉半句、下拉框里有发白的方块。

根因不是坐标写错,而是 进程 DPI 感知与硬编码像素布局之间的矛盾:

  • Core.Main 里调了 Native.SetProcessDPIAware()(为了字清晰),于是 GDI+ 按真实 DPI 渲染字体;
  • 而界面所有坐标/尺寸都是硬编码像素(窗体 1160x764、卡片 932 宽、MakeLabel 写死 200x20…)。

在 125% 缩放的屏幕上,字大了 25%、容器一点没变 → 文字溢出色块、互相压字、被硬裁半句。 标题栏副标题写死 x=158,主标题变宽后正好压上去,就是那团重影。

现象 根因 修法
标题栏 Fluxion优化 重影 副标题 x 写死 158px,主标题随 DPI 变宽后压上去 副标题位置改成"主标题实测宽度 + 间距"(TextRenderer.MeasureText)
文字压在一起 / 各页参差不齐 字体按 DPI 放大,控件尺寸不变 新增 Theme.Scale(=实际DPI/96)+ Theme.Fit(this):整棵控件树构建完成后只放大一次;Theme.S() 用于自绘偏移量,Theme.TextTop() 用 Font.Height 做垂直居中
GitHub 直连通常被墙,可走本地代 半句消失 MakeLabel 写死 200×20,且 20px 高装不下 125% 的字 改为按字体实测宽度定尺寸(MeasureLabel),并关掉 UseMnemonic
下拉框里有发白方块 / 方角 ComboBox 的边框、按钮、箭头由系统主题绘制,深色下必然亮灰 新增 FlatCombo(保留 ComboBox 类型,WndProc 拦 WM_PAINT 后整块重画,调用点零改动);下拉项走 OwnerDraw
复选框文字忽明忽暗 旧逻辑 on ? Text : ForeColor —— 勾选态把标签也点亮 标签固定 TextDim(hover 才提亮),状态只由方框表达
字边发虚、有色偏 深色底 + ClearType 亚像素抗锯齿 → 笔画边缘红蓝描边 所有自绘入口统一 Theme.Prep(g) → 灰度抗锯齿 AntiAliasGridFit

顺手修掉的两个真实布局缺陷(这次是静态查出来的,肉眼不一定注意到):

  • 「帧生成」页的 开启选中 / 关闭选中 / 运行诊断 三个按钮和"已选:…"提示,y 坐标(316/322)超出了 所在卡片高度(280),被父容器裁剪 → 等于根本看不见;同页参数卡片的标签压在输入框上 4~5px。
  • 「仪表盘」页 GPU 温度 状态条超出卡片 4px 被裁;多张卡片的说明文字框右边缘超出卡片内边距。

DPI 自助开关:若自动缩放不合意,在数据目录(%ProgramData%\Fluxion\ 或便携版 exe 同目录) 放一个 ui.scale 文件,内容写 1.0 / 1.25 即可强制指定倍率。 启动日志会打印 界面缩放 1.25x(120 DPI),窗口 1450x955,可用来确认是否按预期生效。

工具同步(本轮新增/加强 2 个校验器,因为编译只能在用户本机跑):

  • 新增 tools/layout_check.py:纯文本分析绝对坐标布局,检查「子控件是否逃出父卡片」与 「同父兄弟是否相交」。它能精确复现上面那个"按钮被裁掉"的缺陷(注入回归 4 处命中)。
  • 加强 tools/member_check.py:原来 OUR_TYPES 是手写清单,新加的 FlatCombo/FlatCheck/FlatInput 没登记 → 这三个类的调用点全部绕过校验。改成按基类自动推导(含继承闭包),校验覆盖从 275 处涨到 363 处; 反向验证:注入 cbFgAutoEntry.Checkd / cbScene.SelectedIndx 均被精确命中。
  • check.bat 从 3 步扩到 4 步(syntax → member → layout → installer)。

⑥ 编译失败 CS0136: 不能在此范围内声明名为"box"的局部变量

报错原文(compile_result.txt,用户机器上跑出来的):

COMPILE_FAIL: csc failed: src\Ui.cs(547,21): error CS0136:
不能在此范围内声明名为"box"的局部变量,因为这样会使"box"具有不同的含义,
而它已在"父级或当前"范围中表示其他内容了

根因:FlatCheck.OnPaint 里第 506 行已经有 var box = new Rectangle(0, by, bs, bs);(复选框方框), 第 547 行在 using (var b = …) { … } 块里又写了 var box = new RectangleF(…)(标签文字框)。 C# 里局部变量的声明空间是整个所在块,嵌套块内不允许出现同名局部变量 —— 与谁先谁后无关。

修法:内层改名为 textBox。

为什么校验器没拦住(这是本条的真正价值):static_check.py 里 CS0136 规则曾经存在且能跑, 但它给"两个同名声明所在块互为嵌套"额外加了一个前提 —— 外层声明必须晚于内层声明。 而实际发生的是最常见的形态:外层先声明、内层后声明,于是这条规则在真实案例上给出了 PASS。 已删除该顺序条件(嵌套关系本身就足够判定),并把报错位置改指向内层那一处, 使行号与编译器一致(编译器报 547,校验器现在也报同一处)。

顺带补上同一漏洞的另一半:局部变量与方法参数同名(void M(int x) { int x = 1; },同样是 CS0136), 以前完全没检查。两条规则都做了注入回归验证,且真实源码零误报。

还有一个会让人白跑一趟的缺陷:rebuild.bat 原来靠 if not exist Fluxion.exe 判断成败 —— 但上一次编译出来的 exe 还在,所以编译失败时它会打印 OK 并把旧 exe 复制过去, 用户看到的是"编译成功、程序没变化"。现已改为:编译前删掉旧报告 → 看 compile.ps1 退出码 → 再从 compile_result.txt 里确认出现 COMPILE_OK,任一不满足就打印真实编译器输出并中止。

  • 新增 tools/type_ref_check.py:静态类型的 Type.Member 引用校验。之前只校验 Program / Dlssg / Config,本轮往 Theme / Native 里加了不少成员(S/SF/Prep/Fit/TextTop/SFLeftPublic…), 这些引用全部绕过校验(这正是"编译器报 CS0117 之前完全没提示"的盲区)。现覆盖 5 个类型。
  • 修正 type_ref_check.py 自身的剥离器 bug:第一版用正则先删注释再删字符串,于是字符串里的 // /* 被当成真注释,一路吞掉后面的代码 —— Program / Dlssg 两个类型直接被吞成"未找到"(假阴性)。 已改为单遍扫描、字符串优先、等长空白替换(与 static_check.py 的实现等价)。
  • check.bat 从 4 步扩到 5 步:syntax → member → type-ref → layout → installer。

⑦ 简约浅色界面重做(第三版,2026-09-11)

为什么要换:深色版的错位与色准问题修了三轮仍有残留,根因不在"哪个坐标写错了",而在路线本身:

深色路线的固有代价 后果
系统原生控件(复选框、下拉框、滚动条、消息框、右键菜单)在深色下永远是浅色皮肤 只能一个个自绘去盖;盖不干净 = "色准不一致",且每加一个自绘控件就多一处错位风险
界面全部硬编码像素 + 构建完成后整体缩放一次(Theme.Fit) 两套坐标系并存:字号按真实 DPI 放大、容器按倍率缩放,任何"漏换算"的地方都表现为文字压字/被裁
自绘标题栏 拖动、圆角 Region、最小化按钮三处都要自己实现,且窗口比工作区大时还要额外收口

换成了什么:

  1. 浅色极简配色:底 #F3F5F7、白色分区 + 1px 描边 #E2E5E9、正文 #1F2328、次级 #57606A、一处主色 #0969DA。 层级靠"白卡浮在浅灰底上"表达,不靠明暗渐变;文字对比度 7:1 ~ 14:1。
  2. 控件全部用系统原生:Label / ComboBox / TextBox / CheckBox / Button / ListView / RichTextBox / FlowLayoutPanel / TableLayoutPanel。 自绘只剩三个小组件(扁平按钮、细进度条、迷你折线),且每个都只在自己边界内绘制。 列表也从"自绘行"换成原生 ListView(列宽按 DPI 换算),少掉一大块绘制代码。
  3. 纵向位置全部由实测高度推导(新 Sec 分区容器): 用法 var s = new Sec("标题"); s.Row("标签", 控件); s.Body("说明"); p.Add(s); —— 分行只有四种(标签+控件 / 说明文字 / 整行控件 / 按钮行),没有一个写死的 y 坐标; 标签与控件在各自行内垂直居中,所以"字压在框上"这类问题在结构上不可能发生。 说明文字的高度用 TextRenderer.MeasureText(..., WordBreak) 现量,与 Label 实际渲染同一套 GDI 度量。
  4. 窗口骨架用 TableLayoutPanel 显式行列(侧栏 / 内容 / 底栏),不再依赖 Dock 的 z-order 规则。
  5. 系统标题栏,不做自绘标题栏;时钟移到右下状态栏(由 1 秒定时器刷新)。
  6. DPI 只换算一次:固定像素尺寸(行高、间距、列宽、列表高度)在创建时走 Theme.S(); 仍然支持数据目录下放 ui.scale 覆盖倍率。
  7. 页面可滚动:内容超过窗口高度时出现滚动条,不会再有"控件被父容器裁掉看不见"的问题。

重做过程中查出并修掉的自身回归(都是编译器与静态检查都看不见的):

  • 关闭窗口不再收进托盘 —— 旧版 FormClosing 挂在自绘标题栏里,换掉标题栏时一起丢了; 漏掉的表现是"点 X 直接退出程序",属于功能级回归。已补回(含首次提示气泡)。
  • 运行期改文案会被旧高度裁掉 —— Sec.Body() 的高度在排版时确定,之后 lbl.Text = ... 不会重新量高度,长文案(诊断结果、扫描结果、系统摘要)会被截断。 新增 SetBody(label, text) 作为唯一入口:文字没变直接返回(高频 tick 零开销), 变了就重排所属分区;12 处运行期文案更新已全部改用它。

新增 tools/orphan_check.py:新布局机制下唯一的失效模式是"控件建好了但没挂上去" (编译通过、运行不报错,用户看不见)。该脚本检查:① Build* 方法里 new 出来的控件之后是否再没被引用; ② 分区是否挂到了页面上;③ 分区里是否一条内容都没加。注入回归精确命中 2/2。

check.bat 从 5 步扩到 6 步:syntax → member → type-ref → layout → orphan → installer。 另修 member_check.py / type_ref_check.py 的一个共性漏洞:泛型方法的声明识别 —— public T Block<T>(T c, int h) 这类方法名后面直接跟 <,旧正则匹配不到,会把合法调用误报成 CS1061。


⑧ 静态检查全绿 ≠ 能编译:真实编译才是唯一判据(2026-09-11 深夜,同一天第二次)

第 ⑦ 条做完后,六个静态检查(syntax / member / type-ref / layout / orphan / installer)全部 PASS, 但真编译器一句话就否了:

src\Ui.cs(1239,30): error CS1061: "Fluxion.Sec" 不包含 "Resync" 的定义

两个配套缺失(⑦ 里描述的功能其实一直是空转的):

  • Sec.Resync() 只有调用、没有定义 —— 第 ⑦ 条写的"说明文字变长不会被裁掉"从未真正生效。
  • 更隐蔽的是:SetBody() 靠 l.Tag as Sec 反查所属分区,而 Sec.Body() 从没给标签订过 Tag。 就算把 Resync() 补上,s 仍然是 null,照样静默失效。

也就是说:"改了 12 处调用"只说明调用点改对了,不说明被调用的东西存在。

顺带修掉 member_check.py 的两个盲区(这才是放走 bug 的直接原因)

盲区 表现 修法
强制转换式声明从不登记 var s = l.Tag as Sec; 之后 s.任意东西 全部不校验 —— s.Resync() 正是这样溜过去的 补 v = ... as T 与 v = (T)... 两类声明(转换目标即确定类型,无推断误差)
控件字段(跨方法访问)从不校验 旧注释写的是"找不到声明就跳过,不臆测",但本项目控件几乎全是字段:一个方法里 new、另一个方法里用。实际等于放掉了一大半访问点 新增类字段类型表:类体顶层 Bar barCpu, barMem, barGpu; 这类只声明不初始化的字段也登记类型

覆盖数从 155 处升到 191 处(新增的 36 处全是字段路径)。建字段表时又踩了一个自己挖的坑: Pg[] pages 的元素类型是 Pg 但不是 Pg,pages.Length 合法 —— 按元素类型校验会误报,已显式跳过数组字段。

反向验证(三个探针 3/3 命中,基线 PASS):

探针一  as 强转 + 不存在的方法   -> Ui.cs:1251  s.ResyncX_Probe (var s : Sec)     CS1061 risk
探针二  (T) 强转 + 不存在的方法  -> Ui.cs:1251  s.ResyncY_Probe (var s : Sec)     CS1061 risk
探针三  控件字段跨方法访问       -> Ui.cs:1368  spCpu.PushX_Probe (var spCpu: Spark)  CS1061 risk
数组误报防回归:基线里 Ui.cs 有 3 处 pages.Length(Pg[] 字段),基线仍 PASS

结论:编译这件事做得到,别再以"沙箱编不了"为由跳过

Bash 通道会被安全策略拦下(csc.exe 被视同"可编译任意代码",等同于 Add-Type), 但 PowerShell 通道可以正常调起 compile.ps1 —— 本次就是这样编出 COMPILE_OK 的。

所以流程调整为一句话:改完源码 → check.bat 六项预检 → 真编译一次 → 才敢说"改好了"。 静态检查的定位是"提前预警、缩小范围",它永远是编译器的补充,不是替代品。

另外 rebuild.bat 的第 2~4 步(停进程 → 拷到 <部署目录> → 拷图标)不需要编译器,可单独执行。


⑨ 2.0.3:扫描精度 + 自定义游戏增删 + 两项「未生效」修复(2026-09-12 凌晨)

用户反馈三件事:① 扫描把一堆非游戏收进列表(Clash Verge / Context Menu Manager Plus / dotnet / FlClash / Git / GitHub CLI); ② 希望能手动添加/删除游戏路径;③ 「网络流量节流」「异类线程调度策略」两项一键优化后仍显示未生效。

③ 先说两项「未生效」—— 都是 apply/check 的低级错,且都被 WriteReg 的 catch 吞了

项 根因 修法
网络流量节流 WriteReg(..., 0xFFFFFFFF, DWord) —— 0xFFFFFFFF 是 uint 字面量,而 RegistryKey.SetValue 的 DWord 只收 int,直接抛 ArgumentException 被吞 → 注册表里根本没写进去(实测回读确认"NOT SET") 改传 -1(int),落盘即 0xFFFFFFFF
异类线程调度策略 apply 和 check 用的 GUID 93b8b6dc-… 这台机器上不存在(powercfg /q 报"指定的电源方案、子组或设置不存在",错误被吞);真正存在的设置是 bae08b81-… = 异类短运行线程调度策略(SHORTSCHEDPOLICY),游戏线程正是短运行线程 —— 旧版 apply 实际只把它设成了一半,check 却查着那个不存在的 GUID,永远返回"?" → 永远显示未应用 apply/check 全部对齐到 bae08b81,并当场回读确认(AC 索引实测已是 1 = 高性能处理器/P 核)

排查方法值得记:别猜。直接在目标机上 powercfg /q SCHEME_CURRENT SUB_PROCESSOR 枚举 + 注册表回读,两个"未生效"的根因 5 分钟就实锤了。 顺带:0x00000001 在这套枚举里是"高性能处理器"(强制 P 核),0x00000002 才是"首选高性能处理器"——旧版注释把 1/2 标反了。

② 自定义游戏路径(添加/删除)

  • 帧生成页新增两个按钮:「添加游戏…」(选游戏主程序 exe,信任收录、不走游戏性判定;会向上再探两级目录找补帧组件,兼容选到 bin\x64 里的真主程序)和**「移除选中」**。
  • 持久化在数据目录新文件 games.json(不动 config.json——那边是文本手术式改键,数组不好维护):
    • custom:手动添加的游戏(exe 路径),启动时自动加载,不用等扫描;
    • ignored:手动移除的自动扫描目录 → 加入忽略清单,下次扫描不再出现。
  • 列表里手动添加的条目标注「手动添加」。

① 扫描精度:从"目录里有 exe 就算游戏"到"结构特征判定"

旧规则下 C:\Program Files 里任何一个带 exe 的目录都会被收录。新规则两层:

  1. 工具名黑名单一票否决(目录名 / exe 名):clash / mihomo / v2ray / xray / singbox / dotnet / powershell / steamcmd / github cli / discord / wechat / feishu / geforce / nvidia / bandizip / context menu / steam++ / igamecenter / thunder network… 短词(git / gh / bash / cmd / obs / jdk / java / adb)用词边界匹配——git 会子串命中 "Digital"。
  2. 结构性特征(任一命中才算游戏,主程序体积完全退出判定): UnityPlayer.dll / GameAssembly.dll(Unity)、≥50MB 的 .pak 或 Engine 目录(Unreal)、game.js/www(RPG Maker)、.rpa/renpy 目录(Ren'Py)、.pck(Godot)、data.win(GameMaker)、steam_api(64).dll、EAC/BattlEye、*-shipping.exe(UE 打包成品)、以及本工具的目标特征(DLSS 帧生成/超分/FSR3 DLL)。

⚠️ 体积为什么退出判定:第一版"≥30MB 的 exe +2 分"在你机器上实测混进了 8 个工具 —— Office(74MB)、迅雷(192MB)、MCHOSE HUB(172MB)、Hihonornote(168MB)、HONOR(31MB)、iGameCenter(72MB)、Steam++、Netease。 工具的主程序同样几十上百 MB,大小只能当佐证、不能当主证。 另外 .pak 特征也踩过一次:Edge 的 Chromium 资源包 resources.pak 有 38MB,把 Program Files (x86)\Microsoft 误判成游戏 —— 所以 .pak 还要求 ≥50MB 且排除 resources.pak / *_percent.pak 命名。

在本机真实数据上的验证(规则镜像成脚本跑真实目录):

负样本:C:\Program Files + Program Files (x86) 全部 76 个目录 → 0 误报
        (此前被收进列表的 6 个全部命中黑名单或零特征)
正样本:Steam 库 6 个真游戏 → 6/6 接受
        (Unity ×3 / steam_api ×2 / Ren'Py ×1;各引擎特征都有覆盖)

仍会被拒的边缘情况:不带任何引擎特征的独立小游戏(如部分 GOG 老游戏)→ 点「添加游戏…」手动加即可,手动条目不走判定。 六个此前误收的条目重新扫描后不会出现;不想等的话,选中后点「移除选中」,它就会立即从列表消失(并进忽略清单)。


⑩ 2.0.5:启动器类游戏的扫描与能力判定(2026-09-12)

用户反馈三件事:① 扫描读不出二游(绝区零 / 鸣潮);② 修完①之后鸣潮能力判错、绝区零注入后反而更卡;③ Cyberpunk 2077 从列表里消失了(代理其实还装着)。

根因一:Scan() 注释承诺了"注册表卸载项",代码里从来没有这一路

扫描根只有 Steam 库 + 写死的常见目录,而二游全走自家启动器、装在自定义路径: 绝区零在 <启动器安装目录>、鸣潮在 <游戏安装目录>\Wuthering Waves —— 任何一个扫描根都覆盖不到。 (排查姿势:reg.exe 被安全中心拉黑,改用 Python winreg 枚举卸载表 InstallLocation,5 分钟实锤。)

修复:注册表卸载项扫描(ScanRegistryUninstalls + ScanLauncherRoot)

  • 枚举 HKLM(64/32 位视图)+ HKCU 的 Uninstall 键,只对命中启动器关键词的条目深挖 (mihoyo / hoyo / kuro / wuthering / 原神 / 鸣潮 / 绝区零 / 崩坏 / 幻塔 / 尘白…),不会把每个已装程序翻一遍;
  • 启动器根向下探测(深度 ≤4)全子树收集过门候选 → 打分 → 贪心收录: 帧生成组件 +4 / FSR3 +4 / 超分 +2 / *-shipping.exe +3;同分时目录更深者优先, 祖先/后代目录互斥 —— 鸣潮真身(Game\Client\Binaries\Win64\Client-Win64-Shipping.exe,13 分,深度 4) 压过存根目录与启动器根(06 分、深度 01),而绝区零靠"更深"赢过启动器根的同分候选(6 分 vs 6 分)。

根因二:能力判定只找 exe 旁边,漏了 UE 的插件目录

鸣潮是 UE 打包,NVIDIA 插件在 <游戏根>\Engine\Plugins\Runtime\Nvidia\StreamlineCore\Binaries\ThirdParty\Win64\ (第 7 层),exe 旁边的 FindFile(depth 3) 根本够不着 → 明明原生支持却显示"无 DLSS 帧生成"。 新增 ProbeUePlugins():从 exe 目录向上 ≤5 级找到游戏根(含 Engine 者),再在 Engine\Plugins 里定点搜索(跳过 Content/Paks 等资源目录,只走插件目录,开销极小)。

根因三:主程序挑选一刀切跳过 launcher —— 把 2077 整条删了

CP2077 根目录只有 REDprelauncher.exe(真身在 bin\x64,但那是子目录、不在浅层挑选范围), 把 launcher 列入硬跳过名单后该目录连一个候选 exe 都没有 → 条目直接消失(而代理还装着)。 改为降级备选:launcher 类仅在目录里没有其它候选 exe 时才采用;unins/setup/crash/redist/safemode/bootstrap/install 仍硬跳过。

附带修复

  • NiceTitle() 旧实现只向上走 3 跳就耗尽、且跳出后把原名返回 → 鸣潮一直显示成「Win64」;改为最多 8 跳并正确返回第一个非结构名。
  • 新增二游注入风险确认(Dlssg.InjectionRiskNote + UI 确认框):命中二游关键词时, 点「开启帧生成」先弹提示 —— 内核反作弊注入有封号风险;原生已带帧生成(FSR3 / DLSS FG)时叠加注入 会同时占用显存与帧生成流水线,实测表现为"游戏内用的还是 FSR 帧生成,但明显更卡"。 建议二游只用「一键优化」的驱动层设置,帧生成走游戏内原生选项。

实测验证(tools/scan_mirror_check.py 跑真实目录,规则镜像自 Dlssg.cs)

== Steam 根(Inspect 直扫)==
   Cyberpunk 2077            (REDprelauncher.exe)        score=10  DLSS 帧生成 √ · DLSS 超分 √ · FSR3 帧生成 √ [启动器降级]
== 启动器根: <游戏安装目录>\Wuthering Waves ==
   Wuthering Waves Game      (Client-Win64-Shipping.exe) depth=4 score=13  DLSS 帧生成 √ · DLSS 超分 √ · FSR3 帧生成 √
== 启动器根: <启动器安装目录> ==
   ZenlessZoneZero Game      (ZenlessZoneZero.exe)       depth=2 score=6   DLSS 帧生成 √ · DLSS 超分 √
游戏 收录条目 能力 说明
鸣潮 Wuthering Waves Game(Client-Win64-Shipping.exe) DLSS 帧生成 ✓ · DLSS 超分 ✓ · FSR3 帧生成 ✓ 本体原生三支持(FSR3 在 exe 旁,DLSS 系在 Engine\Plugins)
绝区零 ZenlessZoneZero Game(ZenlessZoneZero.exe) DLSS 帧生成 ✓ · DLSS 超分 ✓ 不注入:内核反作弊 + 原生已有帧生成
CP2077 Cyberpunk 2077(REDprelauncher.exe) DLSS 帧生成 ✓ · 超分 ✓ · FSR3 ✓ 代理(winmm.dll)仍装着,条目恢复显示
米哈游启动器根 — — 不再被收录(启动器根 + 注入残留已清理)

注:<游戏安装目录>\SkyrimSE 是已删游戏,忽略清单同步清理。


⑪ 2.0.6:引擎内嵌类游戏识别 + 扫描自动刷新(2026-09-13)

用户反馈:新装的原神怎么都扫不出来;识别不能实时更新,每次都得手点「扫描游戏」。

修复一:Unity 数据目录签名 —— 原神漏判的根因

门禁此前只认 UnityPlayer.dll / GameAssembly.dll。原神(YuanShen.exe 411 MB)把 Unity 引擎静态编进主程序, 根目录根本没有 UnityPlayer.dll,结构特征一个都不沾 → 整款游戏被判定为"不是游戏"而丢弃。

  • 新增签名 <游戏名>_Data\globalgamemanagers(Unity 独有文件,工具类目录造不出来)
  • 新增签名 mhypbase.dll(米哈游引擎基座,原神 / 星铁 / 绝区零通用)
  • 顺带把 HoYoKProtect.sys / mhyprot* 计入反作弊特征,喂给注入风险提示

修复二:FSR3 组件名单收口

绝区零用的是 amd_fidelityfx_framegeneration_dx12.dll,而名单里只写了 amd_fidelityfx_dx12.dll ——「普通目录探测」与「UE 插件探测」两处各写一份名单,两边都漏。现收口为单一常量 (FgDllNames / UpDllNames / FsrDllNames),绝区零正确显示 FSR3 帧生成 ✓。

修复三:识别不再依赖手点按钮

  • 启动即后台静默扫描一遍(不阻塞界面,只打一行汇总日志)
  • 窗口重新激活时按需重扫:距上次扫描完成 ≥10 分钟才扫;游戏运行中不抢磁盘 IO
  • 汇总日志列出新增条目,例:自动扫描完成:游戏目录 12 个,其中集成 DLSS 帧生成 4 个;新增 Genshin Impact Game

修复四:禁止把代理装进启动器根目录

<启动器安装目录> 这类目录是装游戏的容器(下挂 games\,内含原神/绝区零/星铁)。 此前被误注入 —— 一次注入同时作用于容器下所有游戏,且它们全是内核反作弊。现在 Install() 直接拒绝并提示改选游戏本体。

实测(tools/scan_mirror_check.py)

== 启动器根: <启动器安装目录> ==
   ZenlessZoneZero Game      (ZenlessZoneZero.exe)  depth=2 score=10  DLSS 帧生成 √ · DLSS 超分 √ · FSR3 帧生成 √
   Genshin Impact Game       (YuanShen.exe)         depth=2 score=0   无帧生成组件
== 期望命中自检 ==
    OK   <启动器安装目录>\games\Genshin Impact Game
    OK   <启动器安装目录>\games\ZenlessZoneZero Game
    OK   启动器根未被收录(应无): 无
游戏 收录条目 能力
原神 Genshin Impact Game(YuanShen.exe) 无帧生成组件(本体原生无 DLSS/FSR3,属事实非 bug)
绝区零 ZenlessZoneZero Game(ZenlessZoneZero.exe) DLSS 帧生成 ✓ · DLSS 超分 ✓ · FSR3 帧生成 ✓(本次修正)
鸣潮 Wuthering Waves Game(Client-Win64-Shipping.exe) DLSS 帧生成 ✓ · DLSS 超分 ✓ · FSR3 帧生成 ✓
CP2077 Cyberpunk 2077(REDprelauncher.exe) DLSS 帧生成 ✓ · DLSS 超分 ✓ · FSR3 帧生成 ✓

原神本体不带 DLSS / FSR3 的 DLL(原生无帧生成),所以能力列显示"无 DLSS 帧生成"是事实而非缺陷; 它仍会被收录、被「一键优化」的驱动层设置覆盖,需要注入时再点「开启」。


⑫ 2.0.7:忽略清单可以放行了 + 关闭窗口询问(2026-09-13)

用户反馈:2.0.6 装完,原神还是识别不出来;另外希望关窗口时能选"退出还是最小化"。

真凶:不是没扫到,是被忽略清单静默挡掉

日志写着 扫描完成:识别 11 个游戏目录,列表却只有 10 个 —— 少的那个正是原神:

C:\ProgramData\Fluxion\games.json
ignored: ["<游戏安装目录>\SkyrimSE", "<启动器安装目录>\\games\\Genshin Impact Game"]

扫描扫到了原神,合并层按 ignored 把它滤掉了。而这份清单:

  • 没有任何 UI 入口能把它捞回来(只能改 games.json)
  • 文件在 C:\ProgramData,由提权进程创建,普通权限连改名都拒绝访问(实测 WinError 5)

即:误点一次「移除选中」= 这款游戏永久消失,用户视角就是"识别不出来"。静默丢弃 + 无法恢复,两个都得治。

修复一:忽略清单可见可恢复

  • 帧生成页新增 「忽略清单」 按钮:列出全部被忽略目录 + 磁盘现状 (可收录 · YuanShen.exe / 目录不存在(可放行清理) / 目录在,但未通过游戏性判定)
  • 支持放行选中 / 全部放行,放行后自动重扫,游戏当场回到列表
  • 扫描完成时不再静默丢弃:日志追加 >>> 注意:有 1 个已扫到的游戏目录在忽略清单里、未显示:Genshin Impact Game(G:\...) → 点「忽略清单」可放行

修复二:关闭窗口时询问(要的正是这个)

点右上角 X 现在弹框二选一,样式与主界面一致:

  • 最小化到通知区域图标(后台继续运行) ← 默认
  • 退出程序(联动与告警一并停止)
  • 勾「记住我的选择,不再询问」后不再弹;想恢复询问,到「关闭窗口时询问」开关里改回(该开关现位于 设置 → 窗口与托盘,v3.2.1 起搬入)

细节:系统关机 / 任务管理器结束时一律不弹框(弹了会卡住关机流程),直接收进托盘; 托盘的「退出」菜单永远直接退出。偏好存 config.json 的 ui.closeAsk / ui.closeToTray。

修复三:顺手修掉一个静默错改配置的老 bug

配置写回靠 IndexOf("\"services\"") 定位节,而 gameAware.remoteApps 里恰好有 "services": ["GameViewerService"] —— 于是**「服务优化」的开关被写到了「CPU 调度」节上** (想关 services.enable,结果写动了 scheduler.enable),而且全程不报错。

  • 节定位改为按花括号深度 = 1 判定顶层节 + 键与 { 之间只允许冒号/空白(FindSectionBlock)
  • 布尔/字符串/整数三种写回合并到同一处 WriteConfigKey,不再各写一份定位代码
  • 写回前先整份解析一遍:写坏宁可拒绝(2026-09 有过一次配置文件被写坏、全部配置静默回默认的事故)

顺带解决"新键存不下来":安装包对 config.json 用的是 onlyifdoesntexist(升级不覆盖用户那份), 所以老安装里没有 ui 节 —— 现在节缺失会自动整节追加、键缺失会自动补键。

新增自测脚本 tools/cfg_write_mirror_check.py

配置写回是文本手术,编译通过 ≠ 写回正确。脚本对着真实 config.json 断言 6 件事: services 节不落在 gameAware 内、改 services.enable 不动 scheduler.enable、无 ui 节能整节追加、 节在键缺能补键、旧配置逐键补写、改 power.enable 不动 power.gameAwareSwitch。

这个脚本在本次开发中直接抓出了一个真 bug:补键函数的返回值不含结尾 }(调用方接回去), 但校验时只验了残块 → 永远验不过 → 补键静默失效。

实测

[1] services 节定位 ...  是否误落在 gameAware 内部: False
[2] 改 services.enable -> replaced | services.enable = False | scheduler.enable = True
[3] 无 ui 节时写入 -> appended-section | ui = {'closeAsk': False}
[4] 节在键缺 -> inserted-key
[5] 旧配置(无 ui) 逐键补写: appended-section / inserted-key
[6] power.enable -> replaced | gameAwareSwitch = True
RESULT: PASS

⑬ 2.0.8:修掉 2.0.7 自己引入的 bug —— 「忽略清单」按钮压根没建出来(2026-09-13)

用户反馈:2.0.7 装上了,原神还是识别不出来。

真凶:这次的 bug 出在 2.0.7 自己身上

2.0.7 的日志明明已经给出了答案,甚至给出了操作指引:

[11:56:35] 扫描完成:识别 11 个游戏目录
[11:56:35] >>> 扫描完成:游戏目录 10 个,其中集成 DLSS 帧生成 4 个
[11:56:35] >>> 注意:有 1 个已扫到的游戏目录在忽略清单里、未显示:Genshin Impact Game(<启动器安装目录>\games\Genshin Impact Game)  → 点「忽略清单」可放行

但 Ui.cs 里这一行:

FlatBtn btnAddGame, btnDelGame, btnIgnored;

btnIgnored 只声明、从没 new、也从没进过任何 Buttons(...);配套的 ShowIgnoredDialog() 全文件没有任何调用。 结果就是:按钮在界面上根本不存在,而日志让用户去点它。—— 编译通过、四个静态校验器全 PASS、功能静默消失。

修复

  • btnIgnored 实例化并接线 Click -> ShowIgnoredDialog();按钮排布改两行 (FlowLayoutPanel 设的是 WrapContents = false,6 个按钮塞一行会在 1056 宽的窗口里溢出被裁掉):
    • 第一行:开启选中 / 关闭选中 / 添加游戏… / 移除选中
    • 第二行:运行诊断 / 忽略清单
  • 「移除选中」补二次确认——它就是原神进忽略清单的来源,且是列表里唯一有持久副作用的动作, 确认框里直接写明"不会删文件 / 怎么恢复收录"

工具链补漏:orphan_check.py 从 2 类检查扩到 4 类

这个 bug 能出厂,是因为校验器只覆盖了「局部控件建了就扔」和「分区没挂上」, 没覆盖类字段和私有方法。新增:

新检查 含义
DEAD-FIELD 控件/组件类型的类字段只声明、从未赋值 → 控件不存在
DEAD-METHOD 方法定义了但全文件没有任何引用 → 入口没接线(用"总出现次数 ≤ 1"判定,委托传参也算引用)

验证方式:拿一份把本次修复回退掉的 Ui.cs 副本跑新校验器,两条都被抓出来:

fields scanned : 49      methods scanned: 63
RESULT: 2 suspect item(s)
  - Ui.cs:541   DEAD-FIELD -> FlatBtn btnIgnored 只声明、从未赋值(控件不存在,功能静默消失)
  - Ui.cs:1755  DEAD-METHOD -> ShowIgnoredDialog() 定义了但全文件没有任何引用(入口没接线)

教训:"声明了"不等于"存在"。自绘 UI 的失效模式是"代码在、界面没有", 而编译器对未赋值的字段、未被调用的私有方法一声不吭 —— 必须由静态检查兜住。


⑭ 2.0.10:纠正 2.0.9 的错误结论 + 「换入口重试」+ 忽略条目不再消失(2026-09-13)

用户反馈:我就要用该插件的功能,用 DLSS 的帧生成,这样延迟会低一点也不会卡。不要用 fsr。 以及 自动扫描游戏做的还是不够好,原神还是识别不出来。

🔴 先纠正 2.0.9:本工具不是"把 DLSS 帧生成转成 FSR3"

2.0.9 那一版我写错了一个根本事实,还把它固化进了界面文案。真实情况:

事实 说明
本工具接的是什么 社区项目 dlssg_for_sm86(github.com/sdli1995/dlssg_for_sm86):给 Ampere/Turing 补一份 SM86/SM75 内核,并内嵌 NVIDIA 自己的 DLSS-G 运行时
它跑的是什么管线 NVIDIA 原生 DLSS 帧生成(含 2X/3X/4X),不是 FSR 转发。ini 里 Router=SM86 指"路由到 SM86 内核"
「官方不支持」的含义 NVIDIA 官方确实不给 20/30 系开放 DLSS 帧生成(GpuSupportsDlssFg() 判断的就是这一层)—— 但官方不支持 ≠ 用不了,本工具存在的意义正是绕开这条
那"注入后还是 FSR"是怎么回事 代理没被激活:入口 DLL 只被游戏加载、从未被调用时,代理永不初始化,游戏内自然只剩它自带的 FSR 帧生成

铁证是项目自己的文件头注释与源码地址:接管游戏对 nvngx_dlssg.dll 的加载请求,换成补齐 SM86/SM75 内核的运行时; 代理容器 source\version.dll(15.7 MB)与 source\altnative\{winmm,dxgi,dinput8,winhttp}.dll 体积一致, 正是该项目 0.2.4 的产物。感谢用户坚持 —— 上一版结论是错的。

先例:CP2077 对 version.dll 只加载不调用,代理延迟初始化永不触发(DLL 在进程里、零日志、FG 无效), 换成 winmm.dll 后立即激活,实测 135 → 190 帧。二游大概率是同一类问题,所以这版加了「换入口重试」。

改动一:新增「换入口重试」(Dlssg.SwitchEntry / NextEntry)

  • Install 拆成 Install(自动选入口)+ InstallAt(指定入口);新增 EntryOrder / EntryUsable / NextEntry
  • 按钮点一下:卸掉当前入口 → 用下一个可用入口重装(跳过源文件没下载、或入口名被非本工具 DLL 占用的)
  • 换入口时保留 dlssg_sm86\logs\ 日志目录(Uninstall(g, keepLogs: true)),方便对比换入口前后的诊断
  • 「运行诊断」的两条提示改成可执行动作:代理已加载但未激活 → 点「换入口重试」 / 尚无日志目录 → 先启动一次游戏

改动二:被忽略的游戏不再从列表里消失(原神那个问题)

2.0.7/2.0.8 的做法是「被忽略就不显示,日志里提一句」—— 用户视角仍然是"识别不出来"。 现在 MergeScan 保留被忽略条目并打上 Ignored 标记:

位置 行为
游戏列表 灰显 + [已忽略] 前缀,状态列写「已忽略(选中点「开启选中」即恢复收录)」
「开启选中」 命中忽略态时先自动放行(UnignoreDirs)再继续开启流程,不需要用户去翻 ProgramData 里的 json
「移除选中」 已在忽略清单里的条目直接拒绝(本来就在忽略清单里,无需再移除)
「换入口重试」 忽略态同样先拦下,提示先「开启选中」恢复收录
底部统计 分开算「可接入 N 个」与「被忽略 M 个(列表里灰显)」
扫描日志 游戏目录 11 个(可接入 10 个)… + 注意:有 1 个目录在忽略清单里(列表中已灰显)

教训:"不显示"等于"不存在"。凡是会被隐藏的状态,都必须同时给出可见的标记与一键恢复的入口。

改动三:注入前的收益/风险评估重写

去掉两条错误判据(HasFsr3 → 注入零收益、显卡不支持 DLSS FG → 注入也只能得到 FSR3),改为:

判据 维度 结论
GachaWords 命中 反作弊 内核反作弊(mhyprot / HoYoKProtect / ACE)→ 封号风险,最高优先级;社区已记录 ACE 直接拦截代理 DLL 加载的案例
!g.HasFrameGen 能力 目录里没有 nvngx_dlssg.dll / sl.dlss_g.dll → 没有可接管的调用,装了不生效
GpuSupportsDlssFg() == false 路径 走的是社区补内核方案(非官方),可能选项不出现 / 崩溃 / 画面异常,NVIDIA 更新后可能失效 —— 但不是"没用"

改动四:帧生成页常驻说明重写(HardwareFgNote)

写清三件事:① 跑的是 NVIDIA 原生 DLSS-G,不是 FSR;② 生效判据 = 游戏内出现「DLSS 帧生成」+ dlssg_sm86\logs\*.jsonl 有 runtime_redirect / feature_created / evaluate; ③ 只有 FSR 选项时该做什么(换入口)。并明确提示:帧生成只提高显示帧率,不会降低延迟,想要低延迟请开 Reflex 并保证基础帧率。

改动五:修掉静态校验器的一处真实缺陷(可空返回类型)—— 2.0.9 的有效部分

static_check.py 的 MEMBER_RE / RE_METHOD_DECL 与 orphan_check.py 的方法白名单都不认可空标记 ?, 于是 public static bool? GpuSupportsDlssFg() 整条不被登记,引用处被误报 UNRESOLVED, DEAD-METHOD 检查也会静默失效。补上可选 ? 后:

修复前:RESULT: 1 issue(s)  - UNRESOLVED Dlssg.GpuSupportsDlssFg (referenced in Ui.cs)
修复后:RESULT: PASS - no static issue found

验证修复没有放松检测:把 Dlssg.HardwareFgNote() 改成不存在的名字跑,仍报 UNRESOLVED(exit 1);还原后 PASS。

教训一:校验器自己也会漏。出现新语法(可空类型、泛型…)时先确认校验器的正则认得它,否则"全绿"是假的。 教训二:别把"官方不支持"写成"技术上做不到"。差之毫厘,用户照着做就会得出错误结论。

改动六:GPU 能力检测(2.0.9 引入,本版保留)

不依赖 WMI(本机安全中心禁了系统级工具),改读注册表 HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-…}\0000\DriverDesc,并跳过虚拟显示适配器。实测:

0000 => NVIDIA GeForce RTX 3060 Ti
0001 => GameViewer Virtual Display Adapter   ← 跳过
0002 => Honor Virtual Display Device         ← 跳过

⑮ 2.1.0:d3d12 复合入口 —— 反作弊游戏唯一能进的那条路(2026-09-13)

背景:绝区零、鸣潮在 2.0.x 里怎么换入口都不生效。上游 issue #111 给出了原因,而且是实测结论:

绝区零的反作弊会识别到 version.dll 等的名词,从而使其失效。

issue #387(同日,明日方舟:终末地)5 个入口全部重现了同样的症状 —— 5 个入口要么报"非法劫持模块"、要么能进游戏但菜单里没有选项。这是国产二游 ACE 类反作弊的通用行为,不是操作错误。

路线:d3d12.dll 拦不掉

d3d12.dll 是 DX12 游戏必须加载的系统库,反作弊没法按文件名拉黑它。但上游 dlssg_for_sm86 只发 5 个入口(README 原话:"五种已签名代理入口保持不变"),没有 d3d12 —— 所以本版自己造一个。

实现:3,584 字节的纯 PE 代理(内嵌在 exe 里)

表 内容 作用
导出表 系统 d3d12.dll 的全部 18 个函数 → 转发到 d3d12_orig.dll 同名函数 对游戏完全透明,D3D12 调用照常落到微软签名副本上
导入表 dinput8.dll!DirectInput8Create Windows 加载代理时必须先解析它 → 自动把上游的 dinput8.dll(15.6 MB,真正的帧生成运行时)拉进进程

安装时落三个文件(三件套,缺一件游戏启动即崩):

d3d12.dll         3,584 B   代理(本工具内嵌字节:导出转发 + 导入载体)
d3d12_orig.dll  146,176 B   转发目标 = 本机 C:\Windows\System32\d3d12.dll 的副本
dinput8.dll  15,666,496 B   载体 = 上游 altnative/dinput8.dll(一个字节没改)

与社区流传的"抖音那条路"(ASI 加载器注入)的差别:那边需要一个第三方 ASI 加载器把 DLL 注进去;这边靠 Windows 自己的导入解析机制把 dlssg 拽进来,不需要加载器,也不需要编译器。

改动清单

# 改动 位置
1 D3d12Entry / D3d12Orig / D3d12Carrier 三个常量 + 内嵌代理字节 D3d12ProxyBlob Dlssg.cs
2 EntryOrder():反作弊游戏把 d3d12 排在所有入口之前 Dlssg.cs
3 InstallD3d12():写三件套(占用先备份)+ 写配置 + 假成功校验 Dlssg.cs
4 VerifyD3d12() + PeImports():解析 PE 导入表,确认"代理↔载体"真的接上 Dlssg.cs
5 ScanResidue():扫被反作弊改名隔离的残留(version.dll.1271877294) Dlssg.cs
6 Uninstall():d3d12 三件套 + 残留一起清(d3d12_orig.dll 要先与系统原件比对长度才删) Dlssg.cs
7 IsAntiCheat():反作弊名单收口为唯一来源(GachaWords) Dlssg.cs
8 帧生成页:d3d12 状态行 + 「清理残留」按钮 + 「反作弊游戏自动改用 d3d12」开关 Ui.cs
9 配置项 dlssg.antiCheatD3d12(默认 true) Core.cs / config.json

修掉的三个真实缺陷(都藏在旧的部署包里)

级别 问题 后果
🔴 install.cmd 拷贝 dxgi.dll,包里却是 dinput8.dll d3d12.dll 的导入解析失败 → 游戏启动即崩
🟡 部署用 ini 缺 [Compatibility] 段,且写了上游不存在的键 EvaluateEvery=120 参数静默失效
🟡 make_d3d12_proxy.py 文件头注释仍写"静态依赖 dxgi.dll" 文档与实现相反

教训与 2.0.6 的 FSR 组件名同源:同一份清单写在两处,必然漏一处。 现已加生成期守卫 —— 拷贝清单与文件常量不一致时,脚本直接报错退出。

实测验证(本次交付)

系统 d3d12.dll 导出 18 个 == 代理导出 18 个(无缺失、无多余)
18 个导出全部转发到 d3d12_orig.dll
代理导入表 = ['dinput8.dll']  ← 闭合
内嵌字节 == deploy 包字节(3,584 B,可解析为合法 PE)
临时目录落盘演练:三件套齐全,导出 18 / 导入 dinput8.dll

风险(不变,但说得更清楚)

🟡 绕开的是文件名识别,不是反作弊本身。注入未签名 DLL 仍是违约行为,理论上可封号,封的是你的账号,工具无法撤销。 🟡 若反作弊连 d3d12.dll 都拦(表现为游戏目录下始终不出现 dlssg_sm86 日志目录),那就是社区无解的边界 —— issue #81 的结论仍然成立。 🟢 「关闭选中」可一键回滚(含 d3d12_orig.dll 与改名残留)。

⑯ 2.1.1:显卡名伪装 —— 第 1 层"菜单露出"的一键开关(2026-09-13)

真机首测发现的问题(2.1.0 部署到绝区零/鸣潮后):鸣潮代理激活成功(日志 configuration + ngx_driver_connected,ACE 没拦 d3d12 链路),绝区零被 HoYoKProtect 掐在加载前(连日志都没有)。但两个游戏菜单都没有帧生成开关。

根因:菜单露不露出开关由游戏第 1 层判定决定——游戏先问"硬件支不支持",不少游戏这一问读的是注册表里的显卡名,30 系名字直接被判"不支持",开关根本不渲染。dlssg 代理只接管第 2 层(真跑帧生成),够不到第 1 层。

改动(Dlssg.GpuDisguise + 帧生成页「伪装显卡名 / 还原显卡名」按钮):

  • 扫描注册表三处分支(显示类子键、Enum\PCI、Control\Video)里所有含真名的字符串值
  • 一键全部替换为 NVIDIA GeForce RTX 4070;原值完整备份到 ProgramData\Fluxion\disguise.json
  • 再点一次按钮即从备份逐条还原;状态行常显"真名 / 伪装中"
  • 全局可见(设备管理器/DXDiag/WMI 都显示 40 系名),杀软可能告警,驱动更新会重置注册表

诚实声明:能否骗过具体某个游戏取决于它读不读这些键值——NVAPI 走内核 ioctl 的判定改注册表无效。这是实验性功能,效果要真机验证(鸣潮是首选目标;绝区零在"游戏侧请求帧生成"那一步就断了,伪装无意义)。

产物:installer_out\Fluxion-Setup-2.1.1.exe

⑰ 2.2.0:XeSS 多帧生成 —— 绝区零 / 鸣潮 一键部署(2026-09-13 实机跑通)

背景:2.1.x 的 d3d12 复合入口在绝区零被 HoYoKProtect 拦死(三次不同入口:version.dll 按名拦 / dinput8 链 / 签名版,全挂 11008);鸣潮的 dlssg 直接安装后,在 DX12 命令列表阶段崩溃。社区整合包(B 站教程)在两款游戏上实机跑通,本版直接固化该方案。

游戏 入口 配置 实测
绝区零 d3d12.dll(OptiScaler 25.6MB) 伪装 RTX 5090 · 上采样 DLSS · 帧生成 DLSSG→XeFG(2-6X) 绕开按名拦截,帧生成可用
鸣潮 dxgi.dll(OptiScaler 0.7.7) 5090 伪装 · dlss 上采样 · DLSSG 进出(含 dlssg_sm86 子目录) 帧生成可用

用法:帧生成页 → 游戏列表选中游戏 → 「XeSS 多帧生成」区点部署;点卸载完整还原(覆盖的游戏自带件自动备份恢复)。游戏内 F10 呼出 OptiScaler 面板。

资源包(约 850 MB,不内嵌 exe):%ProgramData%\Fluxion\xess-pack\{zzz,wuwa}\。换机部署时把资源包放入该目录即可。

关键机制:绝区零用 d3d12.dll 名(DX12 游戏必加载的系统库名,反作弊无法按名拦);帧生成走 FGInput=DLSSG → FGOutput=XeFG(游戏原生 DLSS 帧生成调用被拦截后由 Intel XeSS FG 引擎执行)。

踩过的坑(写进代码里):

  • 鸣潮 exe 在 3 层目录(Client\Binaries\Win64),识别需递归查找 —— 首版只找 1-2 层漏了
  • 清理 mod 时误删游戏自带件(绝区零的 nvngx_dlss.dll)导致黑屏 —— 卸载逻辑改为备份清单制:安装前备份被覆盖文件,卸载时恢复
  • DLSS 5 神经渲染(nvngx_dlssnr)在老卡上开销大,本方案只用其帧生成路径

⑱ 2.3.0:内置游戏启动器(图标 + 双击启动)+ 伪装说明修正(2026-09-13)

启动器(帧生成页 · 游戏列表):

  • 列表左侧显示游戏图标(提取 exe 图标,按路径缓存)——解决英文名难辨认
  • 双击列表项直接启动游戏;另加「启动游戏」(推荐按钮)/「打开目录」按钮
  • 直接跑游戏 exe,绕开官方启动器的文件完整性校验弹窗(鸣潮这类装了 mod 的游戏必需)
  • 游戏列表来源:常见安装目录 + Steam 库 + 注册表卸载项(第三方安装的游戏多数有)+ 启动器根目录 + 自定义目录 + 手动添加,覆盖绝大多数安装位置

显卡名伪装说明修正(研究结论,来源见下):

  • 任务管理器 / 设备管理器的 GPU 名读的是注册表设备描述符(Enum\PCI\DeviceDesc、Class\...\DriverDesc)——改这些 + 重启电脑后两者同步显示新名字
  • 之前"改了没变"是因为没重启电脑;OptiScaler 的 SpoofedGPUName 只在游戏进程内生效,系统层永远显示真名(两回事)
  • 工具已把「显卡名伪装」的提示改为"重启电脑后任务管理器 / 设备管理器同步显示"

⑲ 2.4.0:封面卡片游戏库 + 自定义目录收录 + 技术说明重写(2026-09-13)

界面(参考 GamesDeck / Playnite 封面库):

  • 游戏列表从 ListView 换成封面卡片网格:每张卡片 = 封面/图标(132px)+ 名称 + 状态徽章,单击选中(蓝框高亮)、双击启动
  • 封面优先取 %ProgramData%\Fluxion\covers\<游戏名>.png(自己放图即自动使用,GamesDeck 式个人封面库),没有则用 exe 图标

全电脑游戏识别:

  • 新增「添加目录…」按钮:把任意目录(如 <个人目录>)加入扫描根,写入 config 的 dlssg.extraRoots,下次扫描自动包含
  • 自定义目录采用宽松收录:跳过游戏性判定(RPG Maker / Ren'Py 这类引擎特征不在名单里,但用户指定了目录就是要它进库),只保留工具名黑名单与安装器排除。实测 <个人目录> 16 套里收录 14 套(另 2 套是存档目录/无 exe)

技术说明重写(帧生成页常驻文案):写明实际技术栈与三层链路 —— OptiScaler(github.com/optiscaler/OptiScaler)+ dlss-unlocked(ShyVortex)+ dlssg_for_sm86(sdli1995)+ fakenvapi + Intel XeSS-FG; ① 伪装 RTX 5090 让菜单出现开关 → ② 拦截游戏的原生 DLSS 帧生成调用 → ③ 由 XeSS 帧生成引擎实际插帧(2-6 倍)。

移除:「伪装显卡」功能整体删除(OptiScaler 插件已内置 GPU 伪装,系统层伪装不再需要)。

⑳ 2.4.1:封面卡片三处修复(2026-09-13)

用户反馈三条,逐条定位到根因并修:

反馈 根因 修法
卡片图好糊 取图标用的是 Icon.ExtractAssociatedIcon(),它只返回 32×32,拉到 132 的卡片上就是一团糊 改 PrivateExtractIcons 主动要 256×256,在多个候选尺寸里取实际像素最大的一张;缩放统一走 GameIcon.Scale()(HighQualityBicubic + TileFlipXY)。实测:鸣潮/绝区零/2077/极限竞速 + <个人目录> 全部游戏 32×32 → 256×256
页面中部整片空白、只显示底下一个分区、还多出横向滚动条 ① Pg 容器没开双缓冲,而子控件全开了 —— AutoScroll 滚动是位图位移,父子缓冲不同步就留空白带;② Reflow() 在忙时把再入请求直接丢弃,尺寸变化永久丢失,布局停在半成品;③ 没给竖向滚动条预留宽度,竖条一出现就把内容挤出横条 Pg 开双缓冲 + 重排改「挂起标记 + 迭代到收敛」+ 按竖条可见性预留宽度;OnScroll 强制整页重绘;封面网格换双缓冲子类 BufferedFlow
鸣潮「可接入」到底是哪个插件 / 绝区零用的什么方案 卡片状态只写了泛化词:可接入 = 目录里有 DLSS 帧生成组件(不代表装了任何插件);绝区零那条 已开启(d3d12.dll) 是 state.json 里的旧记录在作祟(15:34 装的 3.5 KB 代理早被 XeSS 套件的 25.6 MB d3d12.dll 取代) 卡片状态改为读实际方案:XeSS 多帧生成(d3d12) / XeSS 多帧生成(dxgi);新增 PruneStale() 扫描前清理失效记录(判据是体积,不是「文件在不在」);统计口径不再用「可接入 N 个」而按方案分列

顺带加的护栏:这两个游戏装了 XeSS 套件时,「关闭选中」会拒绝执行并提示改用「XeSS 多帧生成 → 卸载」——两者入口文件同名(d3d12.dll / dxgi.dll),混用会留下装了一半的状态(旧的 libxess、fakenvapi 还在,游戏可能起不来)。

封面库:数据目录 covers\ 现在会在启动时自动创建;把图片按「卡片标题.png」放进去即自动用作封面,没放则用 exe 图标。

㉑ 2.5.0:界面重排 + 游戏启动器能力移植(2026-09-14)

一、界面重排(用户三条反馈,均已定位到代码级根因)

反馈 根因 修法
页面能往上滑出空白 滚动区由子控件边界推导,长文被窄宽测量过会撑出超长内容 Pg 双缓冲 + 重排用自己算出的内容高夹紧 + 重排后逐分区重绘
两个滑条太麻烦 帧生成页 5 分区 / 8 个文字块;卡片网格还自带内层滑条 帧生成页瘦身成「运行时状态 + 目标游戏下拉 + 操作按钮 + 参数」;游戏库、说明各自独立成页;网格关掉内层滚动(整页单滚动条)
点卡片强制跳回最上面 点击 → 状态行文案变 → Sec.Resync() → 整页重排;且重排时 maxSy 读 WinForms 延迟更新的 DisplayRectangle.Height,读到 0 就把滚动归零 状态行改固定高度(不参与高度推导);Reflow() 内容没变直接返回;maxSy 改用自算内容高

页面从 5 页扩到 7 页:仪表盘 / 性能优化 / 帧生成 / 游戏库 / 实时监控 / 说明 / 设置。 页面索引改为常量 P_DASH…P_SET(避免插页时裸数字错位)。长文(技术栈、三层原理、用法判据、使用须知、反作弊、封面库、数据与隐私)全部移到「说明」页。

二、移植用户自研启动器 GameLauncher 的能力(Python 逻辑改写为 C#,不引入任何运行时依赖)

能力 来源 效果(本机实测)
Steam 官方游戏名 appmanifest_*.acf 「the dreamland of I…」→ 「尸姬之梦」;原神/绝区零/鸣潮 也拿到中文名
全部 Steam 库 libraryfolders.vdf + 逐库扫 acf 补回第二个 Steam 库(D:)里漏掉的作品
WeGame 注册表卸载项 + rail_apps 识别 新增 卡拉彼丘 / 无畏契约(此前完全扫不到)
Epic / 育碧 / EA / 战网 / GOG 各平台注册表与 manifests 按平台归类并可筛选
主程序挑选 名称匹配 > 提示名 > UE Shipping > 体积 修掉 CS2 被 18.9MB 的 source1import.exe 挤掉(现为 cs2.exe);新增工具类 exe 黑名单(ffmpeg / source1import / vconsole / TinyDL…)
Steam 官方封面 官方 CDN library_600x900.jpg 卡片显示官方竖版封面;无封面时按游戏名哈希生成渐变卡(含 exe 图标)
库管理 收藏 / 私密 / 隐藏 / 重命名 / 最近游玩 「游戏库」页工具条:搜索 + 平台分类(含收藏/私密/二次元)+ 排序;私密不进「全部」,隐藏需勾选才可见

扫描结果:11 款 → 16 款(Steam 11 + 本地 3 + WeGame 2),与用户启动器一致。

三、新增文件与数据结构

  • src\Lib.cs:库标记存储(library.json,与 games.json 分开,不动现有忽略清单逻辑)、Steam/Epic/育碧/EA/注册表扫描、封面管线(下载 + 渐变生成 + 缓存)
  • DlssgGame 增字段:Platform / AppId / Genre / Id / Favorite / Private / Hidden / LastPlayed
  • %ProgramData%\Fluxion\library.json:收藏 / 私密 / 隐藏 / 重命名 / 平台与 appid;covers\:封面缓存
  • 网络只访问 Steam 官方域名(白名单校验),走「设置」页配置的代理;可在设置里关闭联网封面

四、封面库用法:把图片按「卡片标题.png / .jpg」放进 covers\ 即自动生效;或在游戏库页点「获取封面」联网补齐。

㉒ 2.5.1:右键菜单 + 封面比例 + 网格空白 + 联网封面(2026-09-14)

用户五条反馈,逐条处理:

反馈 处理
底部那排操作按钮要翻到最下面才能用 全部搬进卡片右键菜单(启动 / 打开目录 / 收藏 / 重命名 / 私密 / 隐藏 / 帧生成 / 移除);页面底部整块「选中游戏」区删除,改为顶部一行状态 + 「启动选中」按钮
游戏太多会生成一大块白色区域 CardGrid 新增:每次布局都按当前宽度算出应有的行数并设置高度。原来只在外部按旧宽度算一次,首次布局/窗口变窄时行数偏大 → 网格留出大片空白
有的封面大小不匹配、违和 卡片封面区从 132×176 改为 132×198(正好 2:3),与 Steam 官方竖版封面(600×900 / 300×450)完全对齐;新增 CoverArt.Fit() 按比例裁切填满,不再出现留边
能联网获取封面吗 能。实测:Steam 封面 CDN(steamstatic)直连可用,3/3 成功;失败原因是原来只按配置单路走且静默吞异常。现改为直连/代理双路自动回退,失败原因写进日志。「按名称搜封面」用 steamcommunity,需代理开着
联动只对网游 本来就是:gameAware.gameProcesses 只有 CS2 / 无畏契约 / 三角洲,二游与 3A 不会触发远控暂停。设置页开关文案已写明这一点

设置页「帧生成下载源」改名「下载源与联网封面」,新增联网封面开关(关闭后只用本地图标与按名字生成的颜色卡)。


㉓ 2.6.0:视觉体系重做(圆角 + 深色主题)与封面管线修复(2026-09-14)

用户三条反馈:有的游戏没封面、边角太锋利(希望沿用《游戏启动器》的美术语言)、标题与页面一体性差。 逐条定位到代码级根因,并按"离屏渲染亲眼核对"的方式验证。

① 圆角体系(这是"边角太锋利"的正解)

游戏启动器的 token 是 --radius: 14px(卡片)/ 11px(按钮)/ 10px(输入)/ 999(胶囊)。本版把它移植成一等公民:

token 值 用在哪
Theme.RCard / RPanel 14px 游戏卡片、内容分区
Theme.RBtn 10px 按钮、侧栏导航胶囊
Theme.RField 10px 输入框、下拉框
Theme.RPic 10px 卡片封面

新增自绘控件(原生控件画不了圆角,这是根因):

  • CoverBox:圆角裁切的封面框(原来是 PictureBox,只能直角)
  • NavBtn:圆角胶囊导航项 + 左侧强调条
  • RoundField:圆角输入框宿主(TextBox 自身无圆角能力)
  • RoundCombo:圆角下拉框。原生 ComboBox 的箭头由系统绘制,深色主题下是刺眼白块, 还会破坏圆角观感 —— 自绘字段 + chevron + 用主题化菜单做下拉面板
  • RoundCheck:圆角复选框(同上,系统方框在深色下是白块)

配套:Theme.Round/FillRound/EdgeRound/SoftShadow/ClipRound 与 ThemedMenuRenderer (右键菜单、下拉面板、托盘菜单统一走主题色)。

② 深色主题(可选,一键切换)

Theme 的 350+ 处引用全部集中在 Theme.X,所以做成双调色板只需一个开关:

  • 浅色保持原有观感(输入底改极浅灰:纯白面板上的纯白输入框等于看不见边界)
  • 深色取自《游戏启动器》:底色 #0A0C12,强调色紫→蓝→青(#8A7CFF → #4DA3FF → #3DD6FF)
  • Windows 11 下调用 DwmSetWindowAttribute 让窗口圆角 + 标题栏/边框跟着主题着色 (CORNER_PREFERENCE=33 / CAPTION_COLOR=35 / TEXT_COLOR=36 / BORDER_COLOR=34, 旧系统调用失败会被吞掉,自动回退系统默认外观)
  • 入口:设置 → 界面主题。切换需重启程序(部分控件在构造期就固化了配色), 保存后弹框可选「立即重启」。配置写在 ui.theme,老配置由 InsertKeyBlock 自动补键。

③ 标题与页面一体化

  • 页面标题不再只躺在系统标题栏里。新增 HeadBox:大标题 + 副标题 + 细分隔线,每页一个
  • 系统标题栏文案简化为 Fluxion v2.6.0(页面名交给页内标题头承载)
  • 标题头高度固定,副标题更新只重绘不参与布局 —— 不碰 2.5.x 修好的"点卡片跳回顶部"那条链

④ 封面:为什么"有的没有封面"

实测(2026-09-14)对用户库里 4 个拿不到封面的 Steam appid 做多主机多路径穷举:

appid 2483190   library_600x900 404 / header 404 / capsule 404 …  library_hero 200 (250KB)  ← 有!
appid 3527290   同上 → library_hero 命中 (351KB)
appid 3548580   同上 → library_hero 命中 (219KB)
appid 4262610   全部主机 × 全部路径 均不可用(该 appid 在 CDN 上确实没有任何资源)

根因:旧代码只试了两条路径(library_600x900 → header),没有的永远没有。 改为按"竖版优先"依次尝试 7 条官方路径。

另外两项:

  • 横版图不再硬裁:比例与 2:3 相差过大(横版头图 / 宽幅 hero / 方形 logo)时, 改用模糊铺满做底 + 完整图居中(Spotify / Playnite 的通行做法), 解决"封面有点违和、大小不匹配";比例接近则仍直接裁切填满
  • 生成卡统一色系:无封面时从固定的 6 组渐变(紫→蓝→青家族,按名字哈希取一组)里生成, 配斜向高光 + 底部压暗 + 居中图标 —— 不会 16 张卡五颜六色各自为政
  • 手动兜底:卡片右键新增「设置封面…」(本地任意图片,拷成 <游戏名>.png 优先使用)、 「重新下载官方封面」、「打开封面目录」。「获取封面」也会在库内 appid 失败后按名字搜一个替代 appid 再试。

⑤ 验证方式:离屏渲染,肉眼核对(不只是"编译通过")

新增探针 StyleProbe(与源码同编,可访问 internal 类型),把真实 MainForm 的 每一页渲染成 PNG 再逐张核对。本版据此修掉两个"编译通过但观感不对"的问题:

  • 复选框文字被省略号截断(自绘方框比系统字形宽,预留宽度不够)
  • 浅色下输入框与白卡片分不清(Field 从纯白改极浅灰)
实测:浅色 / 深色 × 游戏库 / 仪表盘 / 设置页 四张渲染图逐张核对通过
封面多路径:4 个修复目标中 3 个成功(第 4 个 CDN 确实无资源 → 生成卡 + 手动兜底)

㉔ 2.6.1:重绘加固 + 生成卡重做 + 无封面游戏联网取图(2026-09-14)

用户反馈三条:侧栏出现黑框、文字重影、"像叠了好几层"、封面参差不齐、标题重复 + 旁边多了条滚动条。

① 「旁边那条滚动条」—— 直接原因确认并修掉

侧栏用的是 FlowLayoutPanel,它的 AutoScroll 默认为开。窗口偏矮(或侧栏内容变高)时, 它会在侧栏右侧冒出一条竖滚动条 —— 就是截图里"旁边那条"。侧栏本来就不该滚动,直接:

flow.AutoScroll = false;
flow.HorizontalScroll.Visible = false;
flow.VerticalScroll.Visible = false;

矮窗口(520px 高)离屏渲染复验:侧栏再无滚动条。

② 「黑框 / 文字重影 / 像叠了好几层」—— 按正确做法加固,但没有 claim 根因

这类现象是"控件自己的矩形没被画满、留下上一帧残留"。自绘控件确实必须遵守一条纪律:

一旦设置 UserPaint + AllPaintingInWmPaint,WinForms 不再帮你擦背景(WM_ERASEBKGND 被跳过), 控件必须把自己的整块矩形画满 —— 包括圆角之外那点角落。

所以本版给 9 个自绘控件(FlatBtn / NavBtn / RoundField / RoundCombo / RoundCheck / HeadBox / Sec / GameCard / CoverBox)统一在 OnPaint 开头铺满自身矩形,并加上 ControlStyles.Opaque (自己铺满就不需要父级再画一遍,同时减少闪烁)。

但要说清楚:我做了反向测试(故意移除 NavBtn 的背景填充再渲染), 离屏探针并没有复现出黑框 —— 因为 DrawToBitmap 走 WM_PRINT,仍会铺一次背景, 这类重绘撕裂它抓不到(这是探针的真实盲区,写在这里免得以后误信)。

所以这一条是"按正确做法落地",不是"已验证的根因"。同时补了两处能确认的重绘加固:

  • 窗体与 root / main / side / foot / content 容器统一开启双缓冲(Panel 的 DoubleBuffered 是 protected,通过反射设置)—— 拖窗、切页、缩放时的撕裂残影会明显减少
  • 侧栏不再有滚动条(见 ①)

如果装上 2.6.1 后仍能复现:请再截图,并顺便告诉我"鼠标动一下/缩放一下窗口后是否消失" —— 能消失就是重绘撕裂,不消失就是布局或绘制错误,两条线的查法完全不同。

③ 封面「参差不齐」—— 生成卡 v3(深色设计感)

v2.6.0 的生成卡是亮青色渐变 + 浮在中间的小图标,跟真实盒装封面(基本都暗调)摆在一起 就显得空、像贴纸。v3 改成:

  • 底色换成一族深色渐变(深靛/深蓝/深青/深紫,低明度),按名字哈希取一组 —— 同款游戏颜色恒定
  • 中央品牌色辉光把图标"托"起来
  • 图标画成 18px 圆角的磁贴(占宽 62%),带投影与 1px 亮描边 —— 看起来像真正的应用磁贴
  • 斜向高光 + 底部压暗收边

④ 无封面游戏:联网取图(新能力)

封面来源现在是三级回退:

级别 来源 说明
① Steam 官方 CDN 按 appid 试 7 种官方尺寸,实测直连可用
② 按名字搜 Steam appid 走 steamcommunity,需要代理
③ 联网搜图兜底 Bing 图片搜索,只取竖版大图;会访问任意 https 图片站

实现要点:

  • Bing 结果里图片直链的形式实测是 &quot;murl&quot;:&quot;https://…&quot;(JSON 被 HTML 转义过)。 用这个 HTML 实体当定界符,源码里连一个转义双引号都不用写 —— v2.6.1 头一版就是在这儿 写坏了正则,编译器报 CS1010 常量中有换行符。
  • 三重过滤:域名黑名单 + 最小尺寸(500×640)+ 竖版比例 0.58~0.78。 初版放宽到 0.95,结果"无畏契约"收到一张三格拼图宣传图 —— 收紧后这类东西被拦住。
  • 入口:卡片右键「联网搜寻封面」单款重抓;「获取封面」批量时作为最后一步; 「重新下载官方封面」也带这一步兜底。

实测(本机 16 款库):5 款原本没封面的游戏全部拿到真实主视觉 —— 原神(官方 600×900 竖版 key art)、绝区零(1.4MB)、鸣潮、卡拉彼丘、无畏契约。

⑤ 顺手:标题"重复"

页内标题头(HeadBox)显示当前页名,侧栏高亮同一项 —— 这是导航的标准形态,不是重复渲染。 真正会看起来"重复/重影"的是 ② 那个重绘问题。若确实希望页内标题头消失,说一声即可去掉。

㉕ 2.7.0:界面语言全面对齐《游戏启动器》(2026-09-14)

用户四条反馈:封面明显不适合做封面、左右不居中、边缘与字体锯齿严重、多处衔接割裂, 并要求继续参照 <个人目录>\GameLauncher。这一版按它的 CSS 逐条对齐。

① 卡片改成「单面设计」(这是"割裂"的主要来源)

启动器的 .card 是:封面铺满整张卡(object-fit: cover)→ 底部渐变遮罩 → 名称与平台压在图上。 我们原来是「上方封面 + 下方两行文字」的两段式 —— 两段之间天然有条接缝,整排看过去必然割裂。

现在改成:

项 做法(对应启动器)
封面 铺满整卡、cover 语义裁切(.card-img)
名称 压在封面底部,最多两行,超出省略(.card-name)
平台 / 状态 彩色小方块 + 「平台 · 状态」一行(.card-plat::before)
遮罩 底部 52% 高的深色渐变,保证任何封面上文字都读得清(.card-shade)
交互 悬停轻提亮 + 强调描边 1.6px;选中 2.6px;★ 收藏角标
结构 整张卡只有一个绘制面(无子控件)—— 顺带少了一类子控件重绘问题

尺寸从 132×198 提到 148×222(仍是 2:3),网格间距 14px。

② 顶栏不再套盒子

启动器的 #topbar 是 space-between:左搜索框、右排序,外面没有面板。 现在游戏库页也按这个来,并给 Sec 加了「工具条模式」:标题传空字符串即为工具条 —— 不画面板底与描边、不留标签列缩进;新增 Sec.Bar2(左控件, 右控件组…) 实现两端对齐。 卡片区同样不再套面板(启动器就是「标题 + 裸网格」)。

③ 封面适配:去掉模糊底,改回纯裁切填满

v2.6.0/2.6.1 对横版图用「模糊铺满 + 完整图居中」,结果上下两条模糊带非常显眼 —— 用户原话"这个明显不适合做封面"。现在一律按 object-fit: cover 裁切填满, 横版图纵向取景 42%(关键视觉通常偏上,取正中容易切掉角色头)。

④ 锯齿:圆角烘焙进位图 + 文字换 GDI

两个独立的根因:

  • 封面圆角:之前用 Graphics.SetClip(path) 裁切 —— 那是区域裁剪,不做抗锯齿,圆角边缘就是硬锯齿。 改为 FillPath + TextureBrush 把圆角烘焙进封面位图(边缘带 alpha 过渡),绘制时直接 1:1 贴图。
  • 文字:标题头等处用的是 Graphics.DrawString(GDI+ 默认渲染,明显发糊)。 改为 TextRenderer.DrawText(GDI,ClearType 亚像素抗锯齿)。4 倍放大实测:字缘有正常的亚像素过渡,不再是硬边。

⑤ 衔接:硬线一律换成两端淡出的渐变线

  • 侧栏与内容区之间的 1px 硬分隔线直接去掉(底色本身已有区分)
  • 标题头、页脚顶边:用 3 段 ColorBlend 的左淡出渐变线替代整条硬线
  • 页面标题头文字改用 GDI 渲染,与页面观感统一

⑥ 本轮修掉的两个自造 bug

  • Sec.Bar2 右侧控件组用的是 FlowLayoutPanel,首次排版时 PreferredSize 可能还没算出来, 高度为 0 → 整组控件"隐形"(表现:筛选下拉全不见了)。改为显式给高度。
  • 卡片 Margin 被设成 0:CellW 里假设了 14px 间距,于是卡片贴在一起、每行数量也算错, 连带"居中"失效。已恢复 Margin = 7(2×7 = 14 与 CellW 一致)。

⑦ 验证方式

离屏探针新增 geom 模式,直接打印网格真实几何(比看截图靠谱):

grid  Left=18 Width=862 ClientW=862 Padding.Left=26 CellW=162 Count=16
perRow=5 leftover=52 期望 Padding.Left=26  ->  卡片实际占 [33, 829](左右各留 33px = 居中)

⑧ 踩坑(写给自己)

Core.cs / Lib.cs 是 CRLF,Ui.cs 是 LF。用 newline='' 读写时我插入的 \n 会和文件原有的 \r\n 混在一起,导致后续多行锚点全部匹配失败(本轮为此浪费了两次来回)。 以后跨文件做多行替换前,先把换行统一再处理,写回时按原风格还原。

㉖ 2.7.1:封面来源治理 + 全息卡面 + 工具条底色修复 + 新图标(2026-09-14)

  • 工具条白补丁:Sec 的标签/按钮流底色写死 Theme.Panel(白),在工具条模式(透明、页面灰底)下就是一块块白补丁——按模式区分(盒式=Panel / 工具条=Bg)。
  • 卡片"上下两半"三连根因:①卡片遮罩 52% 高 + α234 太重 → 降到 40% / α208;②生成磁贴自带底部压暗与卡片遮罩叠加、图标正好压在遮罩中线上 → 去掉自带压暗、图标上移、缓存升 gen4;③steam.jpg 混着横版 library_hero/header(Forza 是风景照横幅)→ 新增 tools\cover_refetch.py:官方竖版优先 → Bing 竖版(0.58~0.78)兜底,实测修复 Forza 等;PEAK / Chill 无竖版资源,退回重新设计的生成磁贴。
  • 全息卡面:tools\cover_holo.py 用 create-holographic-card skill 的材质资产(spectral-lines / pearl / star-holo / micro-grain)+ material-api 配方(高饱和→彩虹 / 低饱和→银),把烫箔/星闪/眩光静态烘焙进封面;原图备份 covers_originals\。
  • 新程序图标:闪电 + GPU 芯片,紫→青渐变(AI 生成、裁水印、多尺寸 .ico);exe(/win32icon)与安装包同步,旧图标备份 icon\icon_old.ico。

㉗ 2.8.0:taste-redesign 全页面设计审计(2026-09-14)

用 redesign-existing-projects skill 的流程对 7 个页面逐页离屏渲染审计(浅色+深色),修复清单:

  • 深色模式白色滚动条(最扎眼的"抠图"残留):uxtheme DarkMode_Explorer 主题把 Panel / ListView / RichTextBox / ListBox 的原生滚动条和表头一起染黑(Win10 1809+,旧系统静默回退)。实现在 Theme.ScrollThemeTree,句柄创建即染色。
  • 性能优化页 ListView:FixedSingle 黑框 → 无框、字号 9→9.5(行距更松)、深色下表头/滚动条随主题。
  • 设置页代理输入框:原生方角 TextBox → RoundField 圆角输入框,与全套控件统一。
  • 实时监控页空态:三张曲线图无数据时显示「等待采样…」(原来只有三条淡线像没画完);日志框去横向滚动条 + 「暂无日志」占位文字。
  • 侧栏底部数据路径:%ProgramData% 字面量 → 真实路径两行折行(C:\ProgramData\…\ + Fluxion)。
  • 方法论沉淀:编译通过 ≠ 观感正确,全部改动以 tools\ui_snapshot.py 的 14 张离屏渲染逐页目检确认。

㉘ 2.8.1:卡片"两道杠"真凶 + 封面来源治理收口(2026-09-14 深夜)

  • 两道杠真凶(抓了三版才定位):GameCard 遮罩的 LinearGradientBrush 画刷矩形只有底部 40%,FillPath 却填整卡路径 —— GDI+ 画刷默认 WrapMode.Tile,把遮罩渐变在卡片上部周期性平铺成两道半透明暗杠。修法一行:WrapMode.Clamp。同款问题在 GradientCard 斜向高光(画刷矩形 ≠ 填充矩形)一并修掉。
  • 方法论教训:这个 bug 藏了三版,因为离屏渲染里它看起来"像封面本来就是深色下半"。破案靠的是渲染卡片 vs 源图并排比对(ui-snapshots\debug_cs2_compare2.png)—— 比对一做,杠不在源图里立刻实锤是渲染层。
  • 封面来源治理:新增 SaveCoverBytes 竖版门禁(<400x560 或比例超出 0.50~0.85 范围直接拒绝写入),任何路径都不能再用方图/横图顶掉好封面;tools\cover_refetch.py 支持按标题强制重抓。
  • 全息加工 v2:v1 把 spectral-lines(横向光谱线)材质烙进封面 = 缩略图上的条纹源;glare 用 point(max(0,v-120)) 产生硬阈值斜边。v2 改为缩略图友好配方(珍珠光泽平滑 10% + 平滑眩光 10% + 底部补偿),18 张已重烙,原图备份 covers\_originals\。
  • 工具条白角:RoundField / RoundCombo 的 BackColor 写死 Panel(白),在透明工具条上四角露白 → SetCorner(contentBg) 按所在分区取色。
  • PEAK / Chill with You / Wannabe Galgame God 无官方封面:appmanifest appid 核实无误,Steam CDN 7 路径 × 4 主机全 404,本地 appcache\librarycache 只有 32px 图标(0 字节占位)—— Steam 自己就没给这三款下发封面图,保留设计感生成磁贴;有社区图需求可右键「设置封面…」手动指定。

㉙ 2.9.0:DLSSG for SM86 0.3.0 整合(代理模式 · 真 DLSS MFG,2026-09-15)

作者更新到 0.3.0,三个实质变化直接改变了我们之前的结论:

变化 内容
回归代理模式 native(自建 NGX host)兼容性差,0.3.0 改回代理 DLL 内嵌未修改的原厂 DLSS-G 运行库,只拦截 nvngx_dlssg.dll 的加载 → 游戏侧调用不变,跑的是真 DLSS 帧生成(不是 XeFG 转译)
运行库 310.9 + 6X MaxGeneratedFrames 上限 3→5(4X→6X),Dynamic MFG 游戏可跑到 6X
官方提供 d3d12 入口 供替换注入\ 新增 d3d12.dll / dbghelp.dll(各 17.5MB),附作者按游戏的建议表(绝区零 d3d12、鸣潮 version/dxgi、燕云十六声 version→dxgi)

包真实性核验:version.dll SHA-256 = a22d2453f25d7df3…(与作者 README 声明的 310.9 版逐位一致);两个代理的签名者均为 CN=DLSSG for SM86 (self-signed),指纹 85BA6676…,与 README 声明一致(自签名 = 不提供默认信任,SmartScreen 仍会提示,属性正常)。

工具侧整合(本版新增):Dlssg030 类 + 帧生成页新区块「DLSS MFG 0.3.0 · 真 DLSS 多帧生成(代理模式)」

  • 部署 = 按游戏选入口(绝区零 d3d12.dll / 鸣潮 dxgi.dll / 其它 version.dll)+ 复制 dlssg_sm86.ini 与 nvngx_dlss* 运行库;覆盖前自动备份游戏自带件
  • 卸载 = 恢复备份、删除本方案新增文件
  • 与 XeSS 方案互斥守卫:已装 XeSS 的游戏点 0.3.0 部署会被拒绝并提示先卸载(反之亦然)
  • 资源包:%ProgramData%\Fluxion\dlssg030-pack\{common,alts}(约 164 MB,含 6 个入口代理)

绝区零已实测换装:卸载 XeSS 套件(备份 backup\zzz_xess_20260915)+ 部署 0.3.0(d3d12 入口)+ 游戏自带 nvngx 备份(backup\zzz_game_nvngx_20260915)。鸣潮维持 XeSS 方案不变,可随时用工具一键切换。

日志位置:<游戏目录>\dlssg_sm86\logs\loader_<pid>.jsonl(INI [Logging] Level 默认 1 只记错误,排查改 2/3)。


㉚ 2.10.0:自管滚动(修「切全屏后点不动下面那排」)+ 界面复刻 DLSS 5 Swapper(2026-09-15)

① 真凶不是命中测试,是 WinForms 的滚动区缓存(这次彻底换掉 AutoScroll)

用户截图:「切全屏后无法选下面的游戏了」。用新增的 hit 模式探针按"先窗口→再全屏→再退回" 的真实操作顺序复现,拿到硬证据:

== ① 窗口 1180x1000   Pg 960x909   AutoScrollMinSize={0,1213}  DisplayRect=943x1213   滚轮可达
== ② 改成 1920x1080     Pg 1700x989  AutoScrollMinSize={0,741}  DisplayRect=1700x989   (内容装得下)
== ④ 退回 1180x1000     Pg 960x909   AutoScrollMinSize={0,1213} DisplayRect=**1686x892**

第 ④ 步是病灶:DisplayRect.Width=1686 而客户区只有 960,Height=892 正是客户区高度 —— WinForms 认定"没有东西要滚",于是竖滚动条消失(VScroll.Visible=False, 尽管内容 1213 > 视口 892),AutoScrollPosition 也设不动。下半页既看不到也点不到。

来源是 LayoutOnce 里那句 AutoScrollMinSize = new Size(0, 1) 的"先复位": 它会让 WinForms 用当时的子控件并集去重算 DisplayRectangle,而那一瞬间子控件还是全屏布局 (宽 1636)留下的旧几何 → 缓存下 1686×892,之后再没人纠正它。

修法不是再补一层矫正,而是不再把滚动交给 WinForms:

  • AutoScroll = false,Pg 自己持一个 VScrollBar(Dock=Right);
  • LayoutOnce 把子控件整体放在 y - ScrollY(子控件是真的被移动的,命中测试自然跟着走);
  • SyncScrollRange() 按"内容总高 vs 视口高"给滚动条定价;
  • Reflow() 里那圈 PerformLayout() 矫正整段删掉(它存在的唯一理由就是刷 WinForms 的缓存)。

顺带修掉一个坑:ScrollBar.Value 的 setter 越界时是抛 ArgumentOutOfRangeException、 不是夹紧 —— 传 int.MaxValue 会被 catch 吞掉,滚动位置纹丝不动。SetScrollY() 现在自己夹紧。

验收(探针 hit 模式,同一会话跑完五个阶段):

阶段 ScrollY 命中失败 视口外
① 窗口 1180×1000 0 0 1
② 切全屏 1920×1080 0 0 0
③ 全屏下滚到底 0(内容装得下,正确) 0 0
④ 退回 1180×1000 0 0 1
⑤ 窗口下滚到底 304(= 内容 1213 − 视口 909) 0 0

② 界面复刻 DLSS 5 Swapper

用户要求"参照或复刻 D://DLSS5//DLSS 5 Swapper"。从它的 resources/app.asar 解出 src/renderer/style.css(137 KB)与 index.html,把 :root[data-theme=light|dark] 两套变量逐项对到 Theme 上:

Swapper 变量 本工具 浅色 深色
--accent Theme.Accent #6CC10A #6CC10A(同一色相)
--bg-1 Theme.Bg #EEF1F4 #05070A
--panel Theme.Panel #FFFFFF #12171F
--panel-2 Theme.Hover #F1F5F8 #1B222C
--bg-3 Theme.Subtle #F7F9FA #0C1016
--text / --muted / --dim Text / TextDim / TextFaint #1B2229 / #6B7885 / #93A0AD #F0F4F9 / #9AA7B6 / #626E7C
--radius / --radius-sm RCard·RPanel / RBtn·RField 18px / 12px 同

同时把 FlatBtn 里写死的蓝(按压 #0654B2 / 悬停 #1476E2)、监控页两条紫色曲线 (Color.FromArgb(130,80,223))、DWM 标题栏与边框色全部收到主题 token 上, 并改掉设置页里那句过时的说明(还写着"底色 #0A0C12、紫→蓝→青、14px/10px")。

设计语言两条约定的原文(来自它的 CSS 注释):强调色浅深共用同一色相; 深色底是"近黑 + 每层抬一级"(#05070A → #0C1016 → #12171F), 旧实现把整屏压在一个灰蓝平面上、什么都看不出层级。

③ 验证

tools/ui_snapshot.py 新增 hit 模式(StyleProbe.exe light hit 3 1180 1000 1920 1080): 先按窗口布局、再改成全屏尺寸、再退回,每阶段用 WinForms 自己的 GetChildAtPoint 逐层问"这个点最深处是谁",断言命中控件就是那张卡。 7 页 × 浅/深共 14 张离屏渲染全部 rc=0。

㉛ 2.10.1:三处视觉/绘制修复(2026-09-15 当晚)

用户截图三张,逐个查实:

① 深色下滑条是纯白的 —— 原生 VScrollBar 不吃 uxtheme

uxtheme 的 DarkMode_Explorer 对独立滚动条控件不生效(只对 Panel/ListView/TextBox 那类有效), 所以 2.10.0 新加的原生 VScrollBar 在深色下就是一整条白底。既然滚动已经自己管了, 滚动条也自绘:新增 FlatScroll(10px 细条 + 圆角滑块 + 悬停变亮 + 拖动/点轨道翻页), 取值对齐 DLSS 5 Swapper 的 --scroll(浅色 rgba(16,24,32,.20)、深色 rgba(255,255,255,.13))。

② 深色下 ListView 表头是白条 —— 表头是独立子窗口

ListView 的表头是单独的 SysHeader32 子窗口,只给 ListView 本身设主题它不跟着变。 必须在句柄创建后单独染一次:SetWindowTheme(SendMessage(lv.Handle, LVM_GETHEADER, 0, 0), "DarkMode_Explorer", null)。

③ 全屏后内容顶部一条被压扁的卡片行 —— 残影,不是布局算错

新增探针 capture 模式(GetDC + BitBlt 直接读窗口表面)复现: 1180x1000 → 1744x537(用户那次 1744×537)之后,卡片网格在被压扁的中间态位置画过一帧, 那一块之后再没被失效,于是留在表面上。

⚠️ 这正是为什么此前所有修正都"看不到问题":DrawToBitmap 会强制整棵树重绘, 残影类 bug 在它面前是隐形的。要验这类问题必须读真实表面。

根因:这套自绘控件族(Pg / Sec / CardGrid / GameCard 全是 Opaque + UserPaint + AllPaintingInWmPaint)父容器不擦背景,孩子移动时留下的旧像素没人负责擦; 而 WinForms 的失效区在连续 resize / 最大化那一帧会被系统重绘吞掉。 修法:MainForm.OnResize 里用 BeginInvoke 等布局落定,再 Invalidate(true) + Update() 把整棵立刻刷一次(Update() 是同步刷,光 Invalidate 不够)。

验收:同一组 capture 序列重跑,顶部那条卡片残影消失;深色 p1 表头变深、深色 p3 滚动条变细变深。

㉜ 2.10.2:倍率上限放开到 6X(用户指出「你不是说支持 6x 倍吗」)

用户截图指出「最大生成帧」下拉只有 1 · 2X / 2 · 3X / 3 · 4X。查证结果:用户是对的,工具错了。

作者 0.3.0 的 README 原话:

310.9 运行库新增 6X(MaxGeneratedFrames 上限由 3 提到 5)。

| 发布包 | 内嵌运行库 | 帧生成上限 | version.dll 大小 | | dlssg-release-x64-310.9 | 310.9.1.0 | 6X | 17,529,120 |

出厂 ini 注释:

Frame-generation multiplier ceiling: 5 = up to 6X, 3 = up to 4X. … Clamped to what the bundled runtime supports: 6X on the 310.9 build, 4X on the 310.1 build.

本工具 dlssg030-pack\common\version.dll = 17,529,120 字节(这是 0.3.0 时代量到的数,只在这条历史记录里成立;现在哪一版、多少字节以帧生成页「运行时状态」那行与日志里的实测值为准,见 5.6),即 310.9 → 本来就能 6X, 但 UI 把下拉写死成 3 项,把 6X 挡在了界面之外。

改的三处(外加两处顺手纠错)

位置 原 现
Ui.cs 下拉项 1·2X / 2·3X / 3·4X … / 4·5X / 5·6X(5 项)
Ui.cs 索引夹取 Math.Min(2, …) Math.Min(4, …)
Core.cs 读配置 原样接受任意值 夹到 1..5(老配置里的越界值会让下拉索引越界)
Core.cs 字段注释 1/2/3 → 2X/3X/4X 1..5 → 2X..6X(上限由运行库决定)
使用须知文案 「8GB 显存建议 2X」 删掉。作者实测:「帧生成的显存增量只随输出分辨率变化,与倍率无关(2X 与 6X 占用相同)」——旧文案是错的

新增 Dlssg030.SetMaxFrames(gameDir, n):0.3.0 的 INI 格式与旧版不同(多了 Enabled / Optimized / Preset / Directory / Mode 这几个键),所以不能用旧版 BuildIni() 覆盖, 否则会丢键。该方法只改 MaxGeneratedFrames 那一行,其余原样保留;键缺失时插到 [FrameGeneration] 之后。 DoApplyFgCfg() 现在会同时对 0.3.0 处于部署态的游戏调用它,日志里逐游戏回执。

验证(两个探针,均 ALL PASS)

  • tools/mfg_probe.cs —— 沙盒里造 Wuthering Waves Game\Client\Binaries\Win64\(放一个假 exe 就能让 XeMfg.Detect 认作鸣潮),真跑 SetMaxFrames:5→2、2→5、越界 99→5 / 0→1、 键整行缺失时插入位置正确、ini 不存在给友好提示、非支持目录被拒; 6 组 24 条断言全过,且每次都断言 0.3.0 的 7 个独有键一个不丢。
  • tools/mfg_items_probe.cs —— 只读反射 cbMaxFrames,断言下拉项恰好 5 项 且为 [1 · 2X][2 · 3X][3 · 4X][4 · 5X][5 · 6X]。 故意不调用 DoApplyFgCfg() —— 那会真往游戏目录写 ini,拿用户配置做实验不可接受。

两句要澄清的话(此前说错)

  1. 「跨代兼容版 NR 模型要你自己去 RenoDX 拿」—— 说错了。 C://ProgramData//Fluxion//xess-pack//wuwa//nvngx_dlssnr.dll 的 SHA-256 = E67DEE20…(ShortFuse 跨代版),工具自己的资源包里就是这一份, 与鸣潮游戏根目录那份逐位一致。所以大概率是本工具部署时放进去的。
  2. 放开上限 ≠ 游戏一定给 6X。 上限只是天花板,实际倍率由游戏自己的帧生成菜单决定; 作者也写明「强制 6X」的做法「逐游戏、依赖游戏侧闭源插件、较脆弱」。诊断页已加提示行说明这一点。

㉝ 2.10.3:全屏后那条「被压扁的封面带」——本机复现不出来,但找到了机制并做了自愈(2026-09-15 晚)

用户第三次反馈同一处:全屏后游戏库页顶部横向出现一条约 11px 高的封面条(截图里能认出 FORZA 等封面), 在标题与搜索框之间,且不消失。先承认一件事:上一版我说"修好了"是错的 —— 当时我用的是 DrawToBitmap 渲染验收,它会强制整棵树重绘到一张新位图上,残影/错位类问题在它面前是隐形的。

这一轮把方法换掉了,并如实说清结论:

手段 结果
读真实窗口表面(GetDC + BitBlt,探针 capture 模式) 我这边顶部干净
差分测试(同一状态抓两次表面,中间强制整树重绘,逐像素比对)9 个时序:窗口→最大化、最大化→还原→再最大化、最小化→还原、先最大化再切页、启动即最大化、一步跳全屏、先压成 400x300 再跳、在别的页切全屏、以及基线 全部 0 差异
摆放矩阵 6 种尺寸(含你窗口的实际 1752×566):量网格的 Pg 坐标 Y 与高度、卡片有没有跑到搜索框上方 全部正常(网格 Y=249、高=476、越位卡片 0)

结论:我怀疑触发条件是"分辨率/DPI 与我这台不同",但在本机始终未能复现(我这边最大只能到 1920×1009)。 但你截图里的东西不是"画完没擦"的残影 —— 已查明 Sec / HeadBox 的 OnPaint 本来就铺满整块矩形(第 932/1269 行), 所以那条带只可能是网格控件本身被摆到了错误的 Y、且高度只剩十几像素:子控件会被裁到父容器范围内, 露出来的正好是每个封面的顶端一条 —— 与截图完全吻合。也就是说,这是布局卡在了错误状态,不是绘制问题。

找到的机制

Pg.Reflow() 有一道早退闸,为了避免"点一下卡片就跳回最上面"而加:

if (lastCount == secs.Count && lastW == ClientSize.Width && lastTotal == ContentHeight()) return;

问题在于 lastTotal 等三个缓存数是 LayoutOnce() 跑完之后才写入的。万一某次布局在中途被打断 (或某个分区高度被算成 0),缓存可能已经写成"最终值"而控件还停在坏位置上 —— 此后每次 Reflow() 都命中早退闸,坏布局永久留在屏幕上。这正好解释"出现后不消失"。

三处修复

  1. 早退闸从"盲信缓存"改成"缓存一致 且 几何正确":新增 Pg.GeometryLooksRight(), 逐个核对 head/sec 的 Top 是否等于算出来的累积位置、高度是否塌陷(head < 20px / sec < 30px)。 不通过就强制重排,并写一条 [布局自检] 日志(含 Pg 尺寸、内容高、每个分区的高:位置、ScrollY)。
  2. LayoutOnce 对塌陷高度就地纠正:分区高度小于 30px 时按最小高度重设并重跑一次 DoLayout()。
  3. OnResize 改成两遍「重排 + 整树重绘 + 自检」(第二遍排到下一轮消息之后)—— 2.10.1 只做了一遍,而"布局落定"可能比那次重绘更晚。另加 MainForm.CheckLayoutHealth(), 只在几何异常时写日志,便于在真机上留证。

验证:把那条带人为造出来,看能不能自愈(探针 selfheal 模式)

造法 结果
A) 只把分区搬到 y=18(高度不变 → 三个缓存数全都一致,旧代码必然早退卡死) 破坏后 Top=18 → Reflow() 后 Top=245(期望 245)、网格 Top=4(期望 4)→ PASS
B) 把分区高度压成 11px(复刻截图那条带的尺寸) Reflow() 后 H=482(期望 ≥30)→ PASS
C) 再全量校验:卡片有没有落在网格矩形之外 越位 0 张 → PASS

回归:残影差分 9 个时序全部 0 差异;摆放矩阵 6 尺寸全部正常;7 页 × 浅/深 14 张渲染全过。

如果还会出现

新版本遇到该状态会在日志里留下一行 [布局自检] ...(含全部几何数据)。 日志位置:%ProgramData%\Fluxion\logs\fluxion_*.log。 把那一行(连同你的触发步骤:是点最大化按钮 / 双击标题栏 / Win+↑ / 还是切换主题之后) 发我,就能定位到具体是哪一次布局被打断的。

㉞ 2.10.4:两个区块并存时的安全补丁(用户问「这个是什么意思不会冲突吗」)

用户截图指出帧生成页同时列出「XeSS 多帧生成 · 绝区零 / 鸣潮(已部署 · 鸣潮)」与 「DLSS MFG 0.3.0(未部署 · 鸣潮)」,问会不会冲突。核实结论:不会同时生效(互斥守卫是对的), 但界面文案认错了自己的部署,而且「卸载」会不可逆地删掉用户的 DLSS 5 NR 套件。

① 先纠正一个我此前说错的事实

鸣潮那套「XeSS 多帧生成」就是本工具自己部署的 OptiScaler 包,不是「XeSS 帧生成转译」:

| 文件 | 游戏目录 | 工具 xess-pack\wuwa | |---|---|---|---| | dxgi.dll | 26,116,608 | 26,116,608 | 逐字节相同 | | nvngx_dlssnr.dll | 165,840,496 | 165,840,496(E67DEE20… 跨代版) | 逐字节相同 | | nvngx.dll_dlssnr.dll | 151,552 | 151,552 | 逐字节相同 |

而 WuwaFiles 里的 Spoofing/Dxgi=auto + FrameGen/External=true + DLSSG/AmpereMfgUnlock=true 意味着帧生成走的是游戏原生 DLSSG(由 SM86 解锁),不是 DLSSG→XeFG。 包内还含 DLSS 5 神经渲染(NR,位置在超分之后、帧生成之前),日志已实证 ApplyToFinishedPicture: 3600 frames。所以鸣潮这一套本来就同时提供「帧生成 + NR」。

区块标题与部署确认框写的「XeSS 多帧生成 / OptiScaler + fakenvapi + XeSS 运行库 / 鸣潮:DLSSG 进出」 只对绝区零成立,对鸣潮是错的 —— 已按实际构成改写(区块改名为「帧生成方案 A · OptiScaler 注入」)。

② 真正会坑人的地方:「卸载」= 不可逆删除

XeMfg.Uninstall 对每个目标文件走 if (备份存在) 还原 else File.Delete。 而 xess-pack\_backup 下只有 _backup\zzz\d3d12.dll,根本没有 _backup\wuwa —— 鸣潮部署时游戏里本来没有这些文件,没得备份。所以原实现会直接删掉:

  • dxgi.dll(26 MB 入口代理)
  • nvngx_dlssnr.dll + OptiScaler\streamline\nvngx_dlssnr.dll(两份 165 MB 的跨代 NR 模型)
  • nvngx.dll_dlssnr.dll(NR 转发器)
  • OptiScaler\dlssg_sm86\dlssg_sm86.dll(15.7 MB SM86 多帧生成运行库)及 OptiScaler 其余组件
  • OptiScaler.ini —— 里面装着调好的 Spoofing/Dxgi 与 [DlssNr] 参数

雪上加霜的是:Dlssg030.Install 的互斥守卫原本提示「请先在 XeSS 区块点「卸载」,再部署 0.3.0」—— 跟着做就会把 NR 弄丢,而 0.3.0 的包里没有 nvngx_dlssnr.dll,只提供帧生成。 也就是说那条提示实际是在劝用户做一次功能降级。

③ 补的三件事

  1. 卸载改为「移入隔离区」:新增 XeMfg.Retire(),把无备份可还原的文件 File.Move 到 xess-pack\_removed\<kind>-<时间戳>\,按原相对路径存放, 回执里直接给出隔离区路径 —— 误点一次也能原样拷回。
  2. OptiScaler.ini 视为用户配置:卸载时原位保留(没有入口代理它不会被读取), 部署时若已存在则不覆盖(资源包里那份是出厂值 Dxgi=auto / DlssNr=auto, 盖上去会让帧生成选项再次消失)。
  3. 三处文案改成说真话:区块 A 标题与说明、区块 A 的部署/卸载确认框(列出会移走什么)、 以及 0.3.0 的互斥守卫(明确写出「0.3.0 只提供帧生成、不含 NR,换成它属于功能降级」)。

④ 验证

探针 tools/uninst_probe.cs —— 全程在 %TEMP% 沙盒里造一个假鸣潮目录(只放一个 stub exe 就能让 XeMfg.Detect 认作鸣潮),绝不碰用户真实游戏目录。16 条断言 ALL PASS:

组 断言
卸载 OptiScaler.ini 原地保留且内容未变;dxgi.dll / nvngx_dlssnr.dll 已不在游戏目录;IsInstalled 转 false
隔离区 已创建;含 dxgi.dll、含 nvngx_dlssnr.dll;保留了 OptiScaler\streamline\nvngx_dlssnr.dll 这样的原相对路径;不含 OptiScaler.ini
部署 覆盖前先存在的 ini 未被出厂值覆盖(53 → 53 字节,内容逐字相同);回执里说明了保留
全新目录 ini 不存在时仍会正常铺入(不影响首装)

7 页 × 浅/深渲染回归通过;帧生成页新增的三行说明不破版。

㉟ 2.10.5:查实「绝区零没有 DLSS 选项」与「鸣潮进不去」,并修掉把用户引上歧路的提示

用户反馈两点:绝区零设置里没有 DLSS 选项(断言 0.3.0 无法实现);鸣潮用方案 A 进不去。

① 隔离区里的两份目录暴露了真相

xess-pack\_removed\ 下出现了两份不是我建的目录:

目录 含义
wuwa-20260916-002133 用户 00:21:33 点了鸣潮的「卸载」(内含 dxgi.dll / nvngx_dlssnr.dll / OptiScaler\ / Licenses\)
zzz-20260916-002933 用户 00:29:33 点了绝区零的「卸载」(内含 fakenvapi / libxess / libxess_fg / libxell / Remove_OptiScaler.bat)

也就是说:是工具自己的提示把用户引到这条路上的 —— 0.3.0 部署被互斥守卫拒绝时,原来那句 「请先在 XeSS 区块点『卸载』,再部署 0.3.0」用户照做了。(2.10.4 加的隔离区机制正好接住了这次事故, 文件一个没丢,已全部取回。)

② 绝区零没有 DLSS 选项的真正原因

它的 DLSS / 帧生成选项本身就是方案 A 的显卡名伪装换来的。 撤掉方案 A 之后,绝区零看到真实的 RTX 3060 Ti,于是判定不支持、设置里干脆不显示 DLSS。

而 0.3.0 完全不负责伪装。把它的出厂 dlssg_sm86.ini(2099 字节)整份读出来,只有:

[General] Enabled=1
[FrameGeneration] Optimized=1 / MaxGeneratedFrames=5
[Compatibility] Preset=Auto
[Logging] Level=1 / Directory=dlssg_sm86\logs
[Runtime] Mode=Bundled / CacheDirectory=

—— 一个 spoof 项都没有;README 的硬件段也只写「GPU:RTX 30 系列(SM86)」。 0.3.0 只做一件事:让 NGX 能在 30 系上创建帧生成,前提是游戏自己已经显示出并请求了帧生成。

所以「0.3.0 无法实现」这个判断是对的,但原因不是它坏了,而是它不提供绝区零最需要的那个能力。

顺带查出的第二个缺陷:绝区零那份 0.3.0 出厂 INI 被覆盖了 —— 出厂的 2099 字节(带 Enabled=1、 Mode=Bundled、Preset=Auto)实际生效的只剩 261 字节(旧版路径 Dlssg.BuildIni() 的模板), [General] Enabled=1 等键全丢。

另一条无害的痕迹:绝区零日志里有 DeviceFPManager::CheckInvalidDeviceFpValue, invalid devicefp key is: MIHOYOSDK_GPU_NAME, value is: NVIDIA GeForce RTX 5090 —— 那是伪装时期存下的旧指纹。已搜遍注册表 (HKCU 深度 4 / HKLM 相关子树)没有任何持久化的 5090 记录,属 SDK 自己的历史值。

③ 鸣潮进不去的真正原因:两套 SM86 运行时抢同一个 hook

OptiScaler 日志(00:24:07 启动那次):

[00:24:07] Spoofing.Dxgi: true / SpoofedGPUName: NVIDIA GeForce RTX 5090
[00:24:07] DLSSG.AmpereMfgUnlock: true / AmpereMfgMaxFrames: 3
[00:24:07] AmpereMfgLoader: Router selected: SM86 for GPU: NVIDIA GeForce RTX 3060 Ti
[00:24:07] AmpereMfgLoader: Loading ...\OptiScaler\dlssg_sm86\dlssg_sm86.dll
[00:28:00] NVSDK_NGX_D3D12_CreateFeature Passthrough to native NGX CreateFeature for feature 11   ← 帧生成 feature 建成功
[00:28:05~00:29:15] LibraryLoadHooks::LoadLibraryCheckW dxgi.dll call, returning this dll!   ← 只剩刷屏
(00:29:15 之后日志彻底停止 = 卡死那一刻)

配置全对、帧生成 feature 都建成功了,却在之后卡死。原因在同一目录里:还有第二套 SM86 代理 —— dinput8.dll(15,666,496 B,09-11 22:27),字符串里含 native_ 与 MaxGeneratedFrames, 是工具 09-11「换入口重试」时留下的旧版 dlssg_for_sm86。而 OptiScaler 自己也加载了 OptiScaler\dlssg_sm86\dlssg_sm86.dll(Native 0.2.3)—— 两套 SM86 运行时同时挂在一个进程里。 再加上 00:29 工具刚写回 root dlssg_sm86.ini,等于又给旧代理补上了配置。

④ 已执行的修复

对象 动作
鸣潮 dinput8.dll + root dlssg_sm86.ini 移入隔离区 → 只剩 OptiScaler 一套(已验证 dxgi.dll 26,116,608 在位)
绝区零 撤掉 0.3.0 的 d3d12.dll,恢复方案 A 全套 13 个文件(d3d12.dll 25,656,288 + fakenvapi + libxess + libxess_fg + libxess_dx11 + libxell + D3D12_Optiscaler + Licenses),保留用户原有的 OptiScaler.ini(Dxgi=true / SpoofedGPUName=RTX 5090 / FGInput=DLSSG → FGOutput=XeFG)

⑤ 工具修的三处

  1. 「应用参数」不再覆盖 0.3.0 的出厂 INI:对处于 0.3.0 路线的游戏跳过 Dlssg.BuildIni() 那一步 (改倍率交给 Dlssg030.SetMaxFrames,它只改一行、保留其余键)。
  2. 新增 Dlssg030.EnsureFactoryIni():0.3.0 部署完成后检查一次,只要 dlssg_sm86.ini 缺 [General] 就用资源包里的 2099 字节出厂版换回去(旧文件备份成 dlssg_sm86.ini.bak-oldtemplate)。
  3. 三处提示改成说真话:0.3.0 的互斥守卫(列出三条"不建议换"的理由,含「0.3.0 不做伪装」)、 区块 A 的说明(点明绝区零的选项来自本方案的伪装)、卸载确认框(写明撤下后鸣潮失去 FG+NR、 绝区零的 DLSS 选项会直接消失且 0.3.0 补不回来)。

㊱ 2.10.7:「卸载 / 部署」改造成「切换方案」(改名停放,秒级来回)

用户要求:别再用「卸载」这个说法,而且要在两套帧生成方案之间随时来回切换。

原来的语义确实不对 —— 「卸载」在实现里会移动/删除运行库文件,用户看到这个词自然以为"装不回来了"; 而它也确实会带走 NR 模型与 SM86 运行库(用户已因此误点两次)。改造后:

旧 新 实际动作
部署 / 卸载 切换到本方案 / 停放(回到原生) 切换 = 把别人的入口代理改名成 <入口>.parked.<方案>,把自己的改回原名;一个文件都不删

实现(Dlssg.cs 新增 Plan 类):

  • Plan.OwnerOf(path) —— 按文件名后缀 + 体积双重判定一个 DLL 属于哪套方案 (>20MB = OptiScaler 入口、17–20MB = 0.3.0 入口、15–17MB = 早期 dlssg_for_sm86); 不是代理就返回空串,绝不碰游戏自带文件。
  • Plan.Current(gameDir) —— 先看主入口,主入口被停放就扫全部入口名 (dxgi / d3d12 / version / dinput8 / winmm / winhttp / dbghelp),返回 opti / 030 / legacy / none 之一。
  • Plan.ParkOthers(gameDir, kind, keep) —— 把"不是目标方案"的入口代理改名停放。
  • Plan.SwitchTo(gameDir, target) —— 目标方案文件已在目录(只是被停放)→ 只改名,秒完成; 不齐才走一次真正的安装。

界面上两处「当前状态」也都改成读同一个 Plan.Current,所以不会出现"两个区块各说各话":

当前:方案 A · OptiScaler 注入(dxgi.dll 槽位)  ← 本方案在用
当前:DLSS MFG 0.3.0(d3d12.dll 槽位)          ← 切到本方案会把它改名停放
当前:原生(所有代理都已停放)                    ← 代理已停放,游戏跑原生

验证:tools/plan_probe.cs(_run_planprobe.py 编译运行)在 %TEMP% 沙盒里造 假鸣潮 / 假绝区零目录,真跑切换逻辑,10 条断言 ALL PASS:

断言 结果
方案 A 在用 → Current = opti PASS
切到 0.3.0 → Current = 030,且 A 的入口被改名为 .parked.opti PASS
再切到"原生" → Current = none,入口变为 .parked.030 PASS
再切回方案 A → Current = opti,.parked.opti 改回原名 PASS
全程文件总数不变(7 → 7)、没有任何文件消失 PASS
只装旧版代理 → Current = legacy(早期 dlssg_for_sm86 也能识别) PASS
绝区零槽位(d3d12.dll)同样可切、可切回 PASS

同时清掉界面上残留的「卸载」措辞(含数据目录说明、0.3.0 的互斥守卫、各条回执)。

一句话选型(用户问的):

两个二游都用「方案 A · OptiScaler 注入」 —— 绝区零只能用它(它的 DLSS / 帧生成选项就是靠这套的显卡名伪装出现的); 鸣潮也用它(同一套同时给「帧生成」和「DLSS 5 神经渲染」,且实测 6X 可用)。 「DLSS MFG 0.3.0」留着备用:它不含 NR,也不做显卡名伪装,换过去等于功能降级。

㊲ 2.10.8 / 2.10.9:0.3.0 到底"只能给 3A 用"吗 + 措辞一致性收口

用户看到两个 B 站视频(BV1Fje56tEKn 绝区零 0.3.0 / BV1cnYy6VETE 0.3.1)后问: 「这个是不是只能给 3A 用」。

结论:不是。0.3.x 与游戏类型无关,只与"游戏自己会不会去请求 DLSS 帧生成"有关。 它做的唯一一件事是拦截 nvngx_dlssg.dll 的加载并换成内嵌的原厂运行库 —— 游戏侧调用不变,所以任何会请求 FG 的游戏都能用。

真正的分水岭是单机 / 网游(作者自己也是这么分的):

单机 3A 网游(绝区零 / 鸣潮)
装法 丢 version.dll + dlssg_sm86.ini 进渲染 exe 目录,开箱即用 多两步:① 常需改显卡名为 RTX 5090;② 用带数字签名的入口
作者证据 README「实测反馈」:黑神话悟空 / 赛博朋克 2077 / FH6 可开 4X;Resonance 在 310.9 下默认 6X 视频简介原话「NVIDIA GeForce RTX 5090(修改显卡名称,一般网游才需要)」;归档目录名「部分网游需要数字签名才可以使用,单机一般不需要」

对用户的绝区零意味着什么:它的菜单里根本没有帧生成选项 —— 卡在第①步(没改显卡名)。 0.3.0 不负责伪装,所以它补不上这一环;而方案 A(OptiScaler)自带 SpoofedGPUName + Dxgi 伪装, 所以绝区零的选项是它换来的。这条已在 2.10.6 的实测里确证过。

0.3.1 的结论:视频简介原话「0.3.1 添加 20 系支持,30 系无其它功能变化, 30 系使用 0.3.0 或者 0.3.1 无区别」。所以本机(3060 Ti)不需要换包 —— 本工具带的就是 310.9 / 6X 那一版。顺带两个前提:视频强调要开「GPU 硬件加速计划」(本工具 运行时状态里已有 HAGS 检测,本机已开启 ✓);装新版要先删旧版 version 与 sm86 文件(部署时自动清)。

工具侧改动:

  • 0.3.0 区块标题改为 DLSS MFG 0.3.0 / 0.3.1,正文补四行「适用面」(单机开箱即用 / 网游两道门槛 / 作者实测清单 / 0.3.1 对 30 系无区别)。
  • 措辞一致性收口:界面上残留的「XeSS 多帧生成」全部改为 「方案 A 注入」 (卡面短标签、页面副标题、状态栏计数、XeMfg.ShortTag、XeMfg.Status); 0.3.0 区块里"与 XeSS 方案互斥"改为"与方案 A 互斥"。
  • 说明页的"三层工作链路"原先只写了绝区零那种走法,现改为按游戏分述: 绝区零 → 插帧交给 Intel XeSS 帧生成引擎(FGOutput=XeFG); 鸣潮 → 仍走游戏原生 DLSSG,由 SM86 解锁让它在 30 系上能跑(FGOutput=DLSSG,实测 6X)。

验证:静态检查 PASS、编译 OK、7 页 × 浅/深共 14 张离屏渲染全部 rc=0。

㊳ 2.11.0:0.3.x 开放给任意游戏(3A 单机)+ 修掉"把 maxGeneratedFrames=5 写进旧版代理"的致命 bug

用户纠正 + 两个实测问题:

这个也能给 3A 用的,我看视频就是这么说的,要不然现在这个状况我的 2077 只能开 4 倍帧生成。地平线 6 卡在这进不去游戏。

① 我上一轮的说法没错,但工具根本没给这条路。 XeMfg.Detect() 只认绝区零 / 鸣潮, 其余游戏一律"不在支持列表" —— 选中 2077 / FH6 时 0.3.0 区块只会显示「原生(所有代理都已停放)」, 等于把作者文档里"单机只放两个文件"的那条路堵死了。本轮开放:

  • Plan / XeMfg.TargetDir 支持 kind == "":落地目录 = 扫描认定的 exe 所在目录。
  • Dlssg030.InstallGeneric(dir, entry, maxFrames):只放入口 + dlssg_sm86.ini 两个文件, 不覆盖游戏自带的 nvngx(实测 CP2077 的 nvngx_dlssg.dll 7,607,336、FH6 的 7,499,376 都比包内那份新)。
  • 界面新增「通用游戏入口(3A 单机)」下拉:version.dll / winmm.dll / dbghelp.dll / dinput8.dll / dxgi.dll / d3d12.dll (作者 alternatives/README.md 的 6 选 1);游戏不加载默认入口时换一个即可。
  • 方案 A 仍然只对绝区零 / 鸣潮开放 —— 选中其它游戏时该区块会直说原因。

② 真凶:应用参数 把 5 写进了装着旧版代理的游戏 → FH6 卡死。

旧版 dlssg_for_sm86(14.94 MB,内嵌 310.1 运行库)只认 MaxGeneratedFrames = 1..3。 而 DoApplyFgCfg() 里的条件只排除了「0.3.0 部署态」,没有排除旧版代理,于是: 用户在帧生成页把「最大生成帧」设成 5 · 6X 并点「应用参数」→ 工具把 MaxGeneratedFrames=5 写进 FH6 的 dlssg_sm86.ini(261 B 旧模板)→ 代理报 {"event":"configuration_error","message":"Invalid INI integer"}(dlssg_sm86\logs\native_10932.jsonl,只有这一行) → FH6 停在加载界面进不去。同一个动作也写到了 CP2077 的根目录(那里不是渲染 exe 目录,无害)。

修法:

  1. DoApplyFgCfg() 改成逐游戏先认代理、再决定写什么(Plan.OwnerOf): 目录里没有我们的代理 → 一律不碰(并计数报告);认出来是旧版 → 用新增的 Dlssg.BuildIniFor(m, log),上限夹到 3(并在日志里说明"要 6X 请先升级到 0.3.x"); 认出来是 0.3.x → 走 Dlssg030.SetMaxFrames(只改一行,保留 [General] 等键)。
  2. Plan.LooksProxy 下限 15 MB → 14 MB:旧版代理是 14.94 MB, 按 15 MB 划界会整类漏掉 —— 既认不出它,也拦不住"两个代理同时生效"。
  3. Plan.OwnerOf 的 0.3.x 阈值 17 MB → 16 MB:0.3.x 入口是 17,529,120 B = 16.72 MB, 原来的 17 MB 阈值会让它掉进 legacy(探针新断言抓到的,不是猜的)。

③ 已直接修复的两台机器现场(本次会话内完成)

游戏 处理 结果
ForzaHorizon6 停放旧版 version.dll(14.94 MB)→ 换成 0.3.x 的 version.dll(17,529,120 / SHA A22D2453…)+ 2099 B 出厂 INI 生效代理只剩一个,[General] Enabled=1、MaxGeneratedFrames=5
Cyberpunk 2077 bin\x64 同上,并把同目录的旧版 winmm.dll 改名停放(两个代理同时生效是作者明令禁止的) 同上;根目录的历史残留 ini 也移走

备份一律就地保留(version.dll.bak-<时间戳>、winmm.dll.parked.legacy),可随时还原。

④ 验证:tools/plan_probe.cs 扩到 10 组 20 条断言,全部 PASS,新增三组专门盯这次的坑:

  • 14.94 MB 的旧版代理必须被认成 legacy(旧代码认不出);
  • 通用游戏(假 FH6 目录)从旧版切到 0.3.x:入口换成 17,529,120、ini 换成含 [General] 的出厂版、 游戏自带的 nvngx_dlssg.dll 未被覆盖、旧代理改名为 .parked.legacy、还能停放回原生;
  • BuildIniFor(5) 必须写出 =3(写 5 就是本次事故),BuildIniFor(0) 兜到 1。

⑤ 顺带说明 6X 的边界(作者 README 原文口径):上限由游戏侧插件决定 —— 游戏自带较新插件(支持 Dynamic MFG)时,装 310.9 + MaxGeneratedFrames=5 可跑到 6X; 游戏只支持 4X 时,本项目无法把它抬上去。作者实测表里 2077 与 FH6 都在"可开 4X"那一档; 装好后 dlssg_sm86\logs\native_*.jsonl 里的 generated_count 会直接给出答案(3 = 4X,5 = 6X)。

㊴ 3.0.0:UI 不再挡视线 + 「方案推荐」+ 长文案搬到说明页 + 功能缺口盘点

用户三条要求:① UI 有遮挡、② 插件太多希望直接给推荐、③ 解释文字太多挡视线(并顺手找别的 bug)。

① UI 阻挡的真凶:Sec 的行布局用了固定标签宽

DoLayout() 的 KRow 分支里,控件位置写的是

if (it.B != null) { it.B.Left = (it.A == null) ? padX : padX + labelW; ... }   // labelW = Theme.S(102)

labelW 是固定值 102px,而标签是不透明底色(BackColor = contentBg)。 标题一旦比 102px 长,就会直接盖住右侧控件 —— 用户截图里那个只剩 n.dll 的下拉就是这么来的 (version.dll 左半被「通用游戏入口(3A 单机)」这个长标题糊掉了,不是控件窄)。

修:标签宽度按真实文字量算(Math.Max(labelW, it.A.Width + 8)),控件放不下就缩宽(下限 48px), 宁可窄也不重叠。另外把「通用游戏入口」这一项单独占一行(Block 铺满),彻底避开挤压。

② 方案推荐:按档位给一句话结论

新增区块「方案推荐 · 按游戏类型挑」,两行动态文字由 Dlssg.RecommendLine(DlssgGame) 生成 (档位取自配置里已有的三张名单:二游 / 3A / 竞技,不靠猜):

档位 推荐 理由(原文摘)
竞技网游(CS2 / 无畏契约 / 三角洲) 不建议装(只做电源 / 加速包联动) 内核反作弊,注入类封号风险自负,收益也不值当
二游(绝区零 / 鸣潮) 方案 A · OptiScaler 注入 绝区零的 DLSS 选项靠它的显卡名伪装才会出现;鸣潮同一套同时给帧生成 + DLSS 5 NR
3A 单机(2077 / 地平线 6 / 黑神话…) 0.3.x 通用模式 只放两个文件、不需要改显卡名;要 6X 就用自带的 310.9 版并把「最大生成帧」设成 5
未归类 0.3.x 通用模式(可试) 只拦截 nvngx_dlssg.dll 的加载,装错最多不生效

状态栏也补了「0.3.x 通用:N 个」计数;卡片短标签新增 DLSS MFG 0.3.x(通用), 让 3A 游戏装没装成功在列表里一眼可见。

③ 长文案搬家

两个方案区块的正文压到 2–3 行,只留"当前状态 + 关键差异 + 与谁互斥"; 适用面 / 前置条件 / 6X 边界 / 各游戏实测清单 / 旧版代理上限这些整段搬到「说明」页 的新区块「帧生成方案详解(怎么选 / 前置条件 / 6X 边界)」。说明页里那句过时的 「② 开启选中」也改成了实际按钮名。

④ 顺手排掉的 bug

位置 问题 处理
Dlssg030.ShortTag 3A 游戏装了 0.3.x 在卡片上没有标签(看不见是否成功) 新增 GenericInstalled() 判据 + DLSS MFG 0.3.x(通用)
状态栏 只统计方案 A 的接入数,0.3.x 通用完全没算 补 0.3.x 通用:N 个
Dlssg030.Status 3A 游戏点开显示"不在支持列表" 交由 RecommendLine 给出通用模式结论

验证:静态检查 PASS、编译 OK、7 页 × 浅/深共 14 张离屏渲染全部 rc=0。

⑤ 功能缺口盘点(联网对照 Playnite / Steam / GOG Galaxy 的能力面,2026-09)

类别 已有 缺(按性价比排序)
库管理 多平台扫描(Steam / 注册表卸载项 / 常见目录)、标签收藏、忽略、封面自定义 元数据抓取(发行年份 / 类型 / 开发商)、按状态分类(在玩 / 通关 / 弃坑)、多平台账号导入(Epic / GOG / Xbox)
启动 双击卡片启动、管理员启动开关 每游戏启动参数与环境变量(沙盒)、批量启动一组、全局热键唤出、鼠标边缘触发
存档 — 存档备份 / 还原(最高性价比,且与"反复重装"直接相关)
数据 游戏时长、配置导出 / 导入 成就记录、游玩历史时间线
性能 实时监控(CPU / GPU / 帧)、性能档位联动 游戏内 OSD(帧率 / 帧生成状态)、按游戏自动切换性能档
其它 崩溃日志、错误捕获、深色 / 浅色主题 插件 / 脚本扩展、手柄与全屏模式、自动更新

结论:最该补的两项是「存档备份 / 还原」与「每游戏启动参数 + 环境变量」 —— 前者直接解决"重装一次就丢东西"的焦虑,后者是 Playnite / Steam 用户的硬需求。

㊵ 3.0.1:「绝区零方案A从未生效」的实锤 + 「应用参数」误写 bug 修复 + FH6 卡死修复

用户反馈三件事:2077 显示 4X 却有 300 帧;绝区零还是没帧生成;FH6 启动不了;并抱怨界面"越优化越乱"。

① 2077 的 4X / 300 帧 —— 不是 bug

日志(native_7728.jsonl):max_generated=3、generated_count=3×101 帧 —— 游戏菜单请求的就是 4X (2077 的 Streamline 插件上限 4X,作者实测表也列在 4X 档)。300 帧 = 基础 ~75fps × 4,4X 在正常工作。 0.3.x 的 6X 只是天花板,游戏不请求就没有;作者原话:游戏仅支持 4X 时本项目无法抬到 6X。

② 绝区零:方案A 的代理从未被加载过(此前所有判断的前提错了)

证据 结论
OptiScaler.log 从未存在(每次游戏启动后都没有) 代理从未初始化
游戏日志 MIHOYOSDK_GPU_NAME = NVIDIA GeForce RTX 3060 Ti(02:40 那次) 伪装从未生效
隔离实验:把探针 exe 放进绝区零目录 LoadLibrary("d3d12.dll") → 加载成功且立刻写日志 dll 本身没问题、路径解析没问题
version.dll / dinput8.dll 在本机被强制解析到 System32(实验证实) "换入口重试"从来就不可能成功
02:40 那次游戏启动:方案A 在位、游戏照常运行、无任何代理日志 游戏进程内拦截了未签名 DLL

结论:绝区零的反作弊拦截未签名 DLL —— 方案A(OptiScaler,未签名)在绝区零永远不可能生效。 作者包里写明的"自带数字签名"就是为这个准备的。绝区零唯一可行路径 = 作者签名版 0.3.x d3d12 + 注册表显卡名伪装 (0.3.x 不做伪装,游戏看到 3060 Ti 就不会显示帧生成选项)。

已执行:方案A 全套移入隔离区(可恢复);部署签名版 d3d12.dll(17,529,632 B)+ 出厂 ini; 生成 <个人目录>\绝区零-显卡名伪装\(开启/还原 .reg + README)。 此前"绝区零必须用方案A"的判断正式推翻。

③ FH6 启动不了的根因:被写坏的 261B ini

FH6 的 dlssg_sm86.ini 是 261B 旧模板(含 Router=SM86)—— 0.3.x 代理把 Router 当整数解析, 直接报 {"event":"configuration_error","message":"Invalid INI integer"} → 游戏卡死在加载界面。 已恢复出厂 2099B ini(备份 .bak-oldtemplate-*)。

④ 「应用参数」误写 bug("越优化越乱"的元凶)

工具日志显示 02:05-02:40 之间 DoApplyFgCfg 跑了 10+ 次,每次都把绝区零/鸣潮判成 "装的是旧版代理"并写入 4X 旧模板。根源:分类逻辑的 else 分支同时接住了 Legacy 和 Opti —— 方案A 在用的游戏也被当旧版代理写 ini。修:else → else if (own == Plan.Legacy), Opti 计数跳过;D030 分支写前先 EnsureFactoryIni 修复被污染的 ini。

⑤ 界面

「方案推荐」移到帧生成页最顶;推荐文案改为今天的实测结论(鸣潮=方案A / 绝区零=0.3.x+伪装 / 3A=0.3.x / 竞技=不装);RecommendLine 二游分支按游戏拆分;方案A 区块标题/说明、停放确认框、 0.3.0 部署警告、前置条件结论全部改说真话。

㊶ 3.1.0:显卡名伪装内置 + 方案推荐改成"自动识别 + 强调色 + 一键应用"

用户三条要求:① 改显卡能不能内置(不想每次出去双击 .reg);② 绝区零两个方案都没有帧生成; ③ 方案推荐还是模棱两可、没有推荐度,要"强调色显示 + 自动识别自动选择"。

① 绝区零到底支不支持 DLSS —— 先查清这个

ZenlessZoneZero_Data\il2cpp_data\Metadata\global-metadata.dat(119 MB)里同时存在 DLSS / FrameGeneration / FrameGen / SuperResolution / FSR3 字符串 → 游戏本体是支持的,问题在"游戏看不到自己被支持"。这条决定了后面的方向。

② 上一轮的一个结论要撤回:入口名并没有被 KnownDLLs 拦

实测 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs(38 项), d3d12.dll / d3d11.dll / dxgi.dll / version.dll / dinput8.dll / winmm.dll / winhttp.dll / dbghelp.dll 全都不在名单里。所以此前"version.dll / dinput8.dll 被系统强制解析到 System32,换入口重试从来不可能成功" 这句是错的,撤回。

仍然成立的是事实部分:绝区零目录里从来没有生成过 OptiScaler.log —— 方案 A 的代理确实没被加载过。 原因待定,但 0.3.x 的作者包注明"自带数字签名",对反作弊游戏是更合理的赌注,所以本轮把绝区零切到了 签名版 0.3.x 的 d3d12.dll(方案 A 全套 9 个文件 + 2 个目录移入隔离区,可原样拷回)。

③ 显卡名伪装内置(GpuSpoof 类)

能力 说明
Detect() 只认 Service=nvlddmkm 的设备 —— VEN_10DE 下面还挂着 HD Audio 控制器(Service 是 HDAudBus),按厂商号盲改会改到声卡上
Apply() / Restore() 写 DeviceDesc + FriendlyName;还原时把 FriendlyName 整个删掉(只改回 DeviceDesc 会残留伪装名)
EnsureBackup() 一进帧生成页就把"原始显卡名"存成 gpu-spoof-original.txt —— 即使用户从没点过开启,还原也有精确值
RestartAdapter() pnputil /restart-device PCI\VEN_10DE&DEV_xxxx\...,不用重启系统就让伪装生效(失败则提示重启电脑)
StatusLine() 界面上一行显示"当前显卡名 / 已伪装·未伪装"

界面上是一个独立区块(② 显卡名伪装):开启伪装 / 还原真实显卡名 / 重启显卡设备 三个按钮, 副作用(全系统可见、驱动升级后要重做)直接写在按钮上方。

探针抓到的实现缺陷(已修):Enum 键里没有 InfPath —— 它在驱动键 Class\{4d36e968-...}\000N 里,要用 Enum 键的 Driver 值找过去。少了这一步,重建出来的"原始名" 会丢掉 @oem28.inf,%nvidia_dev.2486%; 前缀(还原后名字虽然能显示,但不再是原样)。 tools/spoof_probe.cs 现在 9 条断言 ALL PASS,且断言备份内容 = 精确原始值、不含 5090。

会话里没有管理员权限,写 HKLM 的实测由 app 自己完成(它以 requireAdministrator 运行)。 探针只做只读验证,不会去动用户的注册表。

④ 方案推荐:自动识别 + 强调色 + 一键应用

区块移到帧生成页最上方,只给三行:

推荐:DLSS MFG 0.3.x(作者签名版)+ 显卡名伪装      ← 大号强调色(柠檬绿)
★★★★☆  推荐 · 当前 DLSS MFG 0.3.0                 ← 适用度
绝区零会拦未签名 DLL……两步都已经内置,可一键完成。   ← 一句话理由
[ 一键应用(0.3.x + 伪装) ]  [ 查看详解 ]

Dlssg.BuildRec() 返回 Rec(Head / Stars / Verdict / State / Why / Btn / Action / Bad), Action 告诉界面该执行哪个动作(opti / 030 / 030spoof / none), "一键应用推荐方案"直接照它做,用户不用再去下面找对应区块的按钮。 不推荐的情形(竞技网游)整块文字转告警色、按钮变灰写"无需安装"。

判定链(RecommendCategory)本轮加固:

  1. 工具认得游戏(绝区零 / 鸣潮)→ 二游;
  2. 否则用 exe 名 / 目录名 / 标题 三个候选去匹配名单,归一化后比较 —— 目录名是「Cyberpunk 2077」,名单里写的是「Cyberpunk2077」,不归一化就整类掉进"未归类" (本轮渲染实测到的:2077 从"未归类 ★★★☆☆"变成了"3A ★★★★★");
  3. 竞技关键词兜底(cs2/csgo/valorant/deltaforce/riotclient/完美世界/5eclient)—— 用户的库里 FPS 项目叫「完美世界竞技平台」「5EClient」,而配置里 fpsGames 是空的;
  4. 最后按事实兜底:有 nvngx_dlssg.dll/sl.dlss_g.dll → 按 3A 对待(★★★★☆); 只有 FSR3 组件 → ★★☆☆☆「可选:游戏自带 FSR3,不必装」;两个都没有 → ★☆☆☆☆「不推荐」。

⑤ 顺带修掉的两处

  • 目标游戏那一行原来读 games.json 里上次扫描留下的 Entry 字段,我们在外面换过文件之后就过期了 (显示"入口 winmm.dll"而实际生效的是 version.dll)。改为 Plan.StatusOf(dir) 按目录里真实文件判定。
  • Plan.OwnerOf / LooksProxy 的阈值校准:旧版代理 14.94 MB、0.3.x 入口 16.72 MB、OptiScaler 25.6/26.1 MB。

⑥ 验证

验证项 结果
静态检查 / 编译 PASS / COMPILE_OK
tools/spoof_probe.cs(伪装只读) 9 条 ALL PASS
tools/plan_probe.cs(方案切换,沙盒) 28 条 ALL PASS
7 页 × 浅/深渲染 14 张全过
推荐区块(新增探针模式 recgame) 绝区零 ★★★★☆ 推荐 / 鸣潮 ★★★★★ / 2077 ★★★★★ / 完美世界 ★☆☆☆☆ 不推荐

⑦ 现场修正(本次会话内完成)

对象 动作
绝区零 方案 A 的 d3d12.dll(25.6 MB)改名停放 → 签名版 0.3.x(17,529,632 B)上岗;方案 A 配套 9 个文件 + 2 个目录移入隔离区
地平线 6 停用重复的旧版代理 winmm.dll(15,678,272 B,一个目录里两个代理同时生效是作者明令禁止的)
2077 复查 dbghelp.dll(1.8 MB)确认是游戏自带文件、不是代理,因此未动

㊷ 3.1.0 补充:对战平台不是游戏 + 库诊断表(用户指出「完美世界和 5E 不是游戏是对战平台」)

用户一句话点破:完美世界 与 5E 是 CS2 的对战平台(负责匹配与反作弊),不是游戏。 我上一轮把它们塞进了"竞技网游"关键词,于是界面上给了一段「竞技网游带内核反作弊、封号风险自负」的 封号警告 —— 理由完全不对。

① 扫描阶段就该挡在门外

PlatformScan.NonGamePat(注册表卸载项 / Steam appmanifest 名称过滤)原来有 ^steam$|epic games launcher|^wegame$|gog galaxy|battle.net$|ubisoft connect|ea app|launcher$|启动器$, 但没有覆盖国内的对战平台。补上:

\b5e(client|box|对战|平台)|perfectworld|完美世界|对战平台|电竞平台

② 新增 platform 档位(不是"竞技网游")

RecommendCategory 里平台判定排在最前(认出绝区零 / 鸣潮之后、进名单匹配之前):

不用装:这不是游戏,是对战平台 / 启动器 对战平台(5E / 完美世界竞技平台这类)负责匹配与反作弊,本身没有帧生成 —— 注入进去既没收益、还可能被它的反作弊盯上。要装的是它启动的那个游戏(例如 cs2.exe)。

版式上新增中性色(Rec.Dim)—— 平台既不是"推荐"(绿)也不是"风险"(红),用灰。

③ 关键词分了两档(防误伤真游戏)

第一版用"包含"匹配写出了三个误判,全部在这一轮修掉:

误判 原因 修法
无畏契约被判成平台 它的条目 exe 是 aclos-launcher.exe,我写了 EndsWith("launcher") 删掉这条规则 —— 游戏自己的启动器不是平台(渲染实测抓到的)
SteamWorld Dig 会被判成平台 关键词 steam 用包含匹配 通用名只允许完全等于(steam / wegame / origin / mumu / 模拟器)
刺客信条:起源 / Origins 会被判成平台 关键词 origin 用包含匹配 同上

最终:PlatformExact(完全等于)+ PlatformPart(足够独特,可包含) = 5eclient / 5ebox / 5e对战 / perfectworld / 完美世界 / 对战平台 / 电竞平台 / epicgames / goggalaxy / battlenet / ubisoftconnect / eadesktop / eaapp / gameviewer / steamclient / 平台。

④ 顺带修掉的两条(都是库诊断表暴露的)

新增探针模式 recdump(StyleProbe.exe light recdump 2),把库里每一条游戏的 Exe / Title / Dir / HasFrameGen / HasFsr3 / HasUpscaler / 分类 / 推荐结论 落成一张表 (ui-snapshots\recdump.txt)。一跑就看出两个问题:

条目 原来 现在
PUBG: BATTLEGROUNDS(TslGame.exe) 未归类 → "0.3.x 可试 ★★☆☆☆" 竞技网游 → 不推荐安装(名单补 pubg / tslgame / 绝地求生)
P5R / PEAK / 以撒 / 尸姬之梦 / Esports Manager / Chill with You / Wannabe Galgame God 都在推荐 "0.3.x 可试" 不用装:没检测到帧生成组件(目录里没有 nvngx_dlssg.dll/sl.dlss_g.dll,代理没有可接管的调用)

判定口诀:先看"是不是游戏"→ 再看"有没有可接管的调用"→ 最后才谈装哪套。 顺序错了就会出现今天这种"给对战平台发封号警告、给没有帧生成的游戏推荐装帧生成"。

⑤ 最终 18 条库的判定(实测输出)

档位 条目 结论
3A 单机 Cyberpunk 2077、Forza Horizon 6 推荐 0.3.x 通用 ★★★★★
二游 绝区零 推荐 0.3.x 签名版 + 显卡名伪装 ★★★★☆
二游 鸣潮 推荐 方案 A · OptiScaler 注入 ★★★★★
二游 原神 不用装:游戏没有 DLSS 帧生成
竞技 PUBG、CS2、卡拉彼丘、无畏契约 不推荐安装(保持原生)
平台 5EClient、perfectworldarena 不用装:这不是游戏
其它 P5R、PEAK、以撒、尸姬之梦、Esports Manager、Chill with You、Wannabe Galgame God 不用装:没检测到帧生成组件

验证:静态检查 PASS、编译 OK、plan_probe 28 条 ALL PASS、spoof_probe 9 条 ALL PASS、 7 页 × 浅/深 14 张渲染全过。仍未打包(用户要求)。

㊸ 3.1.0 补充:「判定要对后续新添加的游戏生效」(用户要求)

用户一句话点出的是判定生命周期问题:前面那套判定(是不是游戏 / 有没有可接管的调用 / 该装哪套) 只在"当时那份库"上成立 —— 以后新下载的游戏得靠用户自己去改名单,等于没生效。

查出来的三处缺口(都在"新加的游戏"上暴露):

# 缺口 后果
1 Program.AutoCategory(联动档位)完全不看游戏库,只查四张静态名单,而且未命中一律 return "fps" 新装的游戏被判成竞技网游 → 玩它时 UU远程 被暂停(和"启动二游也杀远控"同源)
2 AllGameProcesses() = 四张名单的并集 新加的游戏根本不算"游戏":电源联动 / 加速包 / 定时器 / 亲和性 / P 核绑定全轮不到它,手动档位下拉里也看不到它
3 MergeScan 只对"手动添加"的条目重探能力;FillGameList 又按对象引用比较高亮 绕过扫描进来的条目带着过期字段;每次扫描(对象全换新)后选中项丢失、推荐块继续渲染旧对象

修法(核心思路:判定只留一个计算点,两个消费者读同一个值):

位置 改动
Dlssg.Library 新增静态引用,只由 MergeScan 写入(唯一入口,避免多处各维护一份而走样)
DlssgGame.RecCat 每次合并重算并缓存分类 —— AllGameProcesses() 在每次进程快照里被调十几次,不能在里面做目录探测
MergeScan 每一条都重探能力字段并重算分类(不再只探手动条目);盘根目录跳过(Inspect 会递归整块盘)
Dlssg.FindByProcess 按进程名找库条目(先比 exe 名,再退一步比目录末段)
Dlssg.LinkCategory 把推荐分类映射成联动档位;platform → ""(不是游戏,不参与任何联动)
Core.AutoCategory 先问游戏库(与「方案推荐」共用同一套规则)→ 再静态名单兜底 → 未知不再默认 fps
Core.AllGameProcesses 并入库里"是游戏"的条目(排除对战平台),并全部改成 null 安全
Core.GameDisplayName 新游戏的显示名取自库标题(否则手动档位下拉里会出现 REDprelauncher 这类原始进程名)
Ui.FillGameList 重建列表后按目录重新定位 selected(否则高亮丢失、推荐块渲染旧字段)

顺手修掉两个真 bug(都是探针先抓到、不是我推出来的):

  1. Cfg.MmoGames 为 null 时 AutoCategory 直接抛 NullReferenceException —— 覆盖层 config.local.json 缺这个键时反序列化就给 null(旧代码里早有这一行,只是从没被撞上)。 现在全链路 null 安全,并在 Config.Load 补了 MmoGames 非空兜底。
  2. MergeScan 信任上游的探测结果 —— 任何没走扫描的条目都会带着过期的 HasFrameGen, 判定跟着错。现在合并层自己重探,不依赖调用者。

验证:tools/link_probe.cs(由 tools/run_link_probe.py 编译运行),在 %TEMP% 造 5 个假游戏 (新 3A / 新竞技 / 对战平台 / 能力字段过期的手动条目 / 无帧生成组件),31 条断言 ALL PASS:

断言 结果
新 3A(有 FG 组件、不在任何名单)→ 分类 aaa、联动档位 aaa PASS
新 3A 不再被判成 fps(旧代码会让 UU远程 被暂停) PASS
新竞技(TurboValorantClone,配置名单里没有)→ fps(关键词兜底) PASS
对战平台 → 档位 "",且不在 AllGameProcesses() PASS
新加的 3A / 竞技 / 手动条目都在 AllGameProcesses()(否则电源与加速包轮不到它) PASS
完全未知的进程 → 不再默认 fps PASS
再合并一次 → 新游戏立刻被认到、档位已算好、旧对象不受影响 PASS
界面层:列表重建后 selected 换成新对象、结论与重建前一致、新对象上确实带上了能力字段 PASS

回归:方案切换 28 条 ALL PASS · 显卡名伪装 9 条 ALL PASS · 14 张渲染无失败 · 真实库 18 条判定表与改动前完全一致(无回归)。

仍未打包(按用户要求)。说一声就出 3.1.0。

㊹ 3.1.0(第 2 批):优化项补齐 5 项 + 一个自我修正

承「优化项盘点与遗漏清单」的 5 条建议,逐条落地。改动集中在 Core.cs(1 处 Dlssg.cs), 不动 UI 结构;能只读的绝不写。

① 刷新率检测(此前零实现)

Native 新增 DEVMODE + EnumDisplaySettingsW,Program 新增 DisplayMode(out w, out h, out hz) / RefreshHz()。

项 说明
为什么要 帧生成的倍率上限由显示器刷新率决定(生成帧超过刷新率就是无效帧)。此前工具连刷新率都不读,"6X 值不值得开"无从回答
本机实测 1920×1080 @165Hz(已是最高刷新率)
交叉验证 探针(C# P/Invoke)与独立 Python 脚本两套实现读出的值一致 → 165

② 体检表:22 项 → 31 项

新增 9 行,全部是"以前写了但看不到"的:

新增行 读的是
显示器刷新率(帧生成上限) EnumDisplaySettings(只读)
鼠标加速(提高指针精确度) HKCU\Control Panel\Mouse MouseSpeed/MouseThreshold1
禁止内核换页出内存 DisablePagingExecutive
网卡节能(挂起/EEE) 网卡类键 PnPCapabilities==24 的个数
已禁用服务数 Services\<名>\Start==4 的个数
全屏优化(独占全屏) GameDVR_FSEBehaviorMode + GameDVR_HonorUserFSEBehaviorMode
进程电源节流 Session Manager\Power\PowerThrottlingOff
优化模块(行为/监控型) 6 个纯行为/监控模块的开关状态(无系统设置可回读)
NVIDIA 驱动版本 当前版本 + 基线比对

③ 按刷新率的倍率建议

新增 Dlssg.RefreshHint(),接在两条"推荐"结论后面。本机输出:

(显示器 165Hz:生成帧超过 165 就是无效帧 —— 基础帧 40 时 4X 就够,6X 要基础帧 ≤27 才不浪费。)

依据是 NVIDIA 官方口径(6X 面向 240Hz+)与超能网实测文("6x 是为 360Hz 以上或 4K 高刷准备的"); 只读刷新率、不猜帧率(本工具不采集帧率)。

④ 补两项系统写入

新写入 位置 本机现状(改动前)
GameDVR_HonorUserFSEBehaviorMode=1 HKCU\System\GameConfigStore 已存在但值是 0 → 只写 FSEBehaviorMode=2 很可能被系统忽略
PowerThrottlingOff=1 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Power 值不存在(= 节流默认开着)

两者都进了 ApplyAll 的备份列表与 RestoreSystemDefaults 的还原范围(还原 = 删除值)。

⑤ 驱动版本变化提示

新增 Program.DriverVerNow() / MarkDriverBaseline():一键优化时记录当前驱动为基线, 版本变化则返回提示(治"更新驱动后第一次进游戏特别卡")。只记录 + 提示,不自动执行任何动作。 体检表只比对、不写入(读函数不产生副作用)。

⑥ 自我修正:新增的体检行不得用 🟡

Ui.cs 的状态分类是「非 ✅ 非 ⚠️ → 红色『需处理』,并计入 warn+bad → 驱动漂移检测与托盘告警」。 我第一版给"用户在配置里关掉某个模块"的情形写了 🟡 —— 那会让主动停用变成永久红色误报, 正好加重"怀疑没生效"这个原有痛点。已全部改为 ✅(已停用/无需设置/全部关闭), 并把这条不变式固化成探针断言(section 2b)。

顺带发现(未改):既有 8 行也用 🟡 表达"已停用/未接入"(游戏联动、定时器联动、加速包、 Windows 游戏模式、内核隔离、帧生成运行时、帧生成接入、全局定时器请求)。按现在的显示规则 它们会呈现为红色"需处理",语义偏重。因涉及既有语义决策、且用户未提出,本轮不动,仅记录。

验证

新增 tools/opt_probe.cs + tools/run_opt_probe.py,36 条断言 ALL PASS:

组 断言
刷新率 可读、>0、与 DisplayMode 一致、与独立 Python 实现一致、范围合理
体检表 不抛异常、项数 31、四列全非空、9 个新增行都在
状态取值不变式 9 个新增行的状态都以 ✅/⚠️ 开头(不会变成红色"需处理")
倍率建议 含本机刷新率、含 ≤hz/6 门槛、含基础帧 40 的结论倍率
驱动基线 首次不误报、重复调用幂等、版本变化给提示(含新旧值与原因);用例结束把基线文件恢复原状
只读纪律 新增的两项系统写入只读不写(写 HKLM 要管理员且会改用户系统,不在测试里做)

回归:方案切换 28 条 · 显卡名伪装 9 条 · 新游戏判定 31 条 · 优化项补全 36 条 —— 全 ALL PASS; 7 页 × 浅/深 14 张离屏渲染无失败;库判定表 18 条与改动前一致。

注意:这两项新写入尚未在本机生效(体检表如实显示 全屏优化 ⚠️ 未应用 / 进程电源节流 ⚠️ 未应用), 点一次「一键优化」即会应用并转为 ✅。

㊺ 3.2.0:核对用户自改的界面 + 补「帧生成生效自检」+ 配色收口(2026-09-16 晚)

用户自己改了界面(Ui.cs 20:05、随后自己编译运行过),要求"看看功能都正常不、还缺啥,完善后打包"。

① 用户改了什么:整套深色配色 + 主题说明

项 改前 改后
深色底 Bg #05070A #0B0F17(沉浸曜石黑)
深色卡片 Panel #12171F #131B26(深空钛灰)
深色悬浮 Hover #1B222C #182230
强调色 Accent #6CC10A(柠檬绿) #00DC82(电竞极光绿),浅色 #10B981
新增 Accent2 / Cyan — #059669 / #38BDF8(科技天青);AccentGrad 深色走 绿→天青 渐变
文字色 旧 slate 值 统一到 slate 系(#F1F5F9 / #94A3B8 / #64748B)

自述视觉系统为「Obsidian Cyber Studio」。核对结论:改动自洽 —— 设置页那段说明里的 #00DC82 / #38BDF8 / #0B0F17 → #131B26 → #182230 与 Theme 的实际取值逐项对得上。 (我第一遍读截图时把 #00DC82 误读成 #00DDC82 并差点当成笔误 —— 小字一律以源码为准。)

② 功能验证:三套判据 + 运行日志

判据 结果
tools/build.py 静态检查 + 编译 PASS(44 个顶层类型)/ COMPILE_OK
接线审计(新增探针模式 wiredump,反射查每个可交互控件挂没挂事件) 65 个控件;49 个 FlatBtn 的 Click 全在
源码静态扫描(每个 new RoundCheck/RoundCombo 找它的消费者) 25 个开关/下拉,0 个死控件
离屏渲染 7 页 × 浅/深 14 张 全部 rc=0,界面结构完好
运行日志(20:06 / 20:08 两次启动) 无异常、无 UNCAUGHT;扫描 18 款、切主题正常、有序退出

顺带纠正一个探针缺陷:wiredump 最初把 23 个控件误报成"未接线"。原因是 ① 自定义控件(RoundCombo)用的是显式声明的事件,编译成同名实例字段,不是框架的 EventXxx 静态键;② 部分控件本就该由「保存」/「应用参数」按钮统一读取(如"使用代理下载")。 已改成"任意相关事件挂了就算接线",并明确以源码静态扫描为权威判据。

③ 配色收口:5 处硬编码 → Theme(逐像素证明零视觉变化)

用户换了整套配色后,暴露出 5 处散落在绘制函数里的硬编码色 —— 它们是针对旧深色配色手调的, 没有跟着 Theme 走(例如 OkSoft 深色 Theme 是 #102A24,代码里却写死 #12201C; 斑马纹写死 #0E141C,而 Theme.Subtle 是 #0E141D,差 1)。

新增 5 个语义字段:OkFill / ErrFill / CheckOn / GridHead / GridAlt, 取值与原硬编码逐位相同,只是不再散落各处 —— 以后换配色改一处即可全局生效。

验收方式:改动前先备份 14 张基线图,改完重渲后逐像素对比 → 差异只出现在动态区域(右下角时钟、CPU 负载百分比、监控曲线), 改动涉及的按钮底 / 表头 / 勾选框区域零差异。

④ 新增功能:帧生成生效自检(Dlssg.FgVerify)

回答那句最常被问的话——"帧生成到底生效没"。代理会把每帧的生成情况写进 jsonl:

{"event":"configuration", …, "max_generated":3, …}     ← 上限(3 → 4X)
{"event":"evaluate", "generated_count":1, …}           ← 每生成一帧一行;取最大值 = 实际倍率

仪表盘「帧生成(DLSSG)」卡片新增一行结论:

最近生效:Cyberpunk 2077 · 实际 4X(09-16 01:35)
最近一次:Forza Horizon 6 · 未见帧生成调用(09-16 17:37)|最近成功:鸣潮 · 实际 2X(09-13 20:31)
尚无代理日志 —— 进游戏跑一局后回来看(代理会自己写记录)

判定规则(以"最近一次代理活动"为准,0 字节日志也有意义 = 代理加载过但没产生生成调用):

最近一份日志 输出
有 evaluate 最近生效:X · 实际 NX(时间) ← 强调色
只有 configuration 最近一次:X · 上限 NX,但没读到生成记录
configuration_error 最近一次:X · 配置被拒(INI 里有代理不认的值…)
0 字节 + 同游戏更早成功过 …未见帧生成调用(时间);上次成功:实际 NX(时间)
0 字节 + 成功记录属于别的游戏 …未见帧生成调用(时间)|最近成功:Y · 实际 NX(时间) ← 不能用"上次"

最后两条是两次修正的结果:一开始只看"最近一份有内容的日志",会把 0 字节的新日志忽略掉 (地平线 6 就会一直显示修复前的旧错误);改成"最新优先"后,又发现跨游戏拼接出的时间线 会被读成同一款游戏的历史 —— 所以同游戏说"上次"、跨游戏说"最近"。

本机实测情报(本次自检读出):2077 的 native_7728.jsonl 里有 135 条 evaluate、 generated_count 最大 3 → 4X 确实跑起来过;而换成 0.3.x 之后,绝区零 / 2077 / 地平线 6 的最新日志全是 0 字节(代理加载了、没产生帧生成调用),鸣潮最近的成功记录是 09-13 的 2X。

⑤ 回归

plan_probe 28 · spoof_probe 9 · link_probe 31 · opt_probe 36 · fg_probe 31(新增) = 135 条断言 ALL PASS;14 张渲染无失败;静态检查 PASS。

㊻ 3.2.1:把「关闭窗口时询问」从优化项搬进设置页(用户:"设置界面也没法改啊")

问题:用户点 X 时不再被询问(因为以前勾过「记住我的选择」),想改回来却发现设置页里没有入口 —— 该开关被塞在「性能优化页 → 自定义项」对话框的 17 项列表里(第 17 项,与「电源计划」「Defender 排除」并列)。

这是分类错误:关闭窗口行为属于「程序自身行为」,不是「系统优化项」。用户找不到是必然的。

改法

设置页新增独立分区「窗口与托盘」,一个三态下拉:

选项 含义 写入的配置
每次询问 弹框让你选「最小化到托盘 / 退出程序」 ui.closeAsk=true(closeToTray 保留作为弹框默认选中项)
直接最小化到托盘 点 X 就收进托盘,联动与告警继续跑 ui.closeAsk=false + ui.closeToTray=true
直接退出程序 点 X 直接退,联动与告警一并停止 ui.closeAsk=false + ui.closeToTray=false

配套三处:

  1. 「自定义项」对话框删掉该行(17 → 16 项)—— 它不是优化项,混在里面用户找不到,也不该由优化流程执行。
  2. 询问框文案改指向:原来写「到『自定义优化项』里重新勾上」,现改为「到『设置 → 窗口与托盘』把 『关闭窗口时』选回第一项」。
  3. 弹框勾「记住我的选择」后同步下拉(RefreshCloseCombo())—— 否则弹框改了配置、设置页还显示旧值, 两处不一致会让人以为没生效。

实现要点:三态 ↔ 两个配置字段的映射收在 CloseComboIndex() / CloseComboText() 一处, 设置页下拉、关闭流程、日志文案共用它。下拉项文案与 CloseComboText() 的输出逐字一致 (探针 close_probe 专门盯这条 —— 两处各写一套就会出现「日志说 A、界面显示 B」)。

验证:新增 tools/close_probe.cs(16 条断言 ALL PASS)

组 断言
映射全组合 (询问=T,托盘=T)→0、(询问=T,托盘=F)→0、(询问=F,托盘=T)→1、(询问=F,托盘=F)→2
下拉实况 字段存在 / 恰好 3 项 / 三项文案与 CloseComboText() 逐字一致 / 初值与真实配置一致
分支等价 选中任一态 → 用同一映射能反推回同一行为(覆盖询问 / 托盘 / 退出三种)
同步回调 RefreshCloseCombo() 存在,且在下拉被 Dispose 时调用也不抛异常

全程只读 + 反射,不触发 SelectedIndexChanged(那会写用户真实的 config.json)。

顺带修掉一处探针自伤

opt_probe 的驱动基线往返测试原先直接操作数据目录里的真实基线文件 (%ProgramData%\Fluxion\nv-driver.txt)。两处问题: ① 那是用户真实数据;② 本机该目录对普通权限只允许新建、不允许覆盖(实测覆盖直接 Errno 13), 所以探针在非管理员下必然失败 —— 那是环境限制,不该记成 FAIL。

改法:Core 给基线路径加一个 internal static DriverFileOverride 测试沙盒出口,探针把路径指到 %TEMP%\gb_drv_test\ 做完整往返,并新增一条断言验证真实文件未被改动(610.88 → 610.88)。

㊼ 3.2.2:换掉图标(手写多尺寸 ICO)+ 一个只有 Win32 才能抓到的坑(2026-09-16 晚)

用户从方向 A 的 6 个受控变体里选了 A1(推进器小方兽 · 机身绿 / 喷焰青)。

① 为什么必须换

旧 icon.ico 是上一套配色的产物(黑底 + 紫青渐变闪电 + 盾形芯片轮廓),与用户新做的 Obsidian Cyber 主题(极光绿 #00DC82 + 曜石黑 #0B0F17)已经完全不是一套; 且 32×32 下盾形边框与芯片针脚糊成一团。

② 裁切决策(用数据定,不靠感觉)

先把 A1 量清楚:角色最右 x=819,身体下沿出血到 y=1024,青焰顶端 y=104, 生成端水印在 x≥903 / y≥963(即角色之外的纯背景区)。

变量 取值 依据
裁切范围 (0, 14, 860, 874) → 860×860 右侧留 41px(>819)、青焰上方留 90px 头部空间;身体下沿照旧出血,与原构图一致
圆角 20% 满幅直角的版本在 16px 下像"被裁的照片";圆角让它在任何桌面上都有明确轮廓
留白 不留 实测 8% 留白会浪费小尺寸的可读面积

候选对比图(含「角落 × 两色分配」「满幅/圆角/留白/圆形」三组变量)留在 icon\candidates\_preview_crop.png、_preview_headroom.png、_preview_32px.png。

水印的处理方式:避开,而不是擦除。 水印在角色之外的背景区,所以把它裁到画面外即可 —— 没有做任何修补/美化,构图与像素来自原图本身。这条很重要:生成端加的"AI生成"水印, 能用裁切解决就不要用修复工具,前者不引入任何发明出来的像素。

③ 坑:全部帧都用 PNG 压缩 → PIL 读得动,Windows 读出来是垃圾

第一版 ICO 容器我把 7 帧全写成 PNG(Vista+ 支持,且体积小)。PIL 回读完全正常, 但走 Windows 自己的图标 API 一验就露馅:

取样 RGB(81,26,235)  ← 紫色
取样 RGB(197,186,144) ← 土黄
角 alpha = 112 / 95 / 93 / 189   ← 圆角应该接近 0

我的图里根本没有紫色和土黄。 原因是 GDI+ 对"非 256 的 PNG 帧"不认 —— 它会按 DIB 的布局去解 PNG 数据,于是整幅错位成噪声。

正解(业界标准做法):

帧 容器格式 体积
16 / 24 / 32 / 48 / 64 / 128 DIB(40 字节 BITMAPINFOHEADER + 自下而上 32 位 BGRA + 1bpp AND 掩码) 1.1KB … 67.6KB
256 PNG 56.8KB

改完再验,取样变成角色真实的 RGB(5,174,110)、四角 alpha 0–3、exe 内嵌图标与文件帧逐像素 0 差异。

⚠️ 教训(这条最值钱):"第三方库能读"不能作为验收依据。 PIL 对 ICO 的宽容度远高于 Windows,PNG 帧它照读不误。 交付给 Windows 的图标,必须走 Windows 自己的 API(System.Drawing.Icon / ExtractAssociatedIcon)验一遍,并把 ICO 目录按字节解析验一遍容器结构。 新增 tools/icon_probe.cs + tools/run_icon_probe.py 固化这两条(40 条断言 ALL PASS)。

④ 另一个实测结论:GDI+ 请求 256 会回退到 128(不是文件问题)

用三种容器分别试(我们的 PNG256 / 我们的 DIB256 / 上一版旧图标), 请求 256 全部返回 128 → 是 System.Drawing.Icon 的上限。Explorer 走 shell 自己的加载器, 能用到 256 帧;Program.GetAppIcon()(托盘)本来只用 16/32。所以这不是缺陷,探针里如实标注放行。

⑤ 落地范围

icon\icon.ico(7 帧 / 158,991 B,旧文件留档 icon.ico.bak-oldtheme、icon.ico.bak-pngframes) 一处替换,四处自动跟随:exe 内嵌图标(build.py 的 /win32icon)、 安装包图标(.iss 的 SetupIconFile)、托盘与窗体(Program.GetAppIcon() → ExtractAssociatedIcon)、 {app}\icon\icon.ico(安装包会拷进去)。

回归:7 个探针 192 条断言 ALL PASS(方案切换 28 / 显卡名伪装 9 / 新游戏判定 31 / 优化项补全 37 / 帧生成自检 31 / 关闭窗口 16 / 图标端到端 40),14 张离屏渲染无失败。

㊽ 3.3.0:修「绝区零失去帧生成」的洞 + DLSS 5 NR 默认关 + 方案A 不再只写一个游戏

用户反馈三件事:鸣潮进不去(UE4 GPUCrash)、绝区零没有帧生成、方案 A 区块里「为什么只写一个鸣潮」, 外加一个疑点「每次启动鸣潮时默认把 DLSS5 打开了」。

① 鸣潮崩溃:是 DLSS 5 神经渲染(NR)与 4X 帧生成同跑

Saved\Crashes\...\CrashContext.runtime-xml:

CrashType    GPUCrash
ErrorMessage Fatal error: [File:Unknown] [Line: 690]
             hr failed at .../D3D12RHI/Private/D3D12Resources.cpp:596
             with error DXGI_ERROR_DEVICE_REMOVED with Reason: DXGI_ERROR_DEVICE_HUNG

两次(21:31 / 21:47),都在启动鸣潮后约 1.5 分钟。崩溃前的 OptiScaler.log 显示 NR 一直在跑 (DlssNr.Enabled: true 是每次启动自动生效),21:46:38 帧生成 feature 创建成功,9 秒后日志停止。

根因链三层叠加:

  1. [DlssNr] Enabled=true 被 2.9.1 固化 —— 用户那句「每次启动鸣潮时默认把 dlss5 打开了」就是这个。 NR 每帧跑一遍 158 MB 模型,RTX 20/30 走更重的 FP16 路径。
  2. 上游文档自己写明风险:wilsjo2/OptiScaler-DLSSNR-PreSR-Multipass([DlssNr] 段的真正出处) 的设计说明里有一条 「Never free under the GPU … Both rules were paid for with device hangs」。
  3. 驱动门槛不满足:该文档要求 驱动 ≥ 616.56;本机 610.88。
    (2026-09-16 23:30 撤回这一条 —— 我读错了,见下方「驱动门槛纠正」) ≥616.56 只对 RTX 50 是硬门槛;对 3060 Ti 不成立,610.88 不是崩溃的原因。

处置:[DlssNr] Enabled 改回 false(备份 OptiScaler.ini.bak-nr-20260916-230749), 并在工具里做成可调项(帧生成参数 → 「DLSS 5 神经渲染 NR」下拉,默认「关闭(推荐)」)。 关闭 NR 不影响帧生成。

② 绝区零没有帧生成:ini 被删后永远补不回来(我自己的 bug)

时间线(工具日志):用户 21:25:51 点了「关闭帧生成」→ 回执明写 「已移除 dlssg_sm86.ini;已清理日志目录」(删除,不是停放)。之后 21:25:58 与 21:27:38 两次 「切换到本方案」,走的都是 Plan.SwitchTo 的快速路径——入口 DLL 还在(只是被停放成 d3d12.dll.parked.030),于是「改回原名、秒完成」,谁也没重新补上配套文件 → dlssg_sm86.ini 永久缺失 → 代理读不到配置、连 dlssg_sm86\logs 都不建 → 游戏里始终没有帧生成选项。

两处修复:

  • Dlssg030.EnsureFactoryIni:原来 if (!File.Exists(ini)) return(文件不在就直接放弃)→ 改为从出厂包补回 2099 字节版(含 [General] Enabled=1)。
  • Plan.SwitchTo:切到 0.3.x 之后无条件调一次 EnsureFactoryIni(dir), 不再假设"入口到位就万事俱备"。

已现场修复:绝区零目录补回 dlssg_sm86.ini(2099 B)。

顺带一条实测现象(重要):21:26:03 写入伪装 → 21:27:06 重启显卡设备 → 21:27:44 启动绝区零 → 21:28:10 游戏日志读到的仍是 "RTX 3060 Ti";而更早那次「导入 .reg + 重启电脑」之后, 游戏日志确实读到了 "RTX 5090"(03:46:48 / 10:50:42 两条)。 → 改完伪装请重启电脑;「重启显卡设备」不足以让它生效(界面说明已按此更正)。

③ 方案A 不再把游戏名写死在标题里

原标题 帧生成方案 A · OptiScaler 注入(鸣潮) + 正文写死「绝区零不适用本方案…鸣潮 = dxgi 入口…」—— 用户质问「为什么只写一个鸣潮」。改为:

  • 标题:帧生成方案 A · OptiScaler 注入(不带游戏名)
  • 正文:新增 Dlssg.OptiNote(选中游戏),切游戏就变,且带上该游戏的 NR 状态 —— 绝区零 → 「不用本方案,改用签名版 0.3.x + 显卡名伪装」;鸣潮 → 「本方案在用,dxgi 入口…」。

④ 本轮联网核实的帧生成方案知识(写进「说明」页)

事实 来源
NR 段的出处是 wilsjo2/OptiScaler-DLSSNR-PreSR-Multipass(基于 OptiScaler + Dagherbou 的 NR fork,配色合成都取自 RenoDX) 该项目 README
硬前提(仅 RTX 50):驱动 ≥ 616.56;RTX 20/30/40 用 ShortFuse 跨代 310.8 运行库(E67DEE20…),旧驱动也能跑 INSTALL-DLSSNR.md + DLSS5-Swapper src/core/install-guards.js
起步姿势:先关 NR + 2X 帧生成,再逐层加 DLSS-FRAME-GENERATION.md
NR 的设计约束原文「were paid for with device hangs」 OptiScaler/dlssnr/README.md
官方 0.9.x 不含 NR(NR 只存在于社区 fork)→ 升级官方版会把 NR 弄没 OptiScaler releases
Reflex 的 FPS 上限会连带限制生成帧(150 的限制在 4X 下把渲染压到约 37.5fps),提倍率不会抬高这个上限 同上
菜单能开但点不动 → 试 [Hotfix] ManualInputPolling=true,并排除冲突的覆盖层 INSTALL-DLSSNR.md

验证:8 个探针 240 条断言 ALL PASS(方案切换 28 / 显卡名伪装 9 / 新游戏判定 31 / 优化项补全 37 / 帧生成自检 31 / 关闭窗口 16 / 图标 40 / NR 与切换 24(新增)); 下拉反射探针 18 条 ALL PASS;14 张离屏渲染无失败。

㊾ 3.3.1:驱动门槛纠正 —— 「≥616.56」只对 RTX 50 成立,别为 NR 升级驱动

用户问「616.56 或者以上的驱动有哪一个适合 3060ti」。查完之后发现 我在 3.3.0 里把这条读错了, 并且写进了源码 6 处 + 本文档 2 处。这是本轮最重要的产出:纠正一个会导致误操作的结论。

① 我错在哪

DLSS5-Swapper(rakanki911,★4.8k)的 src/core/install-guards.js 里有两个常量, 我上一轮只摘了第一个去看:

const OPTI_DRIVER = 61656;          // 我据此写成"驱动 ≥616.56 是硬门槛"  ← 错在这
const NEURAL_FAULT_DRIVER = 61664;  // 这个才是"任何 N 卡都要避开"的

但 OPTI_DRIVER 的判据是 driverSupported:

function driverSupported(rows) { return rows.some(row => blackwell(row) && driverNumber(row) >= OPTI_DRIVER); }
//                                                      ^^^^^^^^^^^^^^^^  ← 必须是 RTX 50

而它上方的注释写得更直白(原文):

Two separate questions, because only one of them is a hard requirement. The card is: DLSS-NR runs on the RTX 50 path. The driver is not: the model file (nvngx_dlssnr.dll) ships with this app rather than being taken from the installed driver, so an older driver still runs an OptiScaler install — which is what people who rolled back off 616.x to keep RenoDX working are seeing.

→ 模型随应用提供、不取自本机已安装的驱动,旧驱动照样能跑。 对 3060 Ti(Ampere)根本不是门槛。

② 那真正该避开的是什么

// Measured upstream by the Feeder author across three machines: with the RenoDX DLSS 5 consumer (v4.6 and
// v4.7), every neural evaluate faults inside NVIDIA's own NGX runtime on 616.64 and 616.86, while 616.56
// completes. Reported as DLSS5-Feeder issue #54. This only warns ...
const NEURAL_FAULT_DRIVER = 61664;
function driverNeuralFault(rows) {
  return (rows || []).some(row => /nvidia|rtx|gtx/i.test(row.name) && driverNumber(row) >= NEURAL_FAULT_DRIVER);
}

注意它的正则是 /nvidia|rtx|gtx/i —— 对任何 N 卡生效,不限于 50 系。 所以约束是「≥616.56 且 <616.64」,也就是只剩 616.56 一个版本。

③ R616 版本序列与对 3060 Ti 的适配

版本 日期 3060 Ti NR 备注
610.88(本机) 07-22 ✅ 能跑(模型不取自驱动) 现状,已实测鸣潮 6X + NR 可用过
616.56 08-26 ✅ 支持清单内 ✅ completes 唯一"达标且能跑完"的版本
616.64 09-03 ✅ 支持清单内 ❌ fault NBA 2K27 首发 DLSS 5(那是给 50 系的)
616.86 09-04 ✅ ❌ fault Hotfix,不通过 NVIDIA App 推送
616.92(最新) 09-09 ✅ ❌ fault 007 First Light 等

④ 结论:不要升级(对这台机器)

不是"哪个版本最好",而是唯一达标的那版恰好有两个对你致命的已知问题:

616.56 的已知问题 对这台机器的影响
升级后远程桌面(RDP)黑屏 🔴 你常驻 UU远程(GameViewer)
升级后无法创建虚拟显示器 🔴 远控/虚拟屏软件依赖这个
「优先最高性能」电源模式可能不生效 🟡 工具 NVAPI 竞技档就写这一项

修好前两条问题的 616.86 hotfix,以及最新的 616.92,都 ≥616.64 → NR 会 fault。 也就是说:想同时要「NR 可用」和「远控正常」,在 R616 里无解。

而且更根本的一点:这次鸣潮 GPUCrash 不是驱动版本造成的 —— 崩溃真因是 NR 与 4X 帧生成同开 (上游明写其约束「were paid for with device hangs」),升级驱动解决不了。NR 已改为默认关闭并做成开关。

⑤ 已同步修改的位置

源码 6 处(Core.cs 字段注释 / Ui.cs cbNr 注释 / Ui.cs 说明页前置条件 / Dlssg.cs NrHint / Dlssg.cs 说明页 / Dlssg.cs PrereqReport),以及本文档 2 处。

其中 PrereqReport 的驱动行改为调用新增的 Dlssg.NoteDriver(drv),按档位给结论:

  • < 591.86 → 低于作者下限,先升级
  • 591.86 ~ 616.55 → 正常区,明确告诉你"不必为了 NR 升级"
  • 616.56 → 唯一既达标、NR 又能跑完的版本
  • ≥ 616.64 → 警告 NR 会 fault,要用 NR 就回退到 616.56

教训(写给以后的我):看到源码里的阈值常量,必须连同它的判定函数一起读。 只摘常量不读判据,就会把一个"只针对 A 类硬件"的门槛说成"人人都要满足", 然后据此劝用户做一个既没收益、又带已知风险的升级。

㊿ 3.3.2:分享友好化 —— 出厂模板去个人残留 + 推荐引擎显卡代际门禁

用户问「能分享给别人吗?会根据别人的显卡和电脑状况来做推荐吗」—— 核实后两个答案都需要补课。

① 安装包模板里的个人残留(会直接坑到别人)

installer\Fluxion.iss 原来打的是项目根 config.json —— 那是便携模式配置,里面带着本机个人设置:

键 本机值 对别人意味着
dlssg.useProxy=true + proxy=http://127.0.0.1:7890 本机 FlClash 「下载运行时」必失败(别人没有这个代理)
gameAware.remoteApps 用户的 UU远程 exe 路径 + 长注释 机器特定,无意义
ui.closeToTray=true 用户点过「记住我的选择」 别人点 X 直接收托盘且不知为何
cleanup.killList 用户的 MuMu/百度网盘名单 个人习惯
dlssg.maxGeneratedFrames=3 用户选的 4X 出厂应为 2

修:新增 installer\config.default.json(由根 config 生成、上述键全部回出厂值),.iss 改指向它。 根 config.json 原样保留(它仍是便携模式配置)。安装器语义不变(onlyifdoesntexist,不覆盖已有配置)。

② 推荐引擎的显卡代际门禁(之前完全没有)

BuildRec / RecommendCategory 原来只看游戏类型与组件,不看对方的卡: 给 RTX 40/50 用户照样推「一键应用 0.3.x + 伪装」——他们原生就有帧生成,且把 4090 改名 5090 是降级; 给 GTX / AMD / Intel 用户也照样推荐 —— 装了也不会有任何选项。

新增(Dlssg):GpuTierFromName(name)(纯函数,可测)→ GpuTier()(读 Program.GpuDisplayName, unknown 不缓存——WMI 探测可能未完成)→ GpuGateRec(tier)。BuildRec 在 platform/fps 分支之后、 gacha/aaa 分类之前过门禁(platform/fps 本来就"不推荐安装",结论与显卡无关,保持不变):

对方显卡 推荐结论
RTX 40 / 50 中性灰「不用装:这张卡原生支持 DLSS 帧生成」+ 点名「伪装对 40 系是降级」,按钮禁用
GTX / 其他 NVIDIA 红色「用不了:不在 RTX 20/30 系支持范围」,按钮禁用
AMD / Intel 红色「用不了」+ 指向游戏自带的 FSR3 / XeSS
RTX 20 / 30 不拦 —— 原有推荐不变(本机 RTX 3060 Ti 实测回归通过)
未识别(探测中/未知) 不拦 —— 不误伤

PrereqReport(前置条件检查)补「显卡代际」一行。顺带:Program.GpuName 是多变量声明 (string CpuName = …, GpuName = …),自研静态检查器只登记首个变量名 → 跨类引用被误报 UNRESOLVED;加单声明属性 Program.GpuDisplayName 绕开(不动检查器)。

③ 两个探针教训(这轮各踩一次)

  • 补丁脚本 sys.exit(1) 在 save() 之前 → 前面的 rep 全部没落盘,而后续探针读到的是 上一轮的旧结果文件,"222 PASS" 是假的。多步补丁必须先写完再退出;探针结果文件要核对时间戳。
  • 探针加载真实配置 → 用户 config.local.json 里 genericEntry=d3d12.dll(UI 下拉选过), plan_probe section 9 断言 version.dll 必 FAIL。沙盒探针必须显式固定被测输入,不该信任宿主机器的配置。

④ 关于分享,还有一件事没做(如实说明)

安装包 2.6MB,不含帧生成资源包(0.3.x 签名版 + OptiScaler 套件 ≈ 270MB,在数据目录)。 对方装完后帧生成功能需要资源包才完整;「下载运行时」按钮目前只覆盖旧版运行库的下载源。 要么把资源包打进安装包(体积会到 ~272MB),要么给 0.3.x/OptiScaler 加下载源 —— 都等用户拍板。

验证:8 探针 238 条断言 ALL PASS(link_probe 新增 16 条门禁断言),14 张渲染无失败。

8. 风险声明

  • 本工具不修改游戏本体、不注入竞技游戏、不以绕过反作弊为目的 —— 代理替换的是游戏自身的 DLSS 加载链,能否生效仍取决于反作弊是否放行
  • 帧生成模块仅建议用于单机 3A;在带内核级反作弊的网游上使用第三方 DLL 代理有封号风险,请自行判断
  • Defender 排除是安全权衡项,默认关闭
  • VBS/内存完整性只做检测不代改 —— 关闭它会降低系统安全基线,需要时请自行在「内核隔离」里操作