news 2026/8/28 6:24:13

自由学习记录(222)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自由学习记录(222)

目前的狀態

已经不像 RenderDoc 的:

• CTWResearcher.dll / .exe
• 导出 CTW_*、CTW_App_GetAPI(不再是 RENDERDOC_GetAPI)
• 版本资源 ProductName
• Vulkan 层名 VK_LAYER_CTWRESEARCHER_Capture
• 全局 hook 的 file mapping 名

进了目标进程之后,Windows 和模块内容里仍然是 RenderDoc 的,按对方容易拿到的顺序:

1. DLL 映像里的明文

LdrLoadDll 钩子不只看路径。它可以在放行前读文件或映射后扫 .rdata。你这份 DLL 里还大量留着:

• C++ RTTI:class RenderDoc → 二进制里是 .?AVRenderDoc@@
• 路径字面量 %TEMP%\RenderDoc\*.rdc / *.log
• renderdoc.conf
• 注册表 RenderDoc.RDCCapture.1
• 源码路径、断言、__FILE__,以及工程目录名 renderdoc-1.x
• PDB 路径,只要 PE debug 目录没剥干净

文件名已经叫 CTWResearcher.dll,内容扫描照样命中。这是「改名了却仍失败」最常见的原因,也不需要扫全进程内存。

2. 进程在 Windows 里留下的活对象(DllMain 里立刻就会做)

注入成功后 RenderDoc::Initialise() 会:

• 在 38920–38927 上 listen。这是 RenderDoc 的固定控制端口。目標在本进程里看 TCP 表,或自己先占 38920,几乎零误报。
• 在 %TEMP%\RenderDoc\ 下建目录、写 log/rdc。
• Present 上画 "Capturing D3D11" / "F12, PrtScrn to capture"。这是公开的 UI 指纹,查 overlay 或挂钩后的交换链就能认。
• 挂钩 DXGI/D3D 虚表。不查名字也能发现 Present 被换掉。

这些都是「进程、DLL、Windows 互相说话」时漏出去的,不是文件名。

3. 你自己这条链路里,也还在用旧名字找对方

即使游戏放行了,CTW 内部还有几处在认 RenderDoc 的壳:

• 找 UI:RenderDoc.RDCCapture.1\DefaultIcon
• 捕获/日志目录:RenderDoc\
• 配置:renderdoc.conf
• renderdoccmd 窗口类
• crash dump 目录名 RenderDoc

表现为:注入器以为成功了,目标控制连不上、F12 拉不起 UI、捕获文件写到另一边在找的目录。看起来也像「没成功」,但锅在自己改名改一半。

之前的改名只改了“表层”——PE 导出符号名、文件名、Replay Marker、Vulkan layer。但没改“内容”——字符串字面量、shader 里的函数名、日志文案、路径、__FILE__源码路径。

扫描当前 CTWResearcher.dll,明文残留是: renderdoc 601 次 renderdoc-1.x 277 次 <- 源码路径 __FILE__ RENDERDOC 139 次 RenderDoc 90 次 样本分类很清楚: Shader 源码函数名: RENDERDOC_PixelHistoryCopyPixel RENDERDOC_QuadOverdrawPS RENDERDOC_MeshGS / TriangleSizeGS / HistogramCS ... 日志/断言字符串: !!!!RenderDoc Internal: Replay %d !RenderDoc::Inst().IsReplayApp() 源码绝对路径: E:\college_researcher\renderdoc-1.x\renderdoc\driver\d3d11\d3d11_resources.h 路径字面量: /RenderDoc/RemoteServer_Client.log /RenderDoc/RemoteServer_Server.log /files/renderdoc.conf /renderdoc_report_%H%M%S.zip /../share/renderdoc/plugins /libVkLayer_GLES_RenderDoc.so

目标进程的LdrLoadDll钩子不用看文件名,映射后扫一下.rdata,甚至直接读文件搜"renderdoc"这个子串,601 处命中,直接拒绝。这就解释了“文件已经叫CTWResearcher.dll,却还是失败”。不是 hook 拦了什么高深东西,是内容一眼就是 RenderDoc。

3. 控制端口还是原版的 38920 / 39920。这不是字符串 renderdoc,但有人按「是不是 RenderDoc」去扫端口时仍对得上。
4. 版本信息里 CompanyName 仍是 Baldur Karlsson。普通模块名扫描不会碰这个;看文件属性的人能看到。

注意EXE 和 DLL 不是同一批编出来的,导入名对不上。這個情況,編了一些另一些沒編,

episode

在日常英語中,episode指的是一個有起承轉合、相對獨立的事件或時期。它不一定跟電視有關:

  • 情緒/病情發作:醫生會說 "a depressive episode"(抑鬱症狀發作期)或 "a psychotic episode"。這指的不是「一集電視」,而是生活中「某一段特定發作的時期」。
  • 人生的一段插曲:如果你跟前任有一段荒謬的分手故事,你可以說:"That was a crazyepisodein my life."(那是我人生中一段瘋狂的小插曲)。

為什麼電視要用episode

電視影集之所以用episode,也是延伸了這個「插曲/片段」的概念。

  • 影集中的每一集,就像是主角漫長人生故事(Series/Show)中,某個特定發生的小事件、小插曲
  • 每一集通常有自己獨立的事件和結局,拼湊起來才是完整的人生。

