Skip to content

Commit 5b6530e

Browse files
committed
Addendum #2 to vertex buffer lock fixes;
Return a dummy buffer. Keep a static 4–8 KB vertex buffer that we can lock ourselves. When the driver hands back null, we copy/zero into that dummy memory, hand its pointer to GTA, and mark it dirty so the driver never tries to use the bogus lock. Rendering for that draw call becomes a no-op (all zeros), but GTA keeps running. (This commit and 5a5fbc5 are to do things safer and avoid access violations)
1 parent 5a5fbc5 commit 5b6530e

File tree

2 files changed

+175
-9
lines changed

2 files changed

+175
-9
lines changed

Client/core/DXHook/CProxyDirect3DVertexBuffer.cpp

Lines changed: 166 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "CProxyDirect3DVertexBuffer.h"
1414
#include "CAdditionalVertexStreamManager.h"
1515
#include "CVertexStreamBoundingBoxManager.h"
16+
#include <algorithm>
17+
#include <cstring>
1618

1719
/////////////////////////////////////////////////////////////
1820
//
@@ -30,6 +32,11 @@ CProxyDirect3DVertexBuffer::CProxyDirect3DVertexBuffer(IDirect3DDevice9* InD3DDe
3032
m_dwUsage = Usage;
3133
m_dwFVF = FVF;
3234
m_pool = Pool;
35+
m_bFallbackActive = false;
36+
m_fallbackOffset = 0;
37+
m_fallbackSize = 0;
38+
m_fallbackFlags = 0;
39+
m_fallbackStorage.clear();
3340

3441
m_stats.iCurrentCount++;
3542
m_stats.iCurrentBytes += m_iMemUsed;
@@ -113,18 +120,105 @@ HRESULT CProxyDirect3DVertexBuffer::Lock(UINT OffsetToLock, UINT SizeToLock, voi
113120
pBoundingBoxManager->OnVertexBufferRangeInvalidated(m_pOriginal, OffsetToLock, SizeToLock);
114121
}
115122

123+
if (m_bFallbackActive)
124+
{
125+
m_bFallbackActive = false;
126+
m_fallbackOffset = 0;
127+
m_fallbackSize = 0;
128+
m_fallbackFlags = 0;
129+
}
130+
116131
*ppbData = nullptr;
117132
HRESULT hr = DoLock(OffsetToLock, SizeToLock, ppbData, Flags);
118133
HRESULT originalHr = hr;
119-
bool bPointerNull = false;
120134

121-
if (SUCCEEDED(hr))
135+
bool bPointerNullEvent = SUCCEEDED(hr) && (*ppbData == nullptr);
136+
bool bRetryAttempted = false;
137+
bool bRetrySucceeded = false;
138+
bool bFallbackUsed = false;
139+
bool bUnlockedAfterNull = false;
140+
DWORD retryFlags = Flags;
141+
142+
if (bPointerNullEvent)
122143
{
123-
bPointerNull = (*ppbData == nullptr);
144+
WriteDebugEvent(SString("Lock VertexBuffer: initial pointer null (Usage:%08x Flags:%08x Offset:%x Size:%x)", m_dwUsage, Flags, OffsetToLock,
145+
SizeToLock));
146+
147+
// Retry once for dynamic buffers using DISCARD
148+
if ((m_dwUsage & D3DUSAGE_DYNAMIC) && (Flags & D3DLOCK_READONLY) == 0)
149+
{
150+
bRetryAttempted = true;
151+
152+
retryFlags &= ~(DWORD)(D3DLOCK_NOOVERWRITE | D3DLOCK_DISCARD | D3DLOCK_READONLY);
153+
retryFlags |= D3DLOCK_DISCARD;
154+
155+
// Release the bogus lock result before retrying
156+
m_pOriginal->Unlock();
157+
bUnlockedAfterNull = true;
158+
159+
void* pRetryData = nullptr;
160+
hr = DoLock(OffsetToLock, SizeToLock, &pRetryData, retryFlags);
161+
162+
if (SUCCEEDED(hr) && pRetryData != nullptr)
163+
{
164+
*ppbData = pRetryData;
165+
bPointerNullEvent = false;
166+
bRetrySucceeded = true;
167+
WriteDebugEvent(SString("Lock VertexBuffer: retry with DISCARD succeeded (Flags:%08x)", retryFlags));
168+
}
169+
else
170+
{
171+
// Ensure we unlock on success with null pointer to avoid leaving the resource locked
172+
if (SUCCEEDED(hr))
173+
{
174+
m_pOriginal->Unlock();
175+
bUnlockedAfterNull = true;
176+
}
177+
}
178+
}
179+
180+
if (bPointerNullEvent)
181+
{
182+
if (!bUnlockedAfterNull && SUCCEEDED(hr))
183+
{
184+
m_pOriginal->Unlock();
185+
bUnlockedAfterNull = true;
186+
}
187+
// Fall back to artificial buffer
188+
UINT clampedOffset = std::min(OffsetToLock, m_iMemUsed);
189+
UINT clampedSize = SizeToLock;
190+
if (clampedOffset + clampedSize > m_iMemUsed)
191+
clampedSize = m_iMemUsed - clampedOffset;
192+
if (clampedSize == 0 && clampedOffset < m_iMemUsed)
193+
clampedSize = m_iMemUsed - clampedOffset;
194+
195+
// Ensure we have some storage to hand back (even zero-sized locks receive a valid pointer)
196+
size_t requiredStorage = std::max<size_t>(static_cast<size_t>(clampedSize), static_cast<size_t>(1));
197+
if (m_fallbackStorage.size() < requiredStorage)
198+
m_fallbackStorage.resize(requiredStorage);
199+
200+
if (clampedSize > 0 && (Flags & D3DLOCK_READONLY))
201+
memset(m_fallbackStorage.data(), 0, clampedSize);
202+
203+
*ppbData = m_fallbackStorage.data();
204+
205+
m_bFallbackActive = true;
206+
m_fallbackOffset = clampedOffset;
207+
m_fallbackSize = clampedSize;
208+
m_fallbackFlags = Flags;
209+
210+
bFallbackUsed = true;
211+
bPointerNullEvent = true;
212+
213+
hr = D3D_OK;
214+
215+
WriteDebugEvent(SString("Lock VertexBuffer: engaged fallback buffer (Offset:%x Size:%x Flags:%08x)", m_fallbackOffset, m_fallbackSize,
216+
m_fallbackFlags));
217+
}
124218
}
125219

126220
// Report problems
127-
if (FAILED(hr) || bPointerNull)
221+
if (FAILED(hr) || bPointerNullEvent)
128222
{
129223
struct
130224
{
@@ -133,7 +227,7 @@ HRESULT CProxyDirect3DVertexBuffer::Lock(UINT OffsetToLock, UINT SizeToLock, voi
133227
uint uiLogEventId;
134228
} info;
135229
HRESULT reportHr = FAILED(hr) ? hr : D3DERR_INVALIDCALL;
136-
if (bPointerNull && originalHr == D3D_OK)
230+
if (bPointerNullEvent && originalHr == D3D_OK)
137231
info = {"result NULL", 8621, 621};
138232
else if (reportHr == STATUS_ARRAY_BOUNDS_EXCEEDED)
139233
info = {"offset out of range", 8622, 622};
@@ -142,16 +236,80 @@ HRESULT CProxyDirect3DVertexBuffer::Lock(UINT OffsetToLock, UINT SizeToLock, voi
142236
else
143237
info = {"fail", 8620, 620};
144238

145-
SString strMessage("Lock VertexBuffer [%s] hr:%x origHr:%x returnHr:%x pointerNull:%u Length:%x Usage:%x FVF:%x Pool:%x OffsetToLock:%x SizeToLock:%x Flags:%x",
146-
info.szText, reportHr, originalHr, hr, static_cast<uint>(bPointerNull), m_iMemUsed, m_dwUsage, m_dwFVF, m_pool, OffsetToLock, SizeToLock,
147-
Flags);
239+
SString strMessage(
240+
"Lock VertexBuffer [%s] hr:%x origHr:%x returnHr:%x pointerNull:%u retryAttempted:%u retrySucceeded:%u fallback:%u fallbackSize:%x fallbackFlags:%x"
241+
" unlockedAfterNull:%u Length:%x Usage:%x FVF:%x Pool:%x OffsetToLock:%x SizeToLock:%x Flags:%x retryFlags:%x",
242+
info.szText, reportHr, originalHr, hr, static_cast<uint>(bPointerNullEvent), static_cast<uint>(bRetryAttempted),
243+
static_cast<uint>(bRetrySucceeded), static_cast<uint>(bFallbackUsed), m_fallbackSize, m_fallbackFlags, static_cast<uint>(bUnlockedAfterNull),
244+
m_iMemUsed, m_dwUsage, m_dwFVF, m_pool, OffsetToLock, SizeToLock, Flags, retryFlags);
148245
WriteDebugEvent(strMessage);
149246
AddReportLog(info.uiReportId, strMessage);
150247
CCore::GetSingleton().LogEvent(info.uiLogEventId, "Lock VertexBuffer", "", strMessage);
151248
}
152249
return hr;
153250
}
154251

252+
/////////////////////////////////////////////////////////////
253+
//
254+
// CProxyDirect3DVertexBuffer::Unlock
255+
//
256+
// Apply fallback data if we had to hand out an artificial buffer
257+
//
258+
/////////////////////////////////////////////////////////////
259+
HRESULT CProxyDirect3DVertexBuffer::Unlock()
260+
{
261+
if (!m_bFallbackActive)
262+
return m_pOriginal->Unlock();
263+
264+
HRESULT copyResult = D3D_OK;
265+
266+
if ((m_fallbackFlags & D3DLOCK_READONLY) == 0 && m_fallbackSize > 0)
267+
{
268+
UINT offset = std::min(m_fallbackOffset, m_iMemUsed);
269+
UINT size = m_fallbackSize;
270+
if (offset + size > m_iMemUsed)
271+
size = (offset < m_iMemUsed) ? (m_iMemUsed - offset) : 0;
272+
273+
if (size > 0)
274+
{
275+
DWORD writeFlags = m_fallbackFlags;
276+
writeFlags &= ~(DWORD)(D3DLOCK_NOOVERWRITE | D3DLOCK_READONLY);
277+
if (m_dwUsage & D3DUSAGE_DYNAMIC)
278+
writeFlags |= D3DLOCK_DISCARD;
279+
280+
void* pReal = nullptr;
281+
HRESULT lockHr = DoLock(offset, size, &pReal, writeFlags);
282+
283+
if (SUCCEEDED(lockHr) && pReal != nullptr)
284+
{
285+
memcpy(pReal, m_fallbackStorage.data(), size);
286+
HRESULT unlockHr = m_pOriginal->Unlock();
287+
if (FAILED(unlockHr))
288+
copyResult = unlockHr;
289+
}
290+
else
291+
{
292+
if (SUCCEEDED(lockHr))
293+
m_pOriginal->Unlock();
294+
295+
WriteDebugEvent(SString("Unlock VertexBuffer: failed to copy fallback data (lockHr:%x offset:%x size:%x flags:%08x)", lockHr, offset, size,
296+
writeFlags));
297+
copyResult = FAILED(lockHr) ? lockHr : D3DERR_INVALIDCALL;
298+
}
299+
}
300+
}
301+
302+
WriteDebugEvent(SString("Unlock VertexBuffer: fallback completed (offset:%x size:%x flags:%08x result:%x)", m_fallbackOffset, m_fallbackSize,
303+
m_fallbackFlags, copyResult));
304+
305+
m_bFallbackActive = false;
306+
m_fallbackOffset = 0;
307+
m_fallbackSize = 0;
308+
m_fallbackFlags = 0;
309+
310+
return copyResult;
311+
}
312+
155313
/////////////////////////////////////////////////////////////
156314
//
157315
// CProxyDirect3DVertexBuffer::DoLock

Client/core/DXHook/CProxyDirect3DVertexBuffer.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#pragma once
1313

1414
#include <d3d9.h>
15+
#include <cstdint>
16+
#include <vector>
1517
#include "CProxyDirect3DDevice9.h" // Include full definition for SResourceMemory
1618

1719
DEFINE_GUID(CProxyDirect3DVertexBuffer_GUID, 0x128A025E, 0x0100, 0x04F1, 0x40, 0x60, 0x53, 0x19, 0x44, 0x56, 0x59, 0x42);
@@ -39,7 +41,7 @@ class CProxyDirect3DVertexBuffer : public IDirect3DVertexBuffer9
3941

4042
/*** IDirect3DVertexBuffer9 methods ***/
4143
HRESULT __stdcall Lock(UINT OffsetToLock, UINT SizeToLock, void** ppbData, DWORD Flags);
42-
HRESULT __stdcall Unlock() { return m_pOriginal->Unlock(); }
44+
HRESULT __stdcall Unlock();
4345
HRESULT __stdcall GetDesc(D3DVERTEXBUFFER_DESC* pDesc) { return m_pOriginal->GetDesc(pDesc); }
4446

4547
// CProxyDirect3DVertexBuffer
@@ -56,4 +58,10 @@ class CProxyDirect3DVertexBuffer : public IDirect3DVertexBuffer9
5658
DWORD m_dwFVF;
5759
D3DPOOL m_pool;
5860
CProxyDirect3DDevice9::SResourceMemory& m_stats;
61+
62+
bool m_bFallbackActive;
63+
UINT m_fallbackOffset;
64+
UINT m_fallbackSize;
65+
DWORD m_fallbackFlags;
66+
std::vector<uint8_t> m_fallbackStorage;
5967
};

0 commit comments

Comments
 (0)