Skip to content

Commit e0bbc88

Browse files
committed
Addendum to 0530c83: Fix last cases of SBO crash exit (which was only deferred on last patch)
1 parent 2ac449e commit e0bbc88

File tree

1 file changed

+121
-103
lines changed

1 file changed

+121
-103
lines changed

Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp

Lines changed: 121 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ static void __declspec(naked) CrashAverted()
4747
// clang-format on
4848
}
4949

50-
static bool HasReadAccess(DWORD dwProtect)
50+
static bool HasReadAccess(DWORD dwProtect) noexcept
5151
{
5252
if (dwProtect & PAGE_GUARD)
5353
return false;
@@ -61,7 +61,7 @@ static bool HasReadAccess(DWORD dwProtect)
6161
dwProtect == PAGE_EXECUTE_READ || dwProtect == PAGE_EXECUTE_READWRITE || dwProtect == PAGE_EXECUTE_WRITECOPY;
6262
}
6363

64-
static bool HasWriteAccess(DWORD dwProtect)
64+
static bool HasWriteAccess(DWORD dwProtect) noexcept
6565
{
6666
if (dwProtect & PAGE_GUARD)
6767
return false;
@@ -71,116 +71,124 @@ static bool HasWriteAccess(DWORD dwProtect)
7171
return dwProtect == PAGE_READWRITE || dwProtect == PAGE_WRITECOPY || dwProtect == PAGE_EXECUTE_READWRITE || dwProtect == PAGE_EXECUTE_WRITECOPY;
7272
}
7373

74-
static bool QueryRegionCached(uintptr_t uiAddress, MEMORY_BASIC_INFORMATION& outMbi, uintptr_t& outStart, uintptr_t& outEnd)
74+
static constexpr std::size_t REGION_CACHE_SIZE = 8;
75+
static constexpr std::uintptr_t NUM_LOWMEM_THRESHOLD = 0x10000;
76+
77+
#define NUM_LOWMEM_THRESHOLD_ASM 0x10000
78+
79+
struct CachedRegion
7580
{
76-
static thread_local MEMORY_BASIC_INFORMATION s_mbi;
77-
static thread_local uintptr_t s_start = 0;
78-
static thread_local uintptr_t s_end = 0;
81+
std::uintptr_t start{};
82+
std::uintptr_t end{};
83+
DWORD state{};
84+
DWORD protect{};
85+
};
7986

80-
if (s_start != 0 && uiAddress >= s_start && uiAddress <= s_end)
87+
static bool QueryRegionCached(std::uintptr_t address, DWORD& outState, DWORD& outProtect, std::uintptr_t& outEnd) noexcept
88+
{
89+
static thread_local CachedRegion s_cache[REGION_CACHE_SIZE]{};
90+
static thread_local std::size_t s_nextSlot{};
91+
92+
for (const auto& entry : s_cache)
8193
{
82-
outMbi = s_mbi;
83-
outStart = s_start;
84-
outEnd = s_end;
85-
return true;
94+
if (entry.start != 0 && address >= entry.start && address <= entry.end)
95+
{
96+
outState = entry.state;
97+
outProtect = entry.protect;
98+
outEnd = entry.end;
99+
return true;
100+
}
86101
}
87102

88-
MEMORY_BASIC_INFORMATION mbi;
89-
SIZE_T mbiResult = VirtualQuery(reinterpret_cast<LPCVOID>(uiAddress), &mbi, sizeof(mbi));
90-
if (mbiResult != sizeof(mbi))
103+
MEMORY_BASIC_INFORMATION mbi{};
104+
if (VirtualQuery(reinterpret_cast<LPCVOID>(address), &mbi, sizeof(mbi)) != sizeof(mbi))
91105
return false;
92106

93-
s_mbi = mbi;
94-
s_start = reinterpret_cast<uintptr_t>(mbi.BaseAddress);
107+
const auto regionSize = static_cast<std::uintptr_t>(mbi.RegionSize);
108+
if (regionSize == 0)
109+
return false;
95110

96-
const uintptr_t uiSize = static_cast<uintptr_t>(mbi.RegionSize);
97-
if (uiSize == 0)
98-
s_end = s_start;
99-
else
100-
{
101-
const uintptr_t uiEndPlusOne = s_start + uiSize;
102-
s_end = (uiEndPlusOne <= s_start) ? static_cast<uintptr_t>(-1) : (uiEndPlusOne - 1);
103-
}
111+
auto& slot = s_cache[s_nextSlot];
112+
s_nextSlot = (s_nextSlot + 1) % REGION_CACHE_SIZE;
113+
114+
slot.start = reinterpret_cast<std::uintptr_t>(mbi.BaseAddress);
115+
slot.state = mbi.State;
116+
slot.protect = mbi.Protect;
117+
118+
const auto endPlusOne = slot.start + regionSize;
119+
slot.end = (endPlusOne <= slot.start) ? static_cast<std::uintptr_t>(-1) : (endPlusOne - 1);
104120

105-
outMbi = s_mbi;
106-
outStart = s_start;
107-
outEnd = s_end;
121+
outState = slot.state;
122+
outProtect = slot.protect;
123+
outEnd = slot.end;
108124
return true;
109125
}
110126

111-
static bool IsReadablePtr(const void* pPtr, size_t uiSize)
127+
static bool IsReadablePtr(const void* ptr, std::size_t size) noexcept
112128
{
113-
if (pPtr == nullptr || uiSize == 0)
129+
if (ptr == nullptr || size == 0)
114130
return false;
115131

116-
const uintptr_t uiStart = reinterpret_cast<uintptr_t>(pPtr);
117-
if (uiStart < 0x10000)
132+
const auto start = reinterpret_cast<std::uintptr_t>(ptr);
133+
if (start < NUM_LOWMEM_THRESHOLD)
118134
return false;
119135

120-
const uintptr_t uiEnd = uiStart + uiSize - 1;
121-
if (uiEnd < uiStart)
136+
const auto end = start + size - 1;
137+
if (end < start)
122138
return false;
123139

124-
uintptr_t uiCur = uiStart;
125-
while (true)
140+
auto cur = start;
141+
for (;;)
126142
{
127-
MEMORY_BASIC_INFORMATION mbi;
128-
uintptr_t uiRegionStart = 0;
129-
uintptr_t uiRegionEnd = 0;
130-
if (!QueryRegionCached(uiCur, mbi, uiRegionStart, uiRegionEnd))
131-
return false;
132-
133-
if (mbi.State != MEM_COMMIT)
143+
DWORD state{}, protect{};
144+
std::uintptr_t regionEnd{};
145+
if (!QueryRegionCached(cur, state, protect, regionEnd))
134146
return false;
135147

136-
if (!HasReadAccess(mbi.Protect))
148+
if (state != MEM_COMMIT || !HasReadAccess(protect))
137149
return false;
138150

139-
if (uiRegionEnd >= uiEnd)
151+
if (regionEnd >= end)
140152
return true;
141153

142-
if (uiRegionEnd < uiCur || uiRegionEnd == static_cast<uintptr_t>(-1))
154+
if (regionEnd < cur || regionEnd == static_cast<std::uintptr_t>(-1))
143155
return false;
144156

145-
uiCur = uiRegionEnd + 1;
157+
cur = regionEnd + 1;
146158
}
147159
}
148160

149-
static bool IsWritablePtr(void* pPtr, size_t uiSize)
161+
static bool IsWritablePtr(void* ptr, std::size_t size) noexcept
150162
{
151-
if (pPtr == nullptr || uiSize == 0)
163+
if (ptr == nullptr || size == 0)
152164
return false;
153165

154-
const uintptr_t uiStart = reinterpret_cast<uintptr_t>(pPtr);
155-
if (uiStart < 0x10000)
166+
const auto start = reinterpret_cast<std::uintptr_t>(ptr);
167+
if (start < NUM_LOWMEM_THRESHOLD)
156168
return false;
157169

158-
const uintptr_t uiEnd = uiStart + uiSize - 1;
159-
if (uiEnd < uiStart)
170+
const auto end = start + size - 1;
171+
if (end < start)
160172
return false;
161173

162-
uintptr_t uiCur = uiStart;
163-
while (true)
174+
auto cur = start;
175+
for (;;)
164176
{
165-
MEMORY_BASIC_INFORMATION mbi;
166-
uintptr_t uiRegionStart = 0;
167-
uintptr_t uiRegionEnd = 0;
168-
if (!QueryRegionCached(uiCur, mbi, uiRegionStart, uiRegionEnd))
177+
DWORD state{}, protect{};
178+
std::uintptr_t regionEnd{};
179+
if (!QueryRegionCached(cur, state, protect, regionEnd))
169180
return false;
170181

171-
if (mbi.State != MEM_COMMIT)
182+
if (state != MEM_COMMIT || !HasWriteAccess(protect))
172183
return false;
173184

174-
if (!HasWriteAccess(mbi.Protect))
175-
return false;
176-
177-
if (uiRegionEnd >= uiEnd)
185+
if (regionEnd >= end)
178186
return true;
179187

180-
if (uiRegionEnd < uiCur || uiRegionEnd == static_cast<uintptr_t>(-1))
188+
if (regionEnd < cur || regionEnd == static_cast<std::uintptr_t>(-1))
181189
return false;
182190

183-
uiCur = uiRegionEnd + 1;
191+
cur = regionEnd + 1;
184192
}
185193
}
186194

@@ -1508,35 +1516,44 @@ static void __declspec(naked) HOOK_CrashFix_Misc34()
15081516
//
15091517
// Invalid list entry pointer (crash at 0x7F374A: mov esi, [eax])
15101518
// (https://pastebin.com/hFduf1JB)
1519+
// WARNING: No pushad/popad with C++ calls (corrupts /GS cookie > 0xC0000409)
15111520
////////////////////////////////////////////////////////////////////////
15121521
#define HOOKPOS_CrashFix_Misc35 0x7F374A
15131522
#define HOOKSIZE_CrashFix_Misc35 5
15141523
#define HOOKCHECK_CrashFix_Misc35 0x8B
15151524
DWORD RETURN_CrashFix_Misc35 = 0x7F374F;
15161525
DWORD RETURN_CrashFix_Misc35_Abort = 0x7F3760;
1517-
static void _declspec(naked) HOOK_CrashFix_Misc35()
1526+
1527+
static void __declspec(naked) HOOK_CrashFix_Misc35()
15181528
{
15191529
MTA_VERIFY_HOOK_LOCAL_SIZE;
15201530
// clang-format off
15211531
__asm
15221532
{
1523-
pushad
1533+
cmp eax, NUM_LOWMEM_THRESHOLD_ASM
1534+
jb abort
1535+
1536+
push eax
1537+
push ecx
1538+
push edx
15241539
push 4
15251540
push eax
15261541
call IsReadablePtr
15271542
add esp, 8
1528-
test eax, eax
1529-
popad
1530-
jnz ok
1531-
1532-
push 35
1533-
call CrashAverted
1534-
jmp RETURN_CrashFix_Misc35_Abort
1543+
test al, al
1544+
pop edx
1545+
pop ecx
1546+
pop eax
1547+
jz abort
15351548

1536-
ok:
15371549
mov esi, [eax]
15381550
add eax, 0FFFFFFF8h
15391551
jmp RETURN_CrashFix_Misc35
1552+
1553+
abort:
1554+
push 35
1555+
call CrashAverted
1556+
jmp RETURN_CrashFix_Misc35_Abort
15401557
}
15411558
// clang-format on
15421559
}
@@ -1555,46 +1572,31 @@ static void _declspec(naked) HOOK_CrashFix_Misc35()
15551572
DWORD RETURN_CrashFix_Misc36 = 0x7F3A0F;
15561573
DWORD RETURN_CrashFix_Misc36_Abort = 0x7F3A5C;
15571574

1558-
static bool CheckTextureName(DWORD ecxValue)
1559-
{
1560-
return IsReadablePtr((const void*)ecxValue, 1);
1561-
}
1562-
1563-
static void _declspec(naked) HOOK_CrashFix_Misc36()
1575+
static void __declspec(naked) HOOK_CrashFix_Misc36()
15641576
{
15651577
MTA_VERIFY_HOOK_LOCAL_SIZE;
15661578

15671579
// clang-format off
15681580
__asm
15691581
{
1570-
cmp ebx, 0x10000
1582+
cmp ebx, NUM_LOWMEM_THRESHOLD_ASM
15711583
jb abort
1572-
cmp ebx, 0x80000000
1573-
jae abort
15741584

15751585
// Replicate original code
15761586
lea eax, [ebx-8]
15771587
lea ecx, [eax+10h]
15781588

1579-
// Quick check: ecx (texture name ptr) must make sense
1580-
cmp ecx, 0x10000
1589+
cmp ecx, NUM_LOWMEM_THRESHOLD_ASM
15811590
jb abort
1582-
cmp ecx, 0x80000000
1583-
jae abort
15841591

1585-
// ecx must point readable memory
1586-
// save all state before call
15871592
push eax
15881593
push ecx
15891594
push edx
1590-
pushfd
1591-
1595+
push 1
15921596
push ecx
1593-
call CheckTextureName
1594-
add esp, 4
1597+
call IsReadablePtr
1598+
add esp, 8
15951599
test al, al
1596-
1597-
popfd
15981600
pop edx
15991601
pop ecx
16001602
pop eax
@@ -1616,24 +1618,33 @@ static void _declspec(naked) HOOK_CrashFix_Misc36()
16161618
//
16171619
// Invalid list head pointer (crash at 0x7F39B3: mov [esi+4], edx)
16181620
// (https://pastebin.com/pkWwsSih)
1621+
// WARNING: No pushad/popad with C++ calls (corrupts /GS cookie > 0xC0000409)
16191622
////////////////////////////////////////////////////////////////////////
16201623
#define HOOKPOS_CrashFix_Misc37 0x7F39B3
16211624
#define HOOKSIZE_CrashFix_Misc37 5
16221625
#define HOOKCHECK_CrashFix_Misc37 0x89
16231626
DWORD RETURN_CrashFix_Misc37 = 0x7F39B8;
1624-
static void _declspec(naked) HOOK_CrashFix_Misc37()
1627+
1628+
static void __declspec(naked) HOOK_CrashFix_Misc37()
16251629
{
16261630
MTA_VERIFY_HOOK_LOCAL_SIZE;
16271631
// clang-format off
16281632
__asm
16291633
{
1630-
pushad
1634+
cmp esi, NUM_LOWMEM_THRESHOLD_ASM
1635+
jb bad
1636+
1637+
push eax
1638+
push ecx
1639+
push edx
16311640
push 8
16321641
push esi
16331642
call IsWritablePtr
16341643
add esp, 8
1635-
test eax, eax
1636-
popad
1644+
test al, al
1645+
pop edx
1646+
pop ecx
1647+
pop eax
16371648
jz bad
16381649

16391650
cmp esi, ecx
@@ -1683,7 +1694,8 @@ static void OnVideoMemoryExhausted()
16831694
OnRequestDeferredStreamingMemoryRelief();
16841695
}
16851696

1686-
static void _declspec(naked) HOOK_CrashFix_Misc38()
1697+
// No pushad/popad with C++ calls (corrupts /GS cookie > 0xC0000409)
1698+
static void __declspec(naked) HOOK_CrashFix_Misc38()
16871699
{
16881700
MTA_VERIFY_HOOK_LOCAL_SIZE;
16891701

@@ -1694,9 +1706,15 @@ static void _declspec(naked) HOOK_CrashFix_Misc38()
16941706
test esi, esi
16951707
jnz ok
16961708

1697-
pushad
1709+
// Save only volatile registers before call
1710+
push eax
1711+
push ecx
1712+
push edx
16981713
call OnVideoMemoryExhausted
1699-
popad
1714+
pop edx
1715+
pop ecx
1716+
pop eax
1717+
17001718
push 38
17011719
call CrashAverted
17021720
jmp RETURN_CrashFix_Misc38_Skip

0 commit comments

Comments
 (0)