補充:其他中文翻成「集」的英文字

  • Episode:強調大故事中的「某個事件/插曲」(如:美劇的一集)。
  • Volume (Vol.):通常指書本的「第幾卷/第幾冊」,動漫或輕小說常用。
  • Issue:指期刊、雜誌、漫畫的「第幾期」

git 里 deleted + untracked 是目录重命名,不是删功能

git add -A中的-A代表All(全部)

.txt / .patch 是刚用 git diff --cached 写到项目目录里的拷贝。

在信息和 git diff --cached 完全相同这个条件下,加了后缀的三个都没用。

完整信息只有 03-full.patch。

• --stat:只剩每个文件改了几行,hunk 没了
• --name-status:只剩 M/R/A 和路径,更少
• core:文件集合都变了,信息既不全也不一样

它们都是另一份摘要或切片,不能代替 full。要同等信息量,看那一份默认补丁就够。

NVIDIA 的利潤率極高(毛利率長期在 70% 以上),等於把大量利潤從 OpenAI、xAI、Google、Meta 這些公司口袋裡拿走。

AI 公司現在最燒錢的就是算力,推理成本尤其是長期營運的關鍵。如果能自己做芯片,把這塊成本壓下來,對利潤和定價權影響巨大。

.rdc 能抓到。 抓的是那一帧 GPU 实际跑过的东西:VS 字节码、常量缓冲(时间、风速)、顶点缓冲、这次 draw。Mesh Viewer 里一般能看两层:

• VS Input:模型原顶点,通常不带风
• VS Output:这一帧被 VS 吹过之后的位置

所以能确认「是不是 VS 在动、用了哪些参数」,不是只能看到静模型。

限制也清楚:一份 rdc 是一帧快照,草会停在被抓住的那个姿势,不会在回放里自己持续晃。时间变量冻在当时 CB 里。要看运动过程,得连抓几帧,或者对着 VS 和那些时间参数看。

注入失败发生在 DLL 跑起来之前的话,DLL 里还剩什么字符串都还没轮到。全替换清的是映射之后才能扫到的明文;成品这边拦的是加载动作,和 renderdoc 扫没扫到不是同一层。

.symfix+ C:\symbols:設定微軟公開符號伺服器的下載路徑,並將下載的.pdb除錯檔存在C:\symbols。這能讓 WinDbg 把記憶體中的機器碼位址(如0x7FFA1234)翻譯成你認識的函式名稱(如LdrLoadDll)。

.reload:強制偵錯器依據剛剛設定的路徑,重新載入該行程所有模組(.exe / .dll)的符號。

通过启动器勾选“DX11”--1-16,本质上是在修改启动器的配置文件或传递启动参数-2。这个设置一旦保存,就会被记录在启动器的配置里-2。

多游戏引擎(如Unity、UE4/UE5)支持通过命令行参数指定图形API。例如,为游戏EXE的启动参数添加-dx11-force-d3d11

python rename_branding.py .

--dry-run可以先只看计划改动,不实际写文件。

覆盖范围
它从根目录递归遍历整个仓库,但有三类例外:

  • 跳过.git.hg.svn__pycache__
  • 只处理文本文件,.dll/.exe/.ico/.png等二进制不动
  • 不处理脚本自身

它会同时改:

  • 文件内容里的renderdoc
  • 文件名里的renderdoc
  • 目录名里的renderdoc

另外它的覆盖:只替"renderdoc"

  • 不处理baldurkCrytekgithub.com/baldurk
  • 不处理图标资源
  • 不处理已编译的 Release 二进制

“换图标”本身不需要重新编译 C++ 源码,只需要重新生成资源并重新链接对应的 EXE

流程是这样的:

  • 图标文件icon.ico.rc引用
  • 改动图标后,rc.exe.rc编译成.res
  • linker 把.res链接进 EXE

文件同时被 GUI 和 Cmd 的.rc引用:

替换后只重编这两个输出:

msbuild qrenderdoc\qrenderdoc_local.vcxproj /p:Configuration=Release /p:Platform=x64 ... msbuild renderdoccmd\renderdoccmd.vcxproj /p:Configuration=Release /p:Platform=x64 ...

CTWResearcher.dll不需要重编,因为图标没嵌在核心 DLL 的.rc里。

60×60 做 128/256 会被放大,高 DPI 下可能不够清晰。如果只是先验证注入,16/32/48 就够;如果要正式报告,最好再提供一张 512×512 的图,生成更干净的 ICO。

把 temp 里的RenderDoc目录名改成了xxxxxx,不是“不写 log”。

能找到并加载 PDB 是最幸福的事——它能极大缩短你理解某段 Shader 或某个 DrawCall 在干什么的时间。

问题不是「这两种写法会不会让 CreateProcess 失败」,而是「这两种写法会不会让第 2 步的 LoadLibraryW 根本没被跑到」。

LaunchAndInjectIntoProcess 实际顺序是:

1. CreateProcess(..., CREATE_SUSPENDED) — 进程已经建好,主线程还没跑
2. InjectDLL — 在这个冻结进程里触发 LoadLibraryW(capture.dll)
3. FindRemoteDLL — 看模块列表里有没有这份 DLL
4. 调 CTW_Implant_* 配路径/选项
5. ResumeThread — 游戏这才开始跑

