481 lines
31 KiB
Markdown
481 lines
31 KiB
Markdown
# 奥日 2 加载闪退与无声:Wwise 线程亲和性失败的定位与兼容补丁
|
||
|
||
研究日期:2026-09-16(UTC+8)
|
||
研究对象:Windows 版《Ori and the Will of the Wisps》
|
||
状态:已定位直接触发链,完成兼容补丁及本机启动、音频输出验证。用户确认最终普通启动是主动关闭。
|
||
|
||
## 1. 结论及其边界
|
||
|
||
本机出现的主要故障位于 Wwise 音频引擎的线程初始化阶段:`SetThreadAffinityMask` 调用失败,返回错误 87;随后初始化失败路径关闭并清空线程句柄,而已经启动的线程仍可能继续运行。历史崩溃的异常位置和动态跟踪都与这一失败路径吻合。
|
||
|
||
兼容补丁将游戏通过指定导出函数设置的音频线程亲和性掩码改为 0。该 DLL 的现有分支把 0 当作“不进行显式绑定”,因此线程交由 Windows 调度,避开失败路径。补丁没有禁用声音,也没有改变系统可用核心数量。
|
||
|
||
验证结果:
|
||
|
||
- 排查前留存的 29 份崩溃报告中,28 份落在同类 Wwise 读取线程路径;另 1 份为此前 DXVK 尝试中的 DXGI 崩溃。
|
||
- 原版在动态跟踪中重现:优先级设置成功、亲和性设置失败、紧随其后访问冲突。
|
||
- 将请求掩码截断到进程报告的可用核心范围,仍然失败。
|
||
- 将亲和性配置改为 0 后,音频初始化和后续场景加载通过。
|
||
- 写入磁盘补丁后,不带调试器的普通启动也通过;Windows Core Audio 检测到活动、未静音的游戏音频会话和非零音频电平。
|
||
- 两个游戏目录均已安装并校验补丁。运行验证集中在版本 A;版本 B 本轮仅完成文件匹配、安装及校验。
|
||
|
||
**尚未证明的部分:**为什么宿主环境拒绝该游戏线程的亲和性设置,仍未进一步确定。独立测试线程可以接受相同掩码,所以不能把原因直接归为“掩码越界”“核心太多”“ES 处理器缺陷”或某个驱动。对已经释放或失效对象的使用有强证据支持,但本轮没有跟踪每一次内存分配与释放,不能声称完整证明了具体分配器层面的生命周期。
|
||
|
||
这是针对已确认触发条件的 **workaround(兼容性绕过)**,不是对 Wwise 源码进行完整的线程生命周期修复,也不是所有奥日 2 闪退的通用补丁。
|
||
|
||
## 2. 故障现象与最初线索
|
||
|
||
用户描述:加载画面中小精灵跑步时经常闪退;极少数可以进入游戏,却没有声音。此前已尝试限制 CPU 核心、禁用部分声卡或显卡,没有解决。两个游戏目录都出现问题。同系统版本的 VMware 虚拟机可以运行,其中使用的应该是版本 A。
|
||
|
||
这些信息最初只用于确定调查顺序:
|
||
|
||
| 线索 | 可以支持的判断 | 不能直接推出的结论 |
|
||
| --- | --- | --- |
|
||
| 闪退伴随偶尔无声 | 值得检查音频初始化是否共同失败 | 一定是物理声卡驱动损坏 |
|
||
| 两个版本都失败 | 应比较共同组件、用户配置及宿主环境 | 两份游戏所有文件都相同 |
|
||
| 同系统版本虚拟机可玩 | 环境差异值得调查 | 系统版本、显卡或 CPU 已被单独证明有问题 |
|
||
| 限制核心数未解决 | 简单减少游戏可用核心不是已验证方案 | 所有线程调度相关假设均被排除 |
|
||
|
||
虚拟机的具体 vCPU 拓扑、VMware 版本、音频驱动及游戏文件哈希,本轮没有直接采集。其可运行情况属于用户报告,不是严格控制变量实验。
|
||
|
||
## 3. 本机环境
|
||
|
||
### 3.1 系统、处理器与固件
|
||
|
||
| 项目 | 采集结果 | 来源与解释 |
|
||
| --- | --- | --- |
|
||
| 操作系统 | Windows 10 Pro for Workstations,22H2,x64 | Windows 注册表;中文名称为专业工作站版 |
|
||
| 系统构建 | `19045.6456` | `CurrentBuild` 与 `UBR` |
|
||
| Windows 处理器名称 | `Genuine Intel(R) 0000` | 处理器注册表字段 |
|
||
| CPU 标识 | `Intel64 Family 6 Model 186 Stepping 2` | 同上 |
|
||
| 固件系统产品名 | `i9-13900HK(ES)` | SMBIOS 对应注册表字段;不等于已独立鉴定零售型号 |
|
||
| 当前可用逻辑处理器 | 20 | .NET 运行时与进程亲和性查询;不是物理核心数 |
|
||
| 后续对照测试的进程/系统掩码 | `0xFFFFF / 0xFFFFF` | `GetProcessAffinityMask` |
|
||
| 物理内存 | 约 32 GB;崩溃报告记载 32600 MiB | 原始崩溃日志;本轮没有采集内存条型号、数量或频率 |
|
||
| 主板制造商字段 | `Intel` | 固件字段原样记录 |
|
||
| 主板产品字段 | `Z790M GAMING WIFI D5` | 固件字段原样记录 |
|
||
| BIOS 厂商 | American Megatrends International, LLC. | 固件字段 |
|
||
| BIOS 版本/日期 | `Leo-Patch 1.5` / `07/26/2024` | 固件字段 |
|
||
|
||
ES 表示 Engineering Sample,即工程样品标识。本报告记录这一环境特征,不据此归因。也没有从“20 个逻辑处理器”推算未经验证的物理核心数或大小核分布。
|
||
|
||
### 3.2 显示环境
|
||
|
||
游戏日志明确选择了 **AMD Radeon RX 9070 XT**,使用 Direct3D 11、功能级别 11.1,显存报告为 16188 MiB。显卡初始化已完成,不能仅凭机器装了多个显示组件就断定它们参与了这次崩溃。
|
||
|
||
| 注册的显示组件 | 驱动版本 | 驱动日期 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| AMD Radeon RX 9070 XT | `32.0.31041.1004` | 2026-08-17 | 本次游戏日志中的渲染设备 |
|
||
| NVIDIA Tesla P4 | `31.0.15.3770` | 2023-10-18 | 注册表中存在;未证明参与本次渲染 |
|
||
| Zako Display Adapter | `0.23.25.226` | 2026-04-25 | 注册表中存在 |
|
||
| Microsoft Remote Display Adapter | `10.0.19041.5794` | 字段为 2006-06-21 | 存在多个注册项,不代表同时连接多个活动设备 |
|
||
|
||
### 3.3 音频环境
|
||
|
||
| 注册的音频组件 | 驱动版本 | 驱动日期 |
|
||
| --- | --- | --- |
|
||
| Realtek High Definition Audio | `6.0.9239.1` | 2021-09-16 |
|
||
| AMD High Definition Audio Device | `10.0.1.41` | 2025-05-26 |
|
||
| Steam Streaming Speakers | `17.56.13.764` | 2017-07-20 |
|
||
| VB-Audio Virtual Cable | `3.3.1.7` | 2024-10-07 |
|
||
| AMD Streaming Audio Device | `1.0.0.14` | 2024-06-05 |
|
||
| Microsoft Streaming Service Proxy | `10.0.19041.1` | 2006-06-21 |
|
||
|
||
这里列的是注册信息,其中可能包含停用、断开或历史设备。本轮没有把某一虚拟音频设备认定为根因,也没有通过卸载驱动来实现修复。
|
||
|
||
环境采集脚本为 `ori-research/Collect-Environment.ps1`,快照为 `ori-research/environment.json`。它不采集账号名、序列号、许可证或网络地址。
|
||
|
||
### 3.4 两个游戏版本
|
||
|
||
| 项目 | 版本 A:目录名含空格 | 版本 B:目录名含下划线 |
|
||
| --- | --- | --- |
|
||
| Unity 引擎 | `2018.4.11f1 (0dbcfe58b2dc)` | `2018.4.24f1 (5ebab45375c4)` |
|
||
| 日志 Build Id | `5257` | `8978` |
|
||
| 日志 Project Revision | `140897` | `147855` |
|
||
| 日志 Revision Time | `2020-05-12 03:56:26` | `2020-11-18 06:35:46` |
|
||
| GameAssembly.dll 大小 | 74,333,696 字节 | 77,572,608 字节 |
|
||
| AkSoundEngine.dll 大小 | 2,719,232 字节 | 2,719,232 字节 |
|
||
| Wwise SDK | `2019.1.7 Build 7135` | `2019.1.7 Build 7135` |
|
||
| 音频 DLL 原始 SHA-256 | 两者相同,见第 8 节 | 两者相同,见第 8 节 |
|
||
| 本轮验证覆盖 | 动态故障复现、内存修正、普通启动、音频会话 | 补丁文件匹配、安装及校验 |
|
||
|
||
Build Id 和 Revision Time 来自日志,不能直接当作游戏平台销售版本号。不能只凭 Unity 或 Wwise 版本字符串判断另一个文件是否适用补丁,必须核对完整哈希。
|
||
|
||
## 4. 从日志中提取真正有区分力的证据
|
||
|
||
Unity 日志中出现了大量 `The referenced script ... is missing`、资源卸载信息和其他警告。它们数量多,却没有形成此次崩溃的直接证据链。
|
||
|
||
最有区分力的是重复出现的两条错误:
|
||
|
||
```text
|
||
Wwise: Cannot create streaming I/O device.
|
||
WwiseUnity: Failed to initialize the sound engine. Abort.
|
||
```
|
||
|
||
这里的 I/O 是 Input/Output,即输入输出。“streaming I/O device”指音频引擎内部读取声音数据的机制,不等于用户在设备管理器里看到的物理声卡。
|
||
|
||
崩溃栈大体如下:
|
||
|
||
```text
|
||
<unknown address>
|
||
AkSoundEngine.dll + 0x5B0C8 或 + 0x5B16E
|
||
KERNEL32!BaseThreadInitThunk
|
||
ntdll!RtlUserThreadStart
|
||
```
|
||
|
||
这表明故障发生在音频 DLL 启动的工作线程上,而不是把某一行普通 Unity 警告简单等同于原因。
|
||
|
||
### 4.1 导出符号名称可能误导
|
||
|
||
原始报告把音频栈帧标成 `AK::StreamMgr::SetFileLocationResolver`。没有完整 PDB 调试符号时,工具可能用附近的导出符号解释地址;这个名称不能证明真正执行的函数就是文件位置解析器。
|
||
|
||
本轮进一步计算模块相对地址并反汇编,才发现 `+0x5B0C5` 和 `+0x5B16B` 附近是虚函数间接调用。`+0x5B0C8`、`+0x5B16E` 分别是这些调用后的返回地址。
|
||
|
||
### 4.2 文本中的“写入冲突”也需要交叉检查
|
||
|
||
2026-09-16 原始 `error.log` 写的是 `Write to location ... caused an access violation`。但对应 `crash.dmp` 的异常记录为:
|
||
|
||
```text
|
||
ExceptionCode = 0xC0000005
|
||
ExceptionInformation[0] = 0x8
|
||
ExceptionInformation[1] = 与异常指令地址相同
|
||
```
|
||
|
||
根据微软的 `EXCEPTION_RECORD` 文档,参数 0 为 0、1、8 时,分别对应读取、写入、用户态 DEP 执行保护违规。本例是最后一种。[4]
|
||
|
||
结合栈中的间接调用和不属于正常模块代码区的目标地址,更符合“通过失效对象取得了错误的调用目标”,而不是仅仅向某个地址写数据。DEP 是 Data Execution Prevention,即数据执行保护;此次修复保留这个保护机制。
|
||
|
||
## 5. 动态实验设计及结果
|
||
|
||
诊断器使用 Windows 调试接口启动自己的子进程,没有附加到其他游戏进程,也没有安装系统钩子。它先校验原始 DLL 的 SHA-256,再在模块加载后设置软件断点。
|
||
|
||
软件断点把目标指令的首字节临时替换为 `INT3`。断点触发后读取寄存器,恢复原字节并把 RIP 指令指针退回原指令,使原指令正常执行。本轮各断点只捕获首次命中,不构成长期、多实例线程调用的完整追踪器。
|
||
|
||
Windows x64 调用约定把前几个整数或指针参数放在 RCX、RDX、R8、R9,返回值通常在 RAX。[5] 因此可在 `SetThreadAffinityMask` 前读取 RDX 中的掩码,在调用后读取 RAX。错误码通过目标线程 TEB 的 LastErrorValue 读取,而不是读取调试器自身的 `GetLastError`;本次 x64 TEB 偏移为 `0x68`,它属于诊断实现细节,不应推广为跨架构稳定接口。
|
||
|
||
### 5.1 实验对照
|
||
|
||
| 实验 | 修改内容 | 观察结果 | 意义 |
|
||
| --- | --- | --- | --- |
|
||
| 原始 DLL | 无业务逻辑修改,仅断点跟踪 | 优先级调用返回 1;亲和性调用返回 0,错误码 87;随后访问冲突 | 确认直接失败调用 |
|
||
| 截断掩码 | 调用前将 `0xFFFFFFFC` 改为 `0xFFFFC` | 仍返回 0、错误 87,随后崩溃 | 单纯去掉高位不是本机充分修复 |
|
||
| 自动调度 | 在进程内将指定 setter 写入的掩码设为 0 | 读取线程属性变为 `[0, 0, 131072]`,通过初始化及后续加载 | 支持回避显式线程绑定 |
|
||
| 独立线程探针 | 仅修改探针自身线程的亲和性,并恢复 | 包括 `0xFFFFC`、`0xFFFFFFFC` 在内的测试值返回成功 | 不能将失败概括为全系统普遍不支持 |
|
||
| 磁盘补丁后普通启动 | 无调试器、无运行时补丁 | 初始化通过,进入后续场景,检测到声音信号,用户主动退出 | 排除仅在调试器下才成功的情况 |
|
||
|
||
原始记录的线程参数为:
|
||
|
||
```text
|
||
nPriority = 0
|
||
dwAffinityMask = 4294967292 = 0xFFFFFFFC
|
||
uStackSize = 131072 = 128 KiB
|
||
|
||
SetThreadPriority -> 1
|
||
SetThreadAffinityMask -> 0
|
||
LastErrorValue -> 87 = ERROR_INVALID_PARAMETER
|
||
后续异常 -> 0xC0000005
|
||
```
|
||
|
||
微软文档规定线程亲和性应是进程允许范围的子集,并说明不允许的位可导致 `ERROR_INVALID_PARAMETER`。[3] 这给出了最初的合理假设。但本机截断实验仍失败,独立探针又成功,所以研究结论必须保留这些反例,不能只照文档把掩码越界写成最终根因。
|
||
|
||
### 5.2 时间和样本限制
|
||
|
||
最初 40 秒的跟踪没有等到音频模块加载,不能把这轮算作成功或失败。用户曾将游戏切到后台,可能影响加载推进。因此,本轮耗时不能用于比较启动性能。
|
||
|
||
自动调度实验总观察上限为 180 秒,其中从音频模块加载到脚本按计划停止约 118.6 秒;不是连续完整游玩 3 分钟。最后的普通启动没有自动关闭计时器,用户确认是自己关闭的。
|
||
|
||
29 份历史报告是已产生的崩溃样本,不包含全部启动尝试,且部分来自此前不同排障设置。`28/29` 是崩溃路径分类,不能当作游戏启动失败率,也不能据此计算补丁治愈率。
|
||
|
||
## 6. 线程生命周期问题的原理
|
||
|
||
### 6.1 关键静态位置
|
||
|
||
以下地址都是 RVA(Relative Virtual Address,模块相对虚拟地址),不是固定的运行时绝对地址:
|
||
|
||
| RVA | 观察到的操作 |
|
||
| --- | --- |
|
||
| `0x5B090` | 工作线程入口附近 |
|
||
| `0x5B0C5` | `call qword ptr [rax+0x18]` |
|
||
| `0x5B16B` | `call qword ptr [rax+0x10]` |
|
||
| `0x5B294` | 调用 `CreateThread`,创建标志为 0 |
|
||
| `0x5B2B9` | 调用 `SetThreadPriority` |
|
||
| `0x5B2BF` | 检查优先级调用结果 |
|
||
| `0x5B2D8` | 调用 `SetThreadAffinityMask` |
|
||
| `0x5B2DE` | 检查亲和性调用结果 |
|
||
| `0x5B2E3` 至 `0x5B2ED` | 失败路径关闭线程句柄并清零 |
|
||
| `0x5B450` 附近 | 清理函数;线程句柄存在时才等待线程 |
|
||
|
||
`CreateThread` 没有使用 `CREATE_SUSPENDED`。微软文档明确说明这种情况下线程可能在 `CreateThread` 返回前就开始执行。[2]
|
||
|
||
### 6.2 为什么关闭句柄不等于停止线程
|
||
|
||
句柄可以理解为访问系统对象的一张票。关掉票并不会让线程立即停止。微软 `CloseHandle` 文档也明确指出:关闭线程句柄不会终止对应线程。[1]
|
||
|
||
下面是从已观察控制流抽象出的伪代码,不是厂商源码,也不包含所有字段和分支:
|
||
|
||
```cpp
|
||
threadHandle = CreateThread(/* 创建后立即可运行 */);
|
||
|
||
if (!ConfigurePriorityAndAffinity(threadHandle)) {
|
||
CloseHandle(threadHandle);
|
||
threadHandle = nullptr;
|
||
return InitializationFailed;
|
||
}
|
||
|
||
// 后续清理函数中的关键关系
|
||
SignalStopEvent();
|
||
if (threadHandle != nullptr) {
|
||
WaitForSingleObject(threadHandle, INFINITE);
|
||
CloseHandle(threadHandle);
|
||
}
|
||
CloseSynchronizationEvents();
|
||
// 调用方之后可能销毁或复用线程依赖的对象。
|
||
```
|
||
|
||
问题在于失败路径让“有没有句柄”与“有没有存活线程”不再等价。句柄清空后,后续清理可能跳过等待,仍存活的线程继续访问已经失效的状态。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant M as 初始化线程
|
||
participant W as 音频读取线程
|
||
M->>W: CreateThread,立即可运行
|
||
M->>M: SetThreadPriority 成功
|
||
M->>M: SetThreadAffinityMask 失败,错误 87
|
||
M->>M: CloseHandle 并清空句柄
|
||
Note over M,W: 关闭句柄不会终止线程
|
||
M->>M: 初始化失败,进入清理
|
||
Note over M,W: 失去句柄后,后续等待可能被跳过
|
||
W->>W: 通过失效对象进行间接调用
|
||
W->>W: 目标地址不可执行,0xC0000005
|
||
```
|
||
|
||
静态代码直接证明了句柄关闭、清零及清理等待条件;动态实验直接证明了调用失败和随后发生的访问冲突。对具体对象已经被释放或复用的判断属于由这些证据强烈支持的生命周期推断,而不是本轮完整记录过的每一次内存管理操作。
|
||
|
||
### 6.3 为什么偶尔进入却无声
|
||
|
||
线程调度顺序和内存复用存在时间差。如果初始化失败,声音引擎可以处于未就绪状态;异常线程不一定在同一时刻撞上不可执行地址。这为“经常加载闪退、偶尔进入但无声”提供了统一且与实验吻合的解释。
|
||
|
||
它仍然不意味着所有无声现象都属于本问题:设备选择、静音、声道或其他驱动故障需要各自的证据。
|
||
|
||
## 7. 补丁设计与精确变更
|
||
|
||
### 7.1 选择语义上已有的关闭绑定方式
|
||
|
||
目标函数:
|
||
|
||
```text
|
||
CSharp_AkThreadProperties_dwAffinityMask_set
|
||
```
|
||
|
||
它把 managed/native 边界传入的掩码写入线程属性的 `+4` 字段。导出表检查只发现这个导出名称指向该入口,没有发现其他导出名称与之共用入口。这减少了误改其他导出 setter 的风险,但不应误读为完成了整个 DLL 的所有内部引用证明。
|
||
|
||
原始线程创建逻辑对掩码 0 有直接分支:不调用 `SetThreadAffinityMask`,继续检查其余初始化条件。补丁利用的就是本 DLL 已经存在的逻辑。
|
||
|
||
### 7.2 地址、机器码和反汇编
|
||
|
||
| 项目 | 值 |
|
||
| --- | --- |
|
||
| PE 首选镜像基址 | `0x180000000` |
|
||
| 函数 RVA | `0x121F0` |
|
||
| 首选基址下的函数地址 | `0x1800121F0` |
|
||
| 文件偏移 | `0x115F0` |
|
||
| 校验/替换区域长度 | 16 字节 |
|
||
| 实际变化字节数 | 8 |
|
||
|
||
RVA 和文件偏移分别属于内存映射与磁盘布局,不能混用。运行时还存在 ASLR(Address Space Layout Randomization,地址空间布局随机化),因此动态工具使用“实际加载基址 + RVA”。
|
||
|
||
原始字节:
|
||
|
||
```text
|
||
48 85 C9 74 03 89 51 04 C3 CC CC CC CC CC CC CC
|
||
```
|
||
|
||
原始逻辑:
|
||
|
||
```asm
|
||
test rcx, rcx
|
||
je original_ret
|
||
mov dword ptr [rcx+4], edx
|
||
original_ret:
|
||
ret
|
||
```
|
||
|
||
补丁字节:
|
||
|
||
```text
|
||
48 85 C9 74 07 C7 41 04 00 00 00 00 C3 CC CC CC
|
||
```
|
||
|
||
补丁逻辑:
|
||
|
||
```asm
|
||
test rcx, rcx
|
||
je patched_ret
|
||
mov dword ptr [rcx+4], 0
|
||
patched_ret:
|
||
ret
|
||
```
|
||
|
||
变化的 8 个文件偏移为:`0x115F4`、`0x115F5`、`0x115F6`、`0x115F8`、`0x115F9`、`0x115FA`、`0x115FB`、`0x115FC`。
|
||
|
||
保留空指针检查,修改对应跳转距离。新指令使用原函数尾部的填充区域,未侵入从 `0x12200` 开始的下一段代码。此函数没有新增栈操作或额外寄存器保存逻辑。补丁后的候选文件已经再次反汇编,确认得到上述指令,而不只是做了十六进制替换。
|
||
|
||
### 7.3 影响范围
|
||
|
||
补丁影响此 DLL 内经该导出 setter 配置的 Wwise 线程亲和性,可能不止一个读取线程。它允许这些线程继承 Windows 和进程当前的调度限制,不保证覆盖 DLL 内所有绕过 setter 的直接赋值路径。
|
||
|
||
显式绑定可能本来有性能目的,取消后需要关注长时间游玩的音频稳定性和性能。本轮没有完成帧时间、音频延迟、休眠唤醒或长时间游戏测试。
|
||
|
||
补丁没有修改 Steam 接口、授权校验、存档格式、系统安全机制或全局处理器设置。
|
||
|
||
## 8. 安装器的防误用设计
|
||
|
||
只接受以下原始文件:
|
||
|
||
```text
|
||
SHA-256:
|
||
8F90A4A3856540CB97D03CA07B4BB10B7A08F7B4AB3C524BE2BEC1AFDD29FECA
|
||
```
|
||
|
||
补丁后结果必须为:
|
||
|
||
```text
|
||
SHA-256:
|
||
62B2079A988D23EB251D4E83366AC04068A6F47E0D38EFE92A76DF022826F9F0
|
||
```
|
||
|
||
安装器 `Ori-AudioFix.ps1` 的行为:
|
||
|
||
1. 检查游戏进程是否还在运行;修改前必须退出。
|
||
2. 对所有指定目录预检查完整哈希、原始指令和已有备份。
|
||
3. 在本地根据用户自己的 DLL 构造候选字节,不下载替代游戏 DLL。
|
||
4. 建立不覆盖既有备份的 `.before-ori-affinity-fix` 文件,并验证备份。
|
||
5. 写入同目录临时文件,验证结果哈希,再替换目标并复核。
|
||
6. 已安装相同补丁时跳过重复修改;未知文件或备份异常时停止。
|
||
7. 还原时只接受经过验证的原文件备份。
|
||
|
||
每个文件的替换使用同目录暂存和移动,减少中途写坏目标的风险;多个目录之间不是事务,不能承诺第二个目录失败时第一个自动回滚。输出会逐目录报告结果,备份可以分别用于恢复。
|
||
|
||
工作区副本已通过“安装 → 再次安装 → 状态检查 → 还原 → 原始哈希一致”的完整测试。分享版安装器还会要求读者明确指定自己的游戏目录。
|
||
|
||
## 9. 音频验证到底证明了什么
|
||
|
||
普通启动后的 Core Audio 会话检查结果为:
|
||
|
||
```json
|
||
{
|
||
"active": true,
|
||
"muted": false,
|
||
"volume": 1.0,
|
||
"peak_over_3_seconds": 0.010620015673339367
|
||
}
|
||
```
|
||
|
||
检查只读取该游戏进程的音频会话状态和电平,不录制声音、不访问麦克风。非零电平说明游戏实际向 Windows 音频管线输出了信号,比单纯看到菜单或没有错误日志更有说服力。
|
||
|
||
这不等于通过麦克风验证扬声器发声,也不替代用户的长期试听。用户已确认最后一次进程退出是自己关闭,不是再次闪退;本轮没有记录用户对全部音效、长时间游玩或所有关卡的验收。
|
||
|
||
## 10. 他人如何判断是否属于同一问题
|
||
|
||
症状相似只是入口,建议按下面顺序核对:
|
||
|
||
1. 保存原有日志,避免下一次启动覆盖。Unity 报告一般在 `%LOCALAPPDATA%\Temp\Moon Studios\OriAndTheWilloftheWisps\Crashes`;常规日志一般在 `%USERPROFILE%\AppData\LocalLow\Moon Studios\OriAndTheWilloftheWisps`。
|
||
2. 检查是否同时出现 Wwise streaming I/O 初始化失败,以及音频工作线程的访问冲突。
|
||
3. 对 `AkSoundEngine.dll` 执行 `Get-FileHash -Algorithm SHA256`,与第 8 节比对。
|
||
4. 只有哈希匹配时才使用本补丁;未知版本重新定位函数、布局和控制流。
|
||
5. 在正常启动方式下验证加载、声音和退出,再延长实际游玩观察时间。
|
||
|
||
不应只凭 `0xC0000005` 判断适用:这是通用访问冲突代码,显卡、注入组件、坏内存或其他程序错误也可能产生它。
|
||
|
||
建议读者反馈:Windows 构建号、CPU 标识及逻辑处理器数、Unity/Wwise 版本、原始 DLL 哈希、关键日志、补丁前后结果和测试时长。无需公开完整转储、账号名、序列号或许可证。
|
||
|
||
## 11. 后续可继续研究的问题
|
||
|
||
- 查询失败游戏线程的处理器组、CPU Sets、相关调度状态及完整访问权限,解释为什么独立探针通过而游戏线程失败。
|
||
- 比较宿主与虚拟机的同一份 DLL、线程属性、API 返回值和环境,而不只比较操作系统版本号。
|
||
- 对对象分配、销毁和工作线程退出进行更细跟踪,严格验证释放后使用路径。
|
||
- 在版本 B 和其他环境中复现,并进行多次正常启动、长时间游玩和音频连续性测试。
|
||
- 若能修改源码,更完整的修复应保证配置失败时仍保留停止和等待线程的能力,确保线程停止后再销毁其依赖对象。是否把亲和性失败降级为非致命错误,要由音频引擎的设计约束决定。
|
||
|
||
这些属于后续研究议题,不是本轮已经完成的结论。
|
||
|
||
## 12. 证据索引与参考资料
|
||
|
||
本地证据:
|
||
|
||
| 路径(相对工作区) | 用途 |
|
||
| --- | --- |
|
||
| `ori-evidence/original-2026-09-16-error.log` | 原始崩溃文本 |
|
||
| `ori-evidence/runtime-original-long/trace.jsonl` | 原始调用失败的动态证据 |
|
||
| `ori-evidence/runtime-clamped/trace.jsonl` | 截断掩码仍失败的反例 |
|
||
| `ori-evidence/runtime-automatic/trace.jsonl` | 掩码 0 的临时修正实验 |
|
||
| `ori-evidence/normal-patched-1/game.log` | 普通启动的后续场景日志 |
|
||
| `ori-evidence/normal-patched-1/audio-status.json` | 音频会话测量 |
|
||
| `ori-research/environment.json` | 相关硬件、驱动与系统快照 |
|
||
| `ori-research/sources/manifest.json` | 官方文档下载结果和原始 URL |
|
||
|
||
原始转储本身保留在用户自己的崩溃目录,不放入公开分享包。前述异常参数来自对该转储异常流的只读解析。
|
||
|
||
官方资料于 2026-09-16 访问并保存本地文本快照:
|
||
|
||
1. [Microsoft:CloseHandle](https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle):关闭句柄与线程终止的区别。
|
||
2. [Microsoft:CreateThread](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread):创建标志及线程可能提前运行的时序。
|
||
3. [Microsoft:SetThreadAffinityMask](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setthreadaffinitymask):掩码含义、返回值和错误条件。
|
||
4. [Microsoft:EXCEPTION_RECORD](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-exception_record):访问冲突参数 0、1、8 的含义。
|
||
5. [Microsoft:x64 calling convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170):参数寄存器与返回值。
|
||
6. [Microsoft:System Error Codes 0–499](https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-):错误 87 的定义。
|
||
|
||
工具说明:使用本机已有 PowerShell 7.6.4、Python 3.14 标准库和 GNU objdump,未为此安装额外依赖。全文把本地实测、静态推断、用户报告和未验证事项分开记录,方便复核和后续补充。
|
||
|
||
## 13. 补充:限制 CPU 核心为什么不能与本补丁混为一谈
|
||
|
||
用户补充,网上曾看到“限制 CPU 核心”的解决办法。应把操作对象与证据层级分开记录:
|
||
|
||
| 操作 | 实际改变什么 | 本案例的证据 |
|
||
| --- | --- | --- |
|
||
| 限制整个游戏的进程亲和性 | 缩小游戏进程可使用的逻辑处理器集合 | 用户此前尝试未解决;保留的旧启动文件包含 `/affinity F`,即低 4 个逻辑处理器。本轮未做所有进程掩码的重新枚举实验 |
|
||
| 修改一条音频线程请求的掩码 | 调整 `SetThreadAffinityMask` 调用的参数 | 本轮实测把 `0xFFFFFFFC` 改成 `0xFFFFC`,仍返回错误 87,随后崩溃 |
|
||
| 让音频配置的掩码为 0 | 让该 Wwise DLL 走“不调用显式绑定 API”的既有分支 | 本轮临时修正及磁盘补丁后的普通启动均通过,检测到实际音频输出 |
|
||
|
||
`/affinity F` 中的 F 是十六进制 15,对应 4 个逻辑处理器,不能直接叫“4 个物理核心”。也不等于改了 BIOS、系统核心数或音频 DLL 中的掩码。
|
||
|
||
进程亲和性给出可运行集合;游戏自己创建线程后,仍可能再次设置线程亲和性。限制进程不会自动修正 DLL 的配置参数,也不会修复失败路径里的线程生命周期。某些设置还可能与线程显式请求范围发生冲突,需要查看实际 API 返回值。
|
||
|
||
限制核心在其他故障中可能通过降低并发、改变调度时序或绕开特定处理器拓扑起作用。这是一般原理层面的可能性,不是本机已经验证的原因;即使让竞争问题暂时不出现,也不能直接当作源码层面的修复。
|
||
|
||
**适合公开发表的准确结论:**“用户此前限制整个游戏可用核心未解决;本轮进一步证明,缩小音频线程请求掩码仍失败,而取消音频线程显式绑定有效。”不能改写成“限制 CPU 核心已经被证明对所有奥日 2 玩家无效”。
|
||
|
||
## 14. 方法归类:用于诊断与兼容性修复的逆向工程
|
||
|
||
本轮工作属于逆向工程:在没有原始源码的情况下,通过已编译程序的指令、导出表、运行行为和系统接口,推断局部实现及故障机制。
|
||
|
||
- **反汇编(静态分析):**将机器指令翻译成汇编,检查 `CreateThread`、绑定操作、句柄清理、条件分支和间接调用。本轮实际使用 GNU objdump。
|
||
- **动态分析:**让程序运行,在目标位置设置断点,读取寄存器、目标线程错误码和后续异常。
|
||
- **最小兼容性修补:**利用已经确认的函数边界及语义,只修改目标 setter,并验证正常启动和音频输出。
|
||
|
||
这不意味着恢复了完整 C++ 源码。报告中的 C++ 风格代码是解释控制关系的伪代码;原始变量名、类设计及全部调用方语义仍有缺失。PDB 符号不完整时,也必须警惕导出函数名被拿来解释邻近地址。
|
||
|
||
本次用途是定位故障、恢复兼容性和输出可复核的补丁;研究对象与改动都限于相关音频行为。结论的依据是日志、指令、输入参数、返回值和对照实验,每一步都可以保存与复核。
|
||
|
||
## 15. 网络方案核查:相同症状不等于相同故障
|
||
|
||
核查日期:2026-09-16。以下是公开资料核对,不代表本机执行过每一项办法。完整比较见工作区的《奥日2网络方案核查.md》;分享包中的文件名为 `COMMUNITY-REVIEW.zh-CN.md`。
|
||
|
||
| 方案 | 已读来源与证据 | 与本案例的关系 |
|
||
| --- | --- | --- |
|
||
| 限制 CPU 核心 | [性能教程](https://www.ghostarrow.com/how-to-improve-performance-in-ori-and-the-will-of-the-wisps)第 18 项针对游玩时接近 100% CPU 占用的卡顿,通过 Process Lasso 在阈值触发时减少核心 | 与本例启动时音频线程错误 87 不是同一份证据;本地三类亲和性操作的区别见第 13 节 |
|
||
| 切换声音输出、Sound 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 调整 | [RTX 5070 / 4K HDR 报告](https://steamcommunity.com/app/1057090/discussions/0/582806854239850133/)一次改变多项显示设置后成功 | 不能确定单项贡献;本机原版已在 1280×720 窗口调试中重现绑定失败,因此窗口化本身不够 |
|
||
| DXVK | [RTX 50 系列花屏帖](https://steamcommunity.com/app/1057090/discussions/0/695372304943750032/)作者更新报告图形破损消失 | 有图形兼容的成功报告;本机历史上有另一次 DXVK/DXGI 崩溃,不把它当音频修复 |
|
||
| 回退 Patch 2、检查系统预览版 | [官方 Patch 3.1](https://steamcommunity.com/app/1057090/discussions/0/2968397584537393710/)提供特定问题的回退分支;[开发者白屏帖](https://steamcommunity.com/app/1057090/discussions/0/3012312119189327120/)初步关联当时 Insider Dev 构建 | 是有条件的历史排障依据。本机为正式版 Win10 22H2,两份游戏的故障音频 DLL 又相同,不能因此断言重装或降级能修好 |
|
||
| 校验文件、修复依赖、调整驱动 | [启动排障清单](https://www.ghostarrow.com/ori-and-the-will-of-the-wisps-fix-wont-launch-black-screen-crashes)列出常见办法 | 对文件缺失、依赖加载或具体驱动回归可能有用;不能修正发布文件原有的线程清理逻辑 |
|
||
| 安装 Unity 引擎、各种“一键优化” | 同一启动清单提出安装 Unity,但没有给出相应机制或受控证据 | 游戏自带 Unity Player;无法从“使用 Unity 开发”推出“玩家必须安装编辑器”。作为通用修复依据不足 |
|
||
|
||
评价“玄学”时,指的是不区分场景、缺少对照却声称万能,而不是宣称某个设置永远不会影响程序。例如采样率或调度参数确实可能改变执行路径;但某一次成功,尤其多项修改后的成功,不足以确定因果。
|
||
|
||
[另一个无声讨论](https://steamcommunity.com/app/1057090/discussions/0/2968398851794293852/)中有采样率、设备、空间音频等多种建议,楼主却明确说未解决;数月后恢复,自己也不知道原因。因此不把建议列表误记为成功清单,也不把后来恢复直接归功于 Windows 更新。
|
||
|
||
公开页面留档位于 `ori-research/community-sources/`,获取结果见 `ori-community-articles-result.json`。本轮检索没有核对到同时包含本例相同 Wwise 调用、参数和错误 87 的独立复现报告;这不代表它不存在,也不构成首创声明。
|