Files
win-troubleshooting-notes-p…/ori-wotw-troubleshooting/文章/博客模板-奥日2加载闪退修复.md
T

323 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "奥日 2 加载闪退、偶尔进入却无声:一次 Wwise 线程故障排查与 8 字节兼容补丁"
date: 2026-09-16
slug: ori-wotw-wwise-thread-affinity-fix
description: "从崩溃转储和 Windows API 返回值定位 Wwise 音频初始化失败,并通过取消音频线程显式核心绑定完成本机验证。附带哈希校验与还原功能的补丁源码。"
tags:
- 游戏排障
- Windows
- Unity
- Wwise
- 逆向分析
draft: true
---
<!-- 发布前:填写下载链接与作者信息;按你的博客系统调整 front matter。若支持 Mermaid,可保留图;不支持时删去图,正文已包含完整解释。 -->
我遇到的《奥日 2》问题很折磨人:小精灵还在加载界面跑步,游戏就闪退;偶尔成功进去,却完全没有声音。换了两个版本、限制 CPU 核心、禁用部分设备,都没有解决。同系统版本的 VMware 虚拟机反而可以运行。我折腾了数小时,最后甚至退了款,游戏文件倒还留在硬盘上。
这次借助 AI 编程助手做了进一步排查,最终找到了一个可以实际验证的触发链:**音频线程绑定 CPU 核心失败,Wwise 的初始化失败路径又留下了仍在运行的线程。**
最终补丁改动 8 个字节,让相关音频线程使用 Windows 默认调度。我的机器已经通过普通启动和 Windows 音频输出验证。下面记录证据、原理,以及补丁适用的严格范围。
> 这是一个具体 DLL 构建、具体故障路径的非官方兼容补丁,不是所有奥日 2 闪退的通用解法。请先看文件哈希和适用条件。
## 想先解决问题:下载、检查、安装、还原
补丁包:**[Ori Wwise Affinity Fix v1.0.0]({{PATCH_DOWNLOAD_URL}})**
完整研究报告:**[详细技术记录]({{RESEARCH_REPORT_URL}})**
源码仓库或镜像:`{{SOURCE_URL_OR_REMOVE_THIS_LINE}}`
分享包只包含脚本、说明与脱敏后的证据摘要。它在本地修改你自己的游戏文件,不附带完整游戏 DLL,也不修改游戏平台授权逻辑。
### 适用条件
- 游戏常在加载阶段退出,偶尔进入但无声。
- 日志中出现 `Cannot create streaming I/O device`、`Failed to initialize the sound engine`,崩溃栈指向 `AkSoundEngine.dll` 工作线程。
- 原始 `AkSoundEngine.dll` 的 **SHA-256 必须完全等于**:
```text
8F90A4A3856540CB97D03CA07B4BB10B7A08F7B4AB3C524BE2BEC1AFDD29FECA
```
DLL 位于游戏目录的 `oriwotw_Data\Plugins` 下。相同游戏名称、相同 Wwise 版本字符串,甚至同样的闪退画面,都不能替代哈希匹配。
### 使用方式
需要 Windows x64 和 PowerShell 7。先退出游戏,解压补丁包,再双击 `Install.cmd`,按提示粘贴游戏根目录,也就是包含 `oriwotw.exe` 的目录。
喜欢命令行的读者,可以在**解压后的补丁目录**打开 PowerShell 7。下面路径只是示例,需要改成你自己的:
```powershell
# 只检查,不修改
& '.\Ori-AudioFix.ps1' -Action Check -GameDirectory 'D:\Games\Ori and the Will of the Wisps'
# 安装补丁并保留经过校验的原文件备份
& '.\Ori-AudioFix.ps1' -Action Install -GameDirectory 'D:\Games\Ori and the Will of the Wisps'
# 还原;请先退出游戏
& '.\Ori-AudioFix.ps1' -Action Restore -GameDirectory 'D:\Games\Ori and the Will of the Wisps'
```
也可以双击 `Restore.cmd` 还原。备份文件名为:
```text
AkSoundEngine.dll.before-ori-affinity-fix
```
补丁后 DLL 的哈希应为:
```text
62B2079A988D23EB251D4E83366AC04068A6F47E0D38EFE92A76DF022826F9F0
```
版本不匹配时,脚本会停止。请不要修改脚本中的哈希限制强行安装。游戏更新或平台文件校验可能覆盖补丁,更新后的 DLL 需要重新确认适用性。
## 我的测试环境
本机有一些值得如实记录的特征,尤其是固件和 Windows 对处理器的命名并不相同。它们是复现背景,不是已经证明的故障原因。
| 项目 | 环境 |
| --- | --- |
| 系统 | Windows 10 专业工作站版,22H2,x64,构建 `19045.6456` |
| CPU:Windows 报告 | `Genuine Intel(R) 0000` |
| CPU:固件产品名 | `i9-13900HK(ES)` |
| CPU 标识 | Family 6 / Model 186 / Stepping 2 |
| 可用逻辑处理器 | 20;测试进程亲和性 `0xFFFFF` |
| 内存 | 约 32 GB;崩溃报告为 32600 MiB |
| 主板固件字段 | `Intel / Z790M GAMING WIFI D5` |
| BIOS | AMI,`Leo-Patch 1.5`,日期 2024-07-26 |
| 本次实际渲染设备 | AMD Radeon RX 9070 XT,驱动 `32.0.31041.1004` |
| 图形接口 | Direct3D 11,功能级别 11.1 |
| 主测版本 A | Unity `2018.4.11f1`,Build Id `5257` |
| 另一版本 B | Unity `2018.4.24f1`,Build Id `8978` |
| 两份游戏共用的音频组件版本 | Wwise `2019.1.7 Build 7135`;原始 DLL 哈希相同 |
注册表还存在 NVIDIA Tesla P4、Zako Display Adapter、Microsoft Remote Display Adapter 等显示组件。音频组件包括 Realtek High Definition Audio(`6.0.9239.1`)、AMD High Definition Audio(`10.0.1.41`)、Steam Streaming Speakers(`17.56.13.764`)、VB-Audio Virtual Cable(`3.3.1.7`)和 AMD Streaming Audio Device(`1.0.0.14`)。注册记录不等于它们当时都处于活动状态。
VMware 可运行的情况是我此前的观察,本次没有同时采集虚拟机的 vCPU、驱动和文件哈希,所以这里不把它包装成严格的对照实验。
## 第一步:把大量日志噪声与重复故障分开
Unity 日志里有不少“脚本引用缺失”等警告。真正有价值的线索是,这批历史记录中的 29 次崩溃,有 28 次走到了同一类 Wwise 工作线程路径。
对应的关键报错是:
```text
Wwise: Cannot create streaming I/O device.
WwiseUnity: Failed to initialize the sound engine. Abort.
```
这里的 streaming I/O device 指音频数据读取机制,不是设备管理器中的某块物理声卡。这个区别很关键:如果直接把它理解成声卡坏了,很容易继续围绕音频驱动反复尝试。
还要注意,28/29 只是留存崩溃样本的分类,不能当成启动失败率。成功启动不会产生同样的崩溃报告,而且这些记录跨越了此前的不同排障尝试。
## 第二步:别完全相信文本栈给出的函数名
原始堆栈把出错帧显示成 `AK::StreamMgr::SetFileLocationResolver`。但没有完整调试符号时,工具可能只找到了附近的导出名称。
计算相对模块地址并反汇编后,真正值得注意的位置是:
```text
AkSoundEngine.dll + 0x5B0C8
AkSoundEngine.dll + 0x5B16E
```
这两个位置分别紧跟在虚函数间接调用之后。崩溃线程试图跳转的目标又不在正常的模块代码区。
更细的一点:Unity 文本报告写的是“Write to location”,但对应转储中的 `ExceptionInformation[0]` 是 **8**。按微软文档,这意味着用户态数据执行保护(DEP)异常,即尝试执行了不允许执行的内存。
所以我没有把“文本日志说写入冲突”当作最终答案,而是继续检查对象和线程的生命周期。
## 第三步:记录真正失败的 Windows 调用
诊断脚本在自己的游戏子进程内,对音频线程初始化附近设置临时断点,记录参数、返回值和目标线程错误码。原始运行捕获到:
```text
线程优先级:0
线程核心掩码:0xFFFFFFFC
线程栈大小:128 KiB
SetThreadPriority 返回 1:成功
SetThreadAffinityMask 返回 0:失败
Windows 错误码 87:ERROR_INVALID_PARAMETER
随后发生 0xC0000005 访问冲突
```
`SetThreadAffinityMask` 用来指定线程可以在哪些逻辑 CPU 上运行。掩码里的每一位代表一个逻辑处理器,例如 `0xF` 对应低四位。
起初很容易想到:游戏请求 `0xFFFFFFFC`,本机进程报告 `0xFFFFF`,是不是请求了不允许的高位?这个解释必须经过实验。
结果是:把掩码缩小到 `0xFFFFC`,游戏线程仍然失败;独立的小程序测试自己的线程时,相同掩码却能成功。因此,**“只要截断多余的位就能修好”没有得到本机实验支持**。
这里保留一个尚未解决的问题:宿主机为什么拒绝这个游戏线程的绑定?现有证据不足以把它归因于 ES CPU、混合架构、某个声卡或某个显示驱动。
### 网上说的“限制 CPU 核心”,和这次修复有什么区别?
我之前确实试过限制游戏可用核心,没有解决;旧启动文件里还留着 `/affinity F`。这里的 F 对应低 4 个**逻辑处理器**,不一定是 4 个物理核心。
需要区分三个操作:
1. **限制整个游戏进程的核心范围:**改变进程能使用哪些处理器,但游戏内部仍可能对音频线程重新请求绑定。
2. **缩小音频线程请求的掩码:**本轮实测过,改为 `0xFFFFC` 后仍返回错误 87。
3. **取消音频线程的显式绑定:**本补丁把配置值设为 0,让 DLL 跳过绑定调用;这才是在本机通过验证的办法。
因此,准确说法是“限制核心没有解决我的这个问题”,而不是“限制核心对所有人都没用”。它可能帮助其他并发、时序或处理器拓扑问题;我们也没有穷举所有核心组合。关键仍然是检查相同的错误调用是否消失,而不是只看修改后的某一次是否碰巧启动。
## 第四步:找到导致随机崩溃的错误清理关系
更严重的问题出现在设置失败以后。
反汇编显示,Wwise 先创建一个可以立即运行的线程,再设置优先级和核心绑定。如果设置失败,它会关闭线程句柄并把字段清空。而后续清理逻辑只有在线程句柄仍然存在时才等待线程。
**关闭线程句柄,不等于结束线程。**微软的 `CloseHandle` 文档明确写了这一点。
下面是控制关系的简化表达,不是厂商源代码:
```cpp
thread = CreateThread(...); // 线程已经可以运行
if (!ConfigureThread(thread)) {
CloseHandle(thread);
thread = nullptr;
return Failure;
}
// 后续清理
if (thread != nullptr) {
WaitForSingleObject(thread, INFINITE);
}
// 此后才应销毁线程仍可能使用的资源。
```
一旦句柄清空但线程还活着,后续清理就可能跳过必要的等待。结合错误的间接调用目标,这强烈支持“仍在运行的线程使用了失效对象”的解释。
```mermaid
flowchart TD
A[音频读取线程开始运行] --> B[设置 CPU 亲和性失败:错误 87]
B --> C[关闭并清空线程句柄]
C --> D[初始化失败,进入资源清理]
D --> E[后续等待可能被跳过]
E --> F[仍存活线程使用失效状态]
F --> G[错误间接跳转,访问冲突]
```
本轮直接确认了调用失败、句柄清理分支以及后续异常;没有完整跟踪所有分配和释放事件,所以我把“释放后使用”写成有强证据支持的解释,而不是宣称已经掌握厂商源码层面的全部细节。
这个时序也能解释为什么现象不完全固定:音频初始化失败会造成无声,而线程什么时候撞上失效状态,还受调度和内存复用时机影响。
## 8 字节补丁:让音频线程交给 Windows 调度
这次选用的兼容方案,是利用 DLL 已有的“掩码为 0 时跳过显式绑定”分支。
被修改的导出函数是:
```text
CSharp_AkThreadProperties_dwAffinityMask_set
```
原来相当于:
```cpp
if (properties != nullptr)
properties->dwAffinityMask = suppliedMask;
```
补丁后相当于:
```cpp
if (properties != nullptr)
properties->dwAffinityMask = 0;
```
注意这里的 0 **不是把线程绑定到“零个核心”**。该音频 DLL 在调用 Windows API 之前就会检查它;值为 0 时直接不调用绑定函数,让线程继承正常的系统调度。
对应构建的精确信息:
```text
函数 RVA: 0x121F0
文件偏移: 0x115F0
原始:48 85 C9 74 03 89 51 04 C3 CC CC CC CC CC CC CC
补丁:48 85 C9 74 07 C7 41 04 00 00 00 00 C3 CC CC CC
```
替换区域为 16 字节,真正变化了其中 8 个字节。空指针检查保留,跳转距离相应调整,使用原函数尾部的填充空间。候选文件经过再次反汇编,确认指令和边界符合预期。
这是一种绕过触发条件的兼容修复。若能修改引擎源码,更完整的修复还应确保初始化失败时正确停止并等待线程,之后再释放其依赖资源。
## 验证结果
| 测试 | 结果 |
| --- | --- |
| 原始 DLL 动态跟踪 | 绑定失败,错误 87,随后崩溃 |
| 仅缩小核心掩码 | 仍然失败 |
| 进程内取消显式绑定 | 通过音频初始化和后续加载 |
| 安装补丁后普通启动 | 不依赖调试器,也通过加载 |
| Windows 音频会话 | 活动、未静音、音量 100%,检测到非零音频信号 |
| 最后一次退出 | 我主动关闭了游戏 |
| 安装及还原 | 副本往返测试通过,还原后哈希与原件相同 |
音频探针在 3 秒采样中的最大电平约为 `0.01062`。它只读取游戏的音频会话状态,没有录音,也没有访问麦克风。这个结果证明游戏确实向 Windows 音频管线输出了声音信号。
本轮主要验证版本 A。版本 B 的音频 DLL 与 A 完全相同,已经安装并校验,但还没有单独进行本轮运行测试。完整长时间游玩、所有关卡、性能和音频延迟测试也还没有完成。
<!-- 可选补充,确实测试后再填写,不要保留虚构结果:
实际试听:{{音乐/音效/对白是否正常}}
连续游玩:{{测试日期、时长、关卡、是否有新问题}}
版本 B:{{是否单独验证}}
其他读者环境:{{经确认的反馈}}
-->
## 分享这个补丁时,我希望保留的限制
补丁安装器检查完整 SHA-256、原始指令和备份,已经修补的文件不会重复修改,未知版本会拒绝处理。补丁文件不携带游戏 DLL,读者使用自己的文件生成结果。
它影响通过这个 setter 配置的音频线程,不改变系统全局调度,不保证修复所有 `0xC0000005`。取消显式绑定也可能改变性能特征,需要更多环境验证。
如果想反馈适用情况,建议附上:Windows 构建号、CPU 标识及逻辑处理器数、Unity/Wwise 版本、DLL 原始哈希、关键报错和测试时长。请先去掉日志中的个人路径;完整崩溃转储不适合直接放在公开评论区。
这次研究最有价值的收获,是把模糊的“加载闪退”逐步缩小成可以观测和对照的行为:哪个线程、哪个调用、什么参数、什么返回值,以及修改之后是否在普通启动下仍然成立。
## 这算逆向工程吗?
算,而且是用于故障诊断和兼容性修复的逆向工程。
反汇编把已编译的机器码翻译成汇编,让我们能看到“创建线程后做了什么、失败分支跳到哪里”。动态调试则记录这些指令在本机真正收到的参数和返回值。把两者与崩溃记录对上,才有依据设计补丁。
这里没有恢复完整的游戏源码,文中的 C++ 风格代码也是解释逻辑的伪代码。但关键地址、调用参数、错误码和修改字节都保存下来了,其他人可以复核这条推理链。
## 网上那些“奥日 2 闪退解决办法”,到底有没有用?
我又核对了官方公告和玩家讨论。结论是:**不少办法有适用场景,但“加载闪退”不是一个足以确定根因的诊断。**下面是截至 2026-09-16 的资料核查,不代表我在本机逐项执行过。
| 办法 | 我的判断与依据 |
| --- | --- |
| 限制 CPU 核心 | **本例此前没解决,不能推成人人无效。**查到的[性能教程](https://www.ghostarrow.com/how-to-improve-performance-in-ori-and-the-will-of-the-wisps)针对 CPU 近满载时的卡顿,使用动态限制规则;没有验证本例 Wwise 错误 87。它也不一定就是我最早看到的那篇教程 |
| 换声音输出/API、关闭空间音频 | **有官方用途依据。**[Patch 2 公告](https://steamcommunity.com/app/1057090/discussions/0/4666237625643445999/)说明这些选项用于硬件兼容和无声排障;不能据此保证能修复我的线程绑定失败 |
| 改成 48 kHz | **可能处理格式兼容,具体证据弱。**[相关帖子](https://steamcommunity.com/app/1057090/discussions/0/581680031271722194/)只有简短建议,没有硬件、日志与回复验证;48 kHz 不是已经证明的万能值 |
| 关闭动态模糊 | **有特定场景的成功反馈。**[Iris Xe 讨论](https://steamcommunity.com/app/1057090/discussions/0/5715697730998329692/)中,两位玩家报告不再冻结。但他们是游玩一段时间后冻结,我是加载时音频初始化失败 |
| 窗口化、调整 HDR;用 DXVK | **有图形路径的相关反馈。**[RTX 5070 帖](https://steamcommunity.com/app/1057090/discussions/0/582806854239850133/)同时改了多项显示设置,无法分清单项贡献;[RTX 50 帖](https://steamcommunity.com/app/1057090/discussions/0/695372304943750032/)作者称 DXVK 修好了花屏。本机窗口化仍复现原故障,历史 DXVK 尝试还出现过另一类 DXGI 崩溃 |
| 回退游戏版本或检查 Windows 预览版 | **有特定历史问题的官方依据。**[Patch 3.1 公告](https://steamcommunity.com/app/1057090/discussions/0/2968397584537393710/)提供 Patch 2 回退;[开发者白屏帖](https://steamcommunity.com/app/1057090/discussions/0/3012312119189327120/)初步关联当时 Insider Dev 预览版。我的正式版 Win10 不能直接照搬 |
| 校验文件、修运行库、换驱动 | **有对应错误时有用。**分别处理损坏文件、缺失依赖和驱动兼容;恢复原版文件无法修正它自身的线程清理逻辑 |
| 安装 Unity 引擎、套用“一键优化” | **作为万能办法缺乏依据。**[一篇启动指南](https://www.ghostarrow.com/ori-and-the-will-of-the-wisps-fix-wont-launch-black-screen-crashes)确实建议安装 Unity,但游戏自带运行用的 Unity Player,不能由此推出玩家必须安装编辑器,也没有对应对照证据 |
我所说的“玄学”,是把缺少证据的相关性讲成必然有效的因果。像改采样率、关闭叠加层、调整核心限制,本身不神秘;问题是有没有对应的故障条件,有没有记录到底是哪一步起作用。
还有一个容易误读的例子:[无声讨论帖](https://steamcommunity.com/app/1057090/discussions/0/2968398851794293852/)收到许多建议,但楼主明确回复没用。几个月后游戏恢复声音,楼主也不知道原因。不能把这段故事压缩成“改声音设置/更新系统已验证有效”。
对我这次排障,最有分量的区别仍是:缩小音频线程请求掩码后,同一个绑定调用继续失败;让它不再发起显式绑定后,音频初始化及普通启动通过。它比仅凭一次“能进游戏了”更能支持这个补丁的作用机制,但仍不代替长时间游玩验证。
分享包附有 `COMMUNITY-REVIEW.zh-CN.md`,保留更详细的适用条件、来源和证据限制。
## 参考资料
- [Microsoft:CloseHandle](https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle)
- [Microsoft:CreateThread](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread)
- [Microsoft:SetThreadAffinityMask](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setthreadaffinitymask)
- [Microsoft:EXCEPTION_RECORD](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-exception_record)
- [Microsoft:x64 调用约定](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170)
- [Microsoft:Windows 错误码 0–499](https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-)
官方资料核对日期:2026-09-16。本文的实验结论来自本机记录,不把论坛上的类似症状或未经验证的猜测当作根因。