自由学习记录(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 RTTIclass 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 的壳• 找 UIRenderDoc.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-d3d11python rename_branding.py .加--dry-run可以先只看计划改动不实际写文件。覆盖范围它从根目录递归遍历整个仓库但有三类例外跳过.git、.hg、.svn、__pycache__只处理文本文件.dll/.exe/.ico/.png等二进制不动不处理脚本自身它会同时改文件内容里的renderdoc文件名里的renderdoc目录名里的renderdoc另外它的覆盖只替renderdoc不处理baldurk、Crytek、github.com/baldurk不处理图标资源不处理已编译的 Release 二进制“换图标”本身不需要重新编译 C 源码只需要重新生成资源并重新链接对应的 EXE。流程是这样的图标文件icon.ico被.rc引用改动图标后rc.exe把.rc编译成.reslinker 把.res链接进 EXE文件同时被 GUI 和 Cmd 的.rc引用替换后只重编这两个输出msbuild qrenderdoc\qrenderdoc_local.vcxproj /p:ConfigurationRelease /p:Platformx64 ... msbuild renderdoccmd\renderdoccmd.vcxproj /p:ConfigurationRelease /p:Platformx64 ...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 — 看模块列表里有没有这份 DLL4. 调 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 shellcodeCreateRemoteThread的入口点是这段 shellcodeshellcode 再调LoadLibraryW和GetLastErrorDuck 在它的 [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.dllCTW 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、往 [rbx0x208] / [rbx0x210] 回写• 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 ──► 再转去真的 D3D11CreateDeviced3d11.dll 里一个字节都没动。游戏也没被通知它还以为自己在调 D3D。renderdoc官方还 hook 了 GetProcAddress目標進程調用GetProcAddress,會得到被hook 返回的地址拦不住的情况——自己解析导出表或提前缓存官方 RenderDoc 原本怎么做官方/CTW 原来的win32_hook.cpp只做两件事在模块加载时把模块 IAT 里的LoadLibrary*、GetProcAddress、D3D/DXGI 函数指针替换成 hook之后靠 hook 住的GetProcAddress对以后才解析出来的函数也返回 hook这个方式只能覆盖通过 IAT 调用的路径通过被 hook 的GetProcAddress拿到的函数指针但如果目标程序早就拿到了Present、SwapChain、CommandQueue等真实导出地址或者它自己直接调用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 detourGetProcAddress(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.dllhttps://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.exeDLL 进 UI 进程DllMain 发现 replay 标记按回放程序初始化。游戏还没启动。2. 你点 LaunchUI 进程里调用 InjectDLL。3. LoadLibraryW 在 游戏进程 里成功DLL 再进游戏DllMain 再跑一次这次没有 replay 标记才注册 hook。UI 进程里下一步就是 WaitForSingleObject(hThread, INFINITE)等到那条远程线程结束。那条线程本身就是 LoadLibraryW。它要先完成映射再跑完目标里的 DllMain才会返回。等的就是这件事结束不是另起一个查询。从 Visual Studio 2015 开始MSVC 工具集主版本号统一为 14v140、v141、v142、v143 之间基本保持二进制兼容。这意味着你可以混合使用不同版本工具集生成的二进制文件但必须使用最新的工具集至少与应用中的最新二进制链接v142 和 v143 之间存在一些 ABI 层面的微妙差异例如name mangling 规则变化v143 为支持 C20 模块将 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 v142和Windows 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.cpp的GetDefaultFiles()里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 同時 activeWebGPU 走 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• 真正出画面的是后面那套自定义 Vulkanlog 里的 [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 -rhiapid3d12 -rhivulkan所以不能给“所有 EXE 默认加-force-d3d12”。正确做法是给每个游戏建一个启动预设里面写它认识的参数启动时实际选了什么用 Unity 自己的 logC:\Users\86134\AppData\LocalLow\Gryphline\Endfield\Player.logE:\GRYPHLINK\games\Arknights Endfield\Endfield_Data\boot.configEstablished D3D12 Not Presenting 游戏里能截帧说明你连接的 LiveCapture 不是真正 presenting/capturing 的那个进程。这个游戏不是单进程CTW 会注入多个子进程。你现在连接的进程目标控制连接 Established但它没有 presenting 的 D3D12 窗口所以 API 显示 Not Presenting实际截帧发生在另一个子进程里。那个子进程有自己的target control identAPI 状态capture listCollected Captures 只收集当前 LiveCapture 所连接的那个进程的帧。如果帧发生在子进程而子进程的 LiveCapture 没有打开主连接里就看不到。源码里对应的地方LiveCapture::ConnectToChild() LiveCapture::NewChild子进程出现时LiveCapture 会添加 Child 条目。你应该在 LiveCapture 窗口里选那个真正 presenting 的子进程打开它的连接它的 API 应显示D3D12 (Presenting supported)它的 Collected Captures 里才会出现这次 F12CC 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 編不用 CMakeroot CMakeLists.txt 裡甚至寫了 Windows 不需要 CMake。solution 裡是多個獨立 VS project常見輸出大致是Project典型產物角色renderdocrenderdoc.dll核心 capture / replay runtimeqrenderdocqrenderdoc.exeQt UIrenderdoccmdrenderdoccmd.exeCLIrenderdocshim 等shim DLLinjection / hookd3d12 等 driver 相關 project對應 DLL 或靜態庫API backendpyrenderdoc_module / qrenderdoc_module.pydPython bindingsversion、IHV plugin例如 NVstub / plugin輔助MSBuild 不是「一次只能編一個」差別在你餵給 MSBuild 的是整個 sln還是單一 vcxproj。一個 vcxproj ≈ 一個 DLL/EXE這層你的理解是對的。一次 msbuild xxx.sln 可以編很多個MSBuild 會依 project 相依順序排程/m 還能平行編不同 project。不是「編譯器一次只能產出一個檔案」而是「一個 project 定義一個 outputsolution 把很多 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半透明、點狀消失。函式名通常長這樣SetElevationDitherAlphaValueSetDitherAlphaValueWithAnimationelevation / camera occlusion fadePeeking ON hook 這些函式把 fade alpha 強制設成 1或直接 return 不跑淡出。結果是視覺上的鏡頭貼牆時角色不再「溶掉」被地形挡住的淡出變弱看起來比較能「往裡看一眼」有些實作會連帶讓遮擋物透明度異常接近輕量透視所以才說它和 visual 有關改的是 shader / material 的 alpha、dither 參數不是 HP、金幣那種 gameplay 數值。選單裡常跟 ESP、speedhack 放一起但原理不同。和「DLL 改數值」差在哪改數值peek/pokePeeking視覺對象記憶體裡的 int/float血、錢、冷卻渲染參數dither alpha、occlusion手段Read/WriteProcessMemory 或直接寫物件欄位hook 渲染 / shader property 函式你看得到的變化UI 數字變了畫面不再淡出、遮擋變少成敗加密、校驗、伺服器權威會擋純客戶端畫面單機/單人場景特別明顯老術語裡peek 讀、poke 寫。現代 ImGui 外掛把「關鏡頭淡出」也叫 Peeking名字容易混。FPS 的peekers advantage網路延遲造成「先探頭的人先看到」跟 DLL 無關API 常見是vertical FOVUnity、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/9106.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_v2\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 只改側邊斜率近平面作為成像窗口還是那一塊像素所以多出來的世界只能被縮小後塞進去。极好的可视化网站FrustumWebGL Visualizing the Camerathree.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 ↑ 510 全挤这里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.cppC #include renderdoc_replay.h然后启动时直接C RENDERDOC_InitialiseReplay(env, args);打开.rdcC 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