Skip to content

Commit 81e90e2

Browse files
committed
Fix sound crash on buffer creation failure
Fix #303
1 parent ca5b664 commit 81e90e2

3 files changed

Lines changed: 120 additions & 0 deletions

File tree

src/Components/Loader.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
#include "Modules/Vote.hpp"
7171
#include "Modules/Weapon.hpp"
7272
#include "Modules/Window.hpp"
73+
#include "Modules/Sound.hpp"
7374

7475
#include "Modules/BotLib/lPrecomp.hpp"
7576

@@ -160,6 +161,7 @@ namespace Components
160161
Register(new ServerList());
161162
Register(new Session());
162163
Register(new SlowMotion());
164+
Register(new Sound());
163165
Register(new StartupMessages());
164166
Register(new Stats());
165167
Register(new StringTable());

src/Components/Modules/Sound.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#include "Sound.hpp"
2+
3+
namespace Components
4+
{
5+
constexpr auto g_en (0x1AA4908); // global enable flag
6+
constexpr auto g_cf (0x79B174); // config value
7+
constexpr auto f_new (0x454E40); // allocator
8+
constexpr auto f_ini (0x6015D0); // ds init
9+
constexpr auto f_log (0x402500); // logger
10+
11+
const char err[] ("Error: Failed to create DirectSound play buffer\n");
12+
13+
// The original implementation has a nasty bug in the failure path: if the
14+
// DirectSound buffer creation fails, it attempts to clean up by calling
15+
// Release() on the interface pointer. The problem is that if creation failed,
16+
// the interface pointer is uninitialized, causing an immediate access
17+
// violation when trying to read the vtable.
18+
//
19+
__declspec (naked) int Sound::Init ()
20+
{
21+
__asm
22+
{
23+
cmp byte ptr ds:[g_en], 0
24+
jnz proceed
25+
xor eax, eax
26+
ret
27+
28+
proceed:
29+
push esi
30+
mov eax, f_new
31+
call eax
32+
mov esi, eax
33+
34+
test esi, esi
35+
jnz alloc_ok
36+
pop esi
37+
ret
38+
39+
alloc_ok:
40+
// Note that we explicitly load ECX/EAX to match the register state
41+
// for `f_ini`.
42+
//
43+
mov ecx, dword ptr ds:[esi+38h]
44+
mov eax, dword ptr ds:[esi+2Ch]
45+
mov eax, dword ptr ds:g_cf
46+
push edi
47+
48+
push eax
49+
mov dword ptr ds:[esi+8], eax
50+
51+
lea edi, [esi+4]
52+
push edi
53+
mov eax, f_ini
54+
call eax
55+
add esp, 8
56+
57+
test eax, eax
58+
jge success
59+
60+
push offset err
61+
push 9
62+
mov eax, f_log
63+
call eax
64+
add esp, 8
65+
66+
// CRASH FIX:
67+
//
68+
// The original code blindly dereferences [EDI] (the buffer pointer) to
69+
// find the Release() vfunc. But if f_ini failed, [EDI] is likely a
70+
// invalid pointer. We check it first.
71+
//
72+
mov eax, dword ptr ds:[edi]
73+
test eax, eax
74+
jz fail_clean
75+
76+
// It's valid, so we release the interface.
77+
//
78+
mov ecx, dword ptr ds:[eax]
79+
mov edx, dword ptr ds:[ecx+8] // Release() is usually at offset 8 in IUnknown
80+
push eax
81+
call edx
82+
83+
fail_clean:
84+
// Otherwise, we zero out the slot and bail out.
85+
//
86+
mov dword ptr ds:[edi], 0
87+
pop edi
88+
xor eax, eax
89+
pop esi
90+
ret
91+
92+
success:
93+
pop edi
94+
mov eax, esi
95+
pop esi
96+
ret
97+
}
98+
}
99+
100+
Sound::
101+
Sound ()
102+
{
103+
Utils::Hook (0x0463A80, Sound::Init, HOOK_JUMP).install ()->quick ();
104+
}
105+
}

src/Components/Modules/Sound.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
3+
namespace Components
4+
{
5+
class Sound : public Component
6+
{
7+
public:
8+
Sound();
9+
10+
private:
11+
static int Init();
12+
};
13+
}

0 commit comments

Comments
 (0)