UI 上的 Failed to inject ...dll 出在第 3 步。CreateProcess 已经成功了,exe 作为普通程序是能建起来的。失败的是:capture DLL 没进目标进程。

RenderDoc 1.44 → 1.46的差距。

2. 注入 DLL 的方式不一样,这是最大嫌疑
CTW 当前在 [win32_process.cpp (line 252)](E:/college_researcher/ctwresearcher-1.x/ctwresearcher/os/win32/win32_process.cpp:252) 里:

  • VirtualAllocEx分配PAGE_EXECUTE_READWRITE
  • 写入一段 54 字节自定义 x64 shellcode
  • CreateRemoteThread的入口点是这段 shellcode
  • shellcode 再调LoadLibraryWGetLastError

Duck 在它的 [win32_process.cpp (line 252)](E:/college_researcher/RenderDuck-1.4-source/RenderDuck-1.4/riderduck/os/win32/win32_process.cpp:252) 里:

  • 直接VirtualAllocEx放 DLL 路径
  • CreateRemoteThread的入口点直接就是kernel32!LoadLibraryW
  • 不写自定义 shellcode,也不回读结果

对目标进程来说,这是一个很明显的可区分点:

  • Duck 的远程线程入口位于已加载模块kernel32.dll
  • CTW 1.46 的远程线程入口位于一段自定义可执行内存

如果目标按“线程起始地址是否属于已知模块”做校验,CTW 的shellcode 注入会比 Duck 更显眼

3. Duck 还多改了 hook 层
Duck 的 [win32_hook.cpp (line 52)](E:/college_researcher/RenderDuck-1.4-source/RenderDuck-1.4/riderduck/os/win32/win32_hook.cpp:52) 里额外加了:

  • ApplyExportDetour
  • 用 Detours 对 D3D/DXGI 导出函数做 export detour
  • 额外 hookkernelbase.dll
  • 不只 hookkernel32.dll

这些不是“品牌改名”,是实际行为差异。CTW 目前没有这部分。

VirtualAllocEx 分配 PAGE_EXECUTE_READWRITE 内存,是远程代码注入和恶意软件分析中一个非常关键且敏感的操作。它意味着在目标进程中分配一块可执行、可读、可写的内存区域。

GPA MCP,是作者为了将Intel GPA这个强大的图形分析工具接入 AI 工作流(如 Codex)而开发的一个 MCP 服务

.gpa_frame

把 AI 的请求转交给一个已经打开并加载了该文件的 RenderDoc 进程qrenderdoc.exe

按你设的条件(必须能断定是 RenderDoc,还不能误伤别的软件),这段 54 字节 stub 没有机制上的排他身份。它不是一份能枚举到的 DLL,模块列表里也没有它。

这段 stub 目标进程实际能看到什么

它不是模块,只是一块匿名内存加一个远程线程:

• 起始 RIP 不在任何已加载映像里
• 那块内存是新分配的 PAGE_EXECUTE_READWRITE
• 内容是代码,不是路径字符串
• 跑完就被 VirtualFreeEx 掉,连常驻模块都不是

在「不能误伤」这个约束下,这些都不够。CreateRemoteThread、远程 RWX、线程入口不在模块里,是一整类加载器都会留下的痕迹,overlay、调试器、别的注入器都有。拿这个当 RenderDoc 检测,误伤面太大。

指令模板本身也不构成「这就是 RenderDoc」:

• 中间 16 字节是运行时填进去的 LoadLibraryW / GetLastError 地址,开机 ASLR 一变,整段 54 字节就不能当固定哈希
• 能稳住的只有骨架:sub rsp,0x28、两次 call rax、往 [rbx+0x208] / [rbx+0x210] 回写
• 0x208 只是 MAX_PATH * sizeof(wchar_t),这是很普通的路径缓冲区布局
• 这段 stub 官方 RenderDoc 和 Duck 都没有。按 1.44/1.45/Duck 写的检测器,本来就不会找它

所以:它最多能被认成「有人用了一个带错误回写的 LoadLibrary 小助手」,不能排他地认成 RenderDoc。你说的对——除非目标已经在专门对着 CTW 这份实现做字节级特征,而这正好违反「不能误伤、且必须能断定是 RenderDoc」。

「一段手写的机器码字节,写进别人进程里直接当入口执行」,不是编译进某个 DLL 里的函数。

最早这个词指用来弹出 shell 的载荷。后来泛化了:只要是这种不落在模块里的裸指令块,都还叫 shellcode。CTW 这份 54 字节 stub 就是这个形态,所以代码里也写成 shellcode[]。它并不弹 shell。

git commit -a(或 git commit --all)默认不会添加新文件(即未被 Git 跟踪的文件),它只会自动暂存已被跟踪文件的修改和删除​。

detour 就是 绕道:在真正的函数开头插一条跳转,调用先走到你的函数,你再决定要不要去执行原来的。

Duck 用的是微软的 Detours 库。ApplyExportDetour 不是改 IAT 里的指针,而是直接改 d3d11!D3D11CreateDevice 这类导出函数本身的前几个字节,改成 jmp 到 hook。官方 RenderDoc / CTW 只改导入表,不改函数本体。

IAT 在计算机领域通常指导入地址表(Import Address Table)​​,是 Windows 可执行文件(PE 格式)中的一个核心数据结构。

官方 RenderDoc 用 hook GetProcAddress、新模块再扫一遍 IAT,挡住常见的「查地址再调」。Duck 的 detour 更进一步:把 d3d11!D3D11CreateDevice 函数开头改成跳到 hook,只要跳进这个导出,不管从哪来都会中。

游戏平时这样调:

游戏 --读自己的 IAT--> 跳到 d3d11 的函数

IAT hook 改的是调用方的那一格,不是 d3d11。
capture DLL 把游戏 IAT 里那个指针改成自己的 hook:

游戏 IAT 某一格 ──► capture 的 hook ──► 再转去真的 D3D11CreateDevice

d3d11.dll 里一个字节都没动。游戏也没被通知,它还以为自己在调 D3D。

renderdoc官方还 hook 了 GetProcAddress:目標進程調用GetProcAddress,會得到被hook 返回的地址

拦不住的情况——自己解析导出表或提前缓存

官方 RenderDoc 原本怎么做
官方/CTW 原来的win32_hook.cpp只做两件事:

  • 在模块加载时,把模块 IAT 里的LoadLibrary*GetProcAddress、D3D/DXGI 函数指针替换成 hook
  • 之后靠 hook 住的GetProcAddress,对以后才解析出来的函数也返回 hook

这个方式只能覆盖:

  • 通过 IAT 调用的路径
  • 通过被 hook 的GetProcAddress拿到的函数指针

但如果目标程序早就拿到了PresentSwapChainCommandQueue等真实导出地址,或者它自己直接调用dxgi.dll!Present的导出入口,不走 IAT,那 IAT patch 就抓不到。

Duck 加的ApplyExportDetour做了什么
Duck 在 [win32_hook.cpp (line 90)](E:/college_researcher/RenderDuck-1.4-source/RenderDuck-1.4/riderduck/os/win32/win32_hook.cpp:90) 里加了 Detours 的 export detour:

GetProcAddress(module, function) -> DetourAttach(&original, hook)

它会直接改写目标模块真实导出函数入口的前几条指令,让任何调用方:

  • 通过 IAT 调用
  • 通过GetProcAddress拿到指针后调用
  • 自己缓存了函数指针再调用
  • 内部模块直接 call export 地址

都会落到 RenderDoc 的 hook 上。

kernel32.dll 是“壳”​​:从 Windows 7 开始,微软为了优化系统架构和向后兼容性,将大部分核心 API 的实际实现代码移到了 kernelbase.dll 中

只 Hook kernel32.dll 的局限​:如果你只 hook 了 kernel32.dll 中的 LoadLibraryW 或 GetProcAddress,那么当程序直接调用 kernelbase.dll 中的实现时,你的 hook 就不会被触发​。因为调用根本没有经过 kernel32.dll 的“跳板”。

kernel32.dll 中的函数(如 WriteFile)在被调用时,会立即跳转(转发)到 kernelbase.dll中对应的函数去执行

API Set 机制​:现代 Windows 还引入了“API Set”机制,像 api-ms-win-core-libraryloader-l1-1-0.dll 这样的文件是虚拟的、不包含实际代码的“占位符”​​。Windows 加载器会根据内部的 Schema 规则,将这些 API Set 调用重定向到真正的实现模块(如 kernelbase.dll)

https://www.zhihu.com/question/4494136057/answer/66932168380#:~:text=%E8%BF%99%E4%BA%9B%E6%96%87%E4%BB%B6%E8%83%BD,PE%E7%BB%93%E6%9E%84%E3%80%82http://Windows API set stub dll 文件存在的作用是什么?

虽然是合法的 PE 格式 DLL,但内部几乎是空的,只包含一个导出表(相当于门牌号)和最基本的 PE 结构,没有任何实际的函数实现代码。

它在哪些时机执行
Duck 在四处都调ApplyExportDetour

  • 模块第一次被ApplyHooks处理时
  • 模块有多个副本,另一个副本变成主模块时
  • Hooked_GetProcAddress发现模块并初始化 hook 时
  • Win32_ManualHookModule手动 hook 时

并且在RemoveHooks里用DetourDetach清理,避免卸载时留下被改写的导出函数。

你打开 qriderduck.exe:DLL 进 UI 进程,DllMain 发现 replay 标记,按回放程序初始化。游戏还没启动。
2. 你点 Launch:UI 进程里调用 InjectDLL。
3. LoadLibraryW 在 游戏进程 里成功:DLL 再进游戏,DllMain 再跑一次,这次没有 replay 标记,才注册 hook。

UI 进程里下一步就是 WaitForSingleObject(hThread, INFINITE),等到那条远程线程结束。

那条线程本身就是 LoadLibraryW。它要先完成映射,再跑完目标里的 DllMain,才会返回。等的就是这件事结束,不是另起一个查询。

从 Visual Studio 2015 开始,MSVC 工具集主版本号统一为 14,v140、v141、v142、v143 之间基本保持二进制兼容​。这意味着你可以混合使用不同版本工具集生成的二进制文件,但必须使用最新的工具集至少与应用中的最新二进制链接

v142 和 v143 之间存在一些 ABI 层面的微妙差异,例如:
  • name mangling 规则变化​:v143 为支持 C++20 模块,将 std::allocator 的实例化细节更深地嵌入 mangled name,导致即使源码完全相同,v143 生成的符号也无法被 v142 链接器识别。

STL 布局差异​:某些情况下,Debug 版 std::vector 的成员布局在 v142 和 v143 之间可能产生偏移差,触发 _ITERATOR_DEBUG_LEVEL mismatch 断言。

Cocos Creator 打 Windows 包​:使用 Cocos Creator 2.4.3 打包 Windows 游戏时,需要 Visual Studio 2019 并选择MSVC v142Windows 10 SDK (10.0.16299.0)​

UE4.27 兼容​:如果你使用 VS2022 开发 UE4.27,需要额外勾选MSVC v142工具集,因为 UE4.27 依赖 v142 工具链。

CTW 1.46 使用 MSVC v143 时,DLL 静态导入表包含额外 CRT/Win32 函数,与目标可接受的已测版本不一致,导致加载器在 DllMain 前拒绝;改用 v142 后,导入表与可行版本完全一致,注入成功。

C:\Users\86134\AppData\Local\Temp\CTWResearcher\

这个路径写在win32_stringio.cppGetDefaultFiles()里:

GetTempPathW() + "CTWResearcher\\..."

默认捕获文件会类似:

C:\Users\86134\AppData\Local\Temp\CTWResearcher\CTWResearcher_app_2026.08.26_20.xx.rdc

日志也在同一目录:

C:\Users\86134\AppData\Local\Temp\CTWResearcher\CTWResearcher_app_*.log

某些游戏会根据硬件或驱动检测,在启动时自动选择最佳 API,但同样不会中途更换。

为什么能获取到游戏进程的命令行

游戏通过 launcher 启动后,TestGame.exe 进程的启动参数(如路径、启动选项等)会被 Windows 内核记录在进程的 PEB(进程环境块)中。WMI 服务通过查询这些系统数据结构,就能获取到完整的命令行信息。
需要注意​:如果游戏进程以管理员权限运行,而你的 PowerShell 不是以管理员身份启动,可能会因权限不足而无法访问该进程的 CommandLine 属性。此时需要以管理员身份运行 PowerShell

不是遊戲在「即時隨便換圖形 API」,而是 RenderDoc 在多個 API / swapchain 同時存在時,用 F11 切換「目前要捕捉哪一個」。

也就是:

  • 一個 process 裡可能同時有多個 graphics API 在跑(最常見的例子是 Chrome / 瀏覽器:D3D11 + D3D12 同時 active,WebGPU 走 D3D12,其他東西可能走 D3D11)。
  • 也可能有多個 swapchain / window(多視窗、多 monitor、或引擎內部有額外的 offscreen / overlay 視窗)。
  • RenderDoc 同一時間只能把「目前 active 的那一個」當作捕捉目標。
  • F11就是在這些已存在的 API / window / swapchain 之間循環切換「誰是目前 active」。

所以 overlay 上你會看到類似 D3D11 (Active), D3D12 (Active) 這種狀態,按 F11 可以把 Active 標記從一個移到另一個。

「显示更多选项」,或 Shift+右键

靠,現在才知道還有這樣的shift右鍵,

因为安装时没勾「把 Open with Code 加到资源管理器右键」。这是 User 安装,Code.exe 本身在,但注册表里原来没有这项。

已经补上了。文件、文件夹、文件夹空白处都能右键 Open with Code。Win11 新右键可能还是要先点「显示更多选项」,或 Shift+右键。关一次资源管理器窗口再试。

Unity 这层被 force 成 D3D11(一般是 -force-d3d11,或 Player 只开了 D3D11)
• 真正出画面的是后面那套自定义 Vulkan(log 里的 [Vulkan init] / HGRP)
• boot.config 里没有 API 开关,只有 gfx-enable-native-gfx-jobs 和远程 engine_config
• 注册表 HKCU\Software\Hypergryph\Endfield 只有画质(DLSS、framegen、阴影),没有 DX12/Vulkan 选项
• 远程 engine_config 里也没有图形 API 字段

所以你在 CTW 里只能跟 Vulkan,不是配置没读到,是 present 的就是 Vulkan。你之前那份 capture log 也写了 Used API: Vulkan (Presenting)。中间冒出来的 D3D12 是短命设备(然后被 Remove),不是游戏主渲染。

Command-line Arguments / Environment Variables 能干涉,这是你这边能塞参数的地方。框是空的,CTW 就按光 exe 启动,游戏自己走 Player.log 里那套:Unity 层 force D3D11,出画面的还是 Vulkan。

如果你在 Arguments 里填 -force-d3d12,最多动 Unity 那层 GfxDevice,动不了后面的 Vulkan 呈现。

CTW 点Launch 时走的是 Windows 的 CreateProcess:由它指定 exe 路径、工作目录、环境变量、以及整条命令行。操作系统原样交给新进程。资源管理器双击等于命令行只有 exe 自己;你在这个框里加字,只是让 CTW 多传几个 token。这是创建进程的常规能力,不是 CTW 特供。

游戏是 Unity,常见的 Unity 图形后端参数:

-force-d3d11 -force-d3d12 -force-vulkan -force-glcore -force-metal -force-feature-level-11-0 -force-feature-level-11-1

其他 Unity 常用参数:

-screen-width 1920 -screen-height 1080 -screen-fullscreen 0 -popupwindow -window-mode borderless -window-mode exclusive -window-mode fullscreen -window-mode windowed -vsync 1 -no-vsync -logFile C:\path\game.log -disable-gpu-skinning -nographics -batchmode

如果是 UE 游戏,则是另一套:

-d3d12 -dx12 -vulkan -sm5 -sm6 -rhiapi=d3d12 -rhi=vulkan

所以不能给“所有 EXE 默认加-force-d3d12”。正确做法是给每个游戏建一个启动预设,里面写它认识的参数:

启动时实际选了什么,用 Unity 自己的 log:

C:\Users\86134\AppData\LocalLow\Gryphline\Endfield\Player.log

E:\GRYPHLINK\games\Arknights Endfield\Endfield_Data\boot.config

Established + D3D12 Not Presenting + 游戏里能截帧说明你连接的 LiveCapture 不是真正 presenting/capturing 的那个进程。

这个游戏不是单进程:CTW 会注入多个子进程。你现在连接的进程:

  • 目标控制连接 Established
  • 但它没有 presenting 的 D3D12 窗口
  • 所以 API 显示 Not Presenting

实际截帧发生在另一个子进程里。那个子进程有自己的:

  • target control ident
  • API 状态
  • capture list

Collected Captures 只收集当前 LiveCapture 所连接的那个进程的帧。如果帧发生在子进程,而子进程的 LiveCapture 没有打开,主连接里就看不到。

源码里对应的地方:

LiveCapture::ConnectToChild() LiveCapture::NewChild

子进程出现时,LiveCapture 会添加 Child 条目。你应该在 LiveCapture 窗口里选那个真正 presenting 的子进程,打开它的连接:

  • 它的 API 应显示D3D12 (Presenting & supported)
  • 它的 Collected Captures 里才会出现这次 F12

CC Switch 里 通用配置(common_config_codex)是 Codex 全局的。你新加一个供应商/号,切换过去时会拼成:

该供应商自己的 config(模型、model_providers、key)
+ 通用配置(personality、[windows]、项目 trust)
→ 写入同一份 C:\Users\86134\.codex\config.toml

同一份工程、同一路径下,没改过的工程 MSBuild 会跳过。 这次没有跳过,是因为目录从 ctwresearcher-1.44 挪到了 ctwresearcher-1.44-source,旧的增量记录对不上,等于全量重来。大约 6 分钟,exit code 0。

Windows 官方做法是直接開 renderdoc.sln 編,不用 CMake(root CMakeLists.txt 裡甚至寫了 Windows 不需要 CMake)。

solution 裡是多個獨立 VS project,常見輸出大致是:

Project典型產物角色
renderdocrenderdoc.dll核心 capture / replay runtime
qrenderdocqrenderdoc.exeQt UI
renderdoccmdrenderdoccmd.exeCLI
renderdocshim 等shim DLLinjection / hook
d3d12 等 driver 相關 project對應 DLL 或靜態庫API backend
pyrenderdoc_module / qrenderdoc_module.pydPython bindings
version、IHV plugin(例如 NV)stub / plugin輔助

MSBuild 不是「一次只能編一個」

差別在你餵給 MSBuild 的是整個 sln還是單一 vcxproj

  • 一個 vcxproj ≈ 一個 DLL/EXE:這層你的理解是對的。
  • 一次 msbuild xxx.sln 可以編很多個:MSBuild 會依 project 相依順序排程,/m 還能平行編不同 project。
  • 不是「編譯器一次只能產出一個檔案」,而是「一個 project 定義一個 output;solution 把很多 project 串在一起」。

9 个字符,现在 8 个字符加一个结束符,后面的数据没有挪动:

改前: Chart.dll \0 \0 \0 paus
改后: Chat.dll \0 \0 \0 \0 paus

启动器里已经搜不到 Chart.dll,只剩一处 Chat.dll。直接跑 Launcher.exe 即可,它会去加载旁边的 Chat.dll。

Peeking多半不是 Cheat Engine 那種「讀記憶體改血量」,而是注入 DLL 的選單裡一個跟畫面遮擋 / 淡出有關的視覺開關。你前面在看 Endfield,這類遊戲(星鐵、原神、絕區零、明日方舟:終末地)的 trainer 很常有同名功能。

它實際在改什麼

這類遊戲相機會貼牆、鑽進模型裡。為了不穿模難看,引擎會把角色或前景dither / fade out(半透明、點狀消失)。函式名通常長這樣:

  • SetElevationDitherAlphaValue
  • SetDitherAlphaValueWithAnimation
  • elevation / camera occlusion fade

Peeking ON= hook 這些函式,把 fade alpha 強制設成 1,或直接 return 不跑淡出。

結果是視覺上的:

  • 鏡頭貼牆時角色不再「溶掉」
  • 被地形挡住的淡出變弱,看起來比較能「往裡看一眼」
  • 有些實作會連帶讓遮擋物透明度異常,接近輕量透視

所以才說它和 visual 有關:改的是 shader / material 的 alpha、dither 參數,不是 HP、金幣那種 gameplay 數值。選單裡常跟 ESP、speedhack 放一起,但原理不同。

和「DLL 改數值」差在哪

改數值(peek/poke)Peeking(視覺)
對象記憶體裡的 int/float(血、錢、冷卻)渲染參數(dither alpha、occlusion)
手段Read/WriteProcessMemory 或直接寫物件欄位hook 渲染 / shader property 函式
你看得到的變化UI 數字變了畫面不再淡出、遮擋變少
成敗加密、校驗、伺服器權威會擋純客戶端畫面,單機/單人場景特別明顯

老術語裡peek = 讀、poke = 寫。現代 ImGui 外掛把「關鏡頭淡出」也叫 Peeking,名字容易混。

FPS 的peeker's advantage:網路延遲造成「先探頭的人先看到」,跟 DLL 無關

API 常見是vertical FOV(Unity、D3D 慣例),也有 horizontal FOV。另加一個aspect決定左右有多寬。

多數引擎寫的是vertical FOV,左右張角由 aspect 推: f o v h = 2 arctan ⁡ ( tan ⁡ ( f o v v / 2 ) ⋅ a s p e c t ) \mathrm{fov}_h = 2\arctan(\tan(\mathrm{fov}_v/2)\cdot\mathrm{aspect}) 。寬螢幕看起來更「廣」,不一定是又改了一次 FOV。

遊戲裡「FOV 滑條」有時只改 world camera,槍/UI 用另一套 projection,所以準星和場景拉伸不一致。

極少數會在改 FOV 時順便移相機或改 near,那是他們自己的鏡頭預設,不是 FOV 定義的一部分。

水平張角本身不能寫成 α h = α v × 16 / 9 \alpha_h = \alpha_v\times 16/9 。正確是半角走 tangent:

α h = 2 arctan ⁡ ( tan ⁡ ( α v / 2 ) ⋅ a s p e c t ) \alpha_h = 2\arctan\bigl(\tan(\alpha_v/2)\cdot\mathrm{aspect}\bigr)

60° vertical 在 16:9 上大約是 90.6° horizontal,不是 60 × 16 / 9 = 106.7 ° 60\times16/9=106.7° 。大角度時誤差很明顯。

為什麼這套常被當成預設

gluPerspective(fovy, aspect, n, f)、D3D、Unity 都是vertical FOV + width/height。好處是改解析度、超寬屏時:

  • 上下看到的範圍鎖定(角色、準星垂直位置穩)
  • 多出來的像素往左右加(Hor+),而不是把上下裁掉

為什麼一定是半角

視錐左右、上下是對光軸對稱的。從相機看,你真正要的是「中心到邊緣」這一條邊的斜率:

slope y = tan ⁡ ( α v / 2 ) = h n \text{slope}_y=\tan(\alpha_v/2)=\frac{h}{n}

近平面全高是 2 h 2h ,所以全角定義是「兩邊各一箇半角」:

α v = 2 arctan ⁡ ( h / n ) \alpha_v=2\arctan(h/n)

公式裡的 2 只是把上下(或左右)兩半拼回一個張角。推寬高、組投影矩陣時,用的一直是 tan ⁡ ( α / 2 ) \tan(\alpha/2) ,不是 tan ⁡ ( α ) \tan(\alpha) 。

丢掉半角會怎樣

tan ⁡ \tan 不是線性的:

tan ⁡ α ≠ 2 tan ⁡ ( α / 2 ) \tan\alpha \neq 2\tan(\alpha/2)

如果硬用全角,近平面半高會變成 n tan ⁡ α n\tan\alpha ,那是把整個張角都堆到光軸同一側,視錐直接歪掉,而且 90° 就炸。
要從 α v \alpha_v 推 α h \alpha_h ,中間也必須進半角再出來,沒有「 α v × a s p e c t \alpha_v\times\mathrm{aspect} 」這種捷徑。

不是「半角不方便、最後還要把 2 補回來」;而是度數是給人看的,

投影必須把整個視錐填進同一塊 clip / 螢幕矩形。FOV 只改側邊斜率,近平面作為成像窗口還是那一塊像素,所以多出來的世界只能被縮小後塞進去。

极好的可视化网站

Frustum

WebGL Visualizing the Camera

three.js examples

資訊量上可以收成這樣:

無量綱的全是形狀,不能單獨定出世界裡的那顆錐。要落地,必須錨上一個帶長度量綱的量。

FOV、aspect、 tan ⁡ ( α / 2 ) \tan(\alpha/2) 、甚至 f / n f/n ,都是比值或角度,縮放世界它們不變,畫面看起來一樣。它們是條件熵裡「形」的那部分,只能當擴展。

把這套比例嵌進 R 3 \mathbb{R}^3 、讓「17」不再只是純數字,需要一個長度。圖形管線裡這個長度被放在:

n = eye 到近平面的距離 n = \text{eye 到近平面的距離}

图里采用的是经典 OpenGL 风格的[-1, +1]

Near → -1 Far → +1

这不是 UE 当前实际使用的方向

Near Clip Plane 太小会严重损害 depth precision。

假设场景真正关心的是:

5 ~ 10

但是你却设置:

Near = 1 Far = 10

那么这段真正重要的区域:

5 ~ 10

只能获得:

NDC 0.778 ~ 1

也就是整个[-1,+1]范围里很小的一段:

-1 ───────────────────────── 0.778 ──── 1 ↑ 5~10 全挤这里

Near 拉得过近会让远处大量深度值挤在一起,而调整 Far plane 的影响通常小得多。developer.nvidia.com/blog/visualizing-depth-precision


因为 RenderDoc 本身就不是一个“GUI 程序里塞着所有功能”的架构。这个项目实际上做的是:直接把 RenderDoc 的 Replay Core 当 C++ library 使用,自己充当一个极简的 headless frontend(无界面前端)。

所以它不依赖renderdocgui/qrenderdoc.exe,但它仍然高度依赖 RenderDoc 本体的renderdoc.dll + Replay API

可以把结构理解成:

官方 RenderDoc qrenderdoc.exe / GUI └─ CaptureContext / ReplayManager / Qt UI └─ RenderDoc Replay API └─ renderdoc.dll ├─ D3D11 replay ├─ D3D12 replay ├─ Vulkan replay ├─ OpenGL replay ├─ shader debug ├─ resource inspection └─ .rdc parser / replay machinery 这个 renderdoc-mcp renderdoc-mcp.exe └─ MCP / JSON-RPC └─ 自己写的 Session / Core wrapper └─ RenderDoc Replay API └─ renderdoc.dll

也就是:

GUI 和 MCP 是两个并列的 frontend,而不是 MCP → GUI → RenderDoc。

它甚至在源码里非常直接地证明了这一点。

session.cpp

C++ #include <renderdoc_replay.h>

然后启动时直接:

C++ RENDERDOC_InitialiseReplay(env, args);

打开.rdc

C++ m_captureFile = RENDERDOC_OpenCaptureFile(); m_captureFile->OpenFile(...); auto [replayStatus, controller] = m_captureFile->OpenCapture(opts, nullptr);

最后得到:

C++ IReplayController* m_controller;

后面几乎所有 RenderDoc 能做的分析,都是围绕这个IReplayController展开的。github.com/JiaboLi-GitHub/renderdoc-mcp/blob/main/src/core/session.cpp

比如:

IReplayController ├─ GetRootActions() ├─ SetFrameEvent() ├─ GetPipelineState() ├─ GetD3D11PipelineState() ├─ GetD3D12PipelineState() ├─ GetTextures() ├─ GetBuffers() ├─ GetShader() ├─ PixelHistory() ├─ DebugPixel() ├─ DebugVertex() ├─ GetPostVSData() ├─ SaveTexture() └─ ...

这些 API 本来就不是 GUI 专属 API。官方 RenderDoc 自己的 Python 示例甚至明确展示了完全不启动 GUI 的流程:

Python rd.InitialiseReplay(...) cap = rd.OpenCaptureFile() cap.OpenFile("test.rdc", ...) result, controller = cap.OpenCapture(...) controller.GetRootActions()

所以 RenderDoc 官方从设计上就支持 standalone replay client。github.com/baldurk/renderdoc/blob/v1.x/docs/python_api/examples/renderdoc_intro.rst

这个项目真正比较值得注意的是,它没有走我们之前讨论过的那种:

AI ↓ MCP Server ↓ IPC RenderDoc GUI ↓ RenderDoc Python Extension ↓ pyrenderdoc

而是直接:

AI ↓ stdio MCP renderdoc-mcp.exe ↓ C++ RenderDoc Replay API ↓ renderdoc.dll ↓ GPU replay

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 6:19:10

智能家居电源控制参考设计:数字高频开关电源实战解析

手上接过智能家居电源项目的朋友&#xff0c;应该都见过这么一份需求单&#xff1a;待机功耗要压到零点几瓦以下&#xff0c;Wi-Fi模组发射瞬间电压不许掉&#xff0c;体积还要小到能塞进一个插座底盒&#xff0c;成本又多一分都不行。几条指标摆在一起&#xff0c;模拟电源方案…

作者头像 李华
网站建设 2026/8/28 6:18:29

Python空间插值实战:从IDW到克里金,数学建模与地理数据分析核心技能

1. 项目概述&#xff1a;当数学建模遇见空间数据搞数学建模的朋友&#xff0c;尤其是处理地理、环境、气象、农业这些领域数据的&#xff0c;肯定都遇到过这个头疼事&#xff1a;你手头有一堆散落在不同位置的观测点数据&#xff0c;比如气象站的温度、土壤采样点的pH值、地下水…

作者头像 李华
网站建设 2026/8/28 6:14:40

重磅推荐欧米到家潍坊中央空调维修-优质服务及正规操作检修|快速上门深度排查故障原因|权威靠谱受市民好评

核心导读潍坊中央空调出现不制冷、制冷效果差、漏水、异响、频繁停机、故障代码或部分房间没有效果时&#xff0c;维修的关键并不是立即加氟或更换配件&#xff0c;而是先判断故障究竟来自冷媒系统、电控系统、风路水路&#xff0c;还是安装与维护问题。欧米到家面向潍坊家庭、…

作者头像 李华