Skip to content

Commit 0e4baf5

Browse files
authored
Merge pull request #71 from CookiePLMonster/cdstream-deadlock
Fixed a deadlock in CdStream and LoadLibrary path translation heuristics
2 parents 37d9ee9 + 6e6ecd5 commit 0e4baf5

File tree

15 files changed

+309
-27
lines changed

15 files changed

+309
-27
lines changed

include/modloader/gta3/gta3.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ namespace modloader
7979

8080
template<uintptr_t addr, class Traits = dtraits::WinCreateFileA>
8181
using WinCreateFileA = modloader::basic_file_detour<Traits,
82-
injector::function_hooker_stdcall<addr, HANDLE(LPCTSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE)>,
83-
HANDLE, LPCTSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE>;
82+
injector::function_hooker_stdcall<addr, HANDLE(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE)>,
83+
HANDLE, LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE>;
8484

8585

8686
template<uintptr_t addr, class Traits = dtraits::LoadAtomic2Return>

include/modloader/modloader.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extern "C" {
2929
/* Version */
3030
#define MODLOADER_VERSION_MAJOR 0
3131
#define MODLOADER_VERSION_MINOR 3
32-
#define MODLOADER_VERSION_REVISION 5
32+
#define MODLOADER_VERSION_REVISION 7
3333
#ifdef NDEBUG
3434
#define MODLOADER_VERSION_ISDEV 0
3535
#else

include/modloader/util/path.hpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -311,9 +311,9 @@ namespace modloader
311311
* WinAPI-like function to check if a directory exists
312312
* @szPath: Directory to check
313313
*/
314-
inline BOOL IsDirectoryA(LPCTSTR szPath)
314+
inline BOOL IsDirectoryA(LPCSTR szPath)
315315
{
316-
DWORD dwAttrib = GetFileAttributes(szPath);
316+
DWORD dwAttrib = GetFileAttributesA(szPath);
317317
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
318318
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
319319
}
@@ -323,7 +323,7 @@ namespace modloader
323323
* WinAPI-like function to check if a file or directory exists
324324
* @szPath: Directory to check
325325
*/
326-
inline BOOL IsPathA(LPCTSTR szPath)
326+
inline BOOL IsPathA(LPCSTR szPath)
327327
{
328328
DWORD dwAttrib = GetFileAttributesA(szPath);
329329
return (dwAttrib != INVALID_FILE_ATTRIBUTES);
@@ -352,7 +352,7 @@ namespace modloader
352352
* WinAPI-like function to make sure a directory exists, if not, create it
353353
* @szPath: Directory to check
354354
*/
355-
inline BOOL MakeSureDirectoryExistA(LPCTSTR szPath)
355+
inline BOOL MakeSureDirectoryExistA(LPCSTR szPath)
356356
{
357357
if(!IsDirectoryA(szPath))
358358
{
@@ -371,7 +371,7 @@ namespace modloader
371371
* WinAPI-like function that copies the full directory @szFrom to @szTo
372372
* If @szTo doesn't exist, it is created
373373
*/
374-
inline BOOL CopyDirectoryA(LPCTSTR szFrom, LPCTSTR szTo)
374+
inline BOOL CopyDirectoryA(LPCSTR szFrom, LPCSTR szTo)
375375
{
376376
if(CreateDirectoryA(szTo, NULL))
377377
{
@@ -403,7 +403,7 @@ namespace modloader
403403
* DestroyDirectoryA
404404
* WinAPI-like function that deletes the path @szPath fully
405405
*/
406-
inline BOOL DestroyDirectoryA(LPCTSTR szPath)
406+
inline BOOL DestroyDirectoryA(LPCSTR szPath)
407407
{
408408
FilesWalk(szPath, "*.*", false, [&szPath](FileWalkInfo& file)
409409
{
@@ -429,7 +429,7 @@ namespace modloader
429429
* GetFileSize
430430
* WinAPI-like function that gets the file size of @szPath
431431
*/
432-
inline LONGLONG GetFileSize(LPCTSTR szPath)
432+
inline LONGLONG GetFileSize(LPCSTR szPath)
433433
{
434434
WIN32_FILE_ATTRIBUTE_DATA fad;
435435
return GetFileAttributesExA(szPath, GetFileExInfoStandard, &fad)?
@@ -471,7 +471,8 @@ namespace modloader
471471

472472
/* Enter on ctor, Leave on dtor */
473473
scoped_lock(CRITICAL_SECTION& cs)
474-
{ c = &cs; EnterCriticalSection(&cs); }
474+
: c(&cs)
475+
{ EnterCriticalSection(&cs); }
475476
~scoped_lock()
476477
{ LeaveCriticalSection(c); }
477478
};

src/plugins/gta3/std.asi/args_translator/hacks/FindCleoScripts.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ namespace hacks
247247
* Hacked FindFirstFileA
248248
*/
249249
template<>
250-
bool FindCleoScripts::Finder<aFindFirstFileA>(HANDLE& result, LPCTSTR& lpFileName, LPWIN32_FIND_DATAA& lpFindFileData)
250+
bool FindCleoScripts::Finder<aFindFirstFileA>(HANDLE& result, LPCSTR& lpFileName, LPWIN32_FIND_DATAA& lpFindFileData)
251251
{
252252
char version;
253253

src/plugins/gta3/std.asi/args_translator/translator_basic.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ struct path_translator_base
7777
bool bFindFirstFile; // ^
7878
bool bFindNextFile; // ^
7979
bool bFindClose; // ^
80+
bool bLoadLibrary; // ^
8081
bool bDoBassHack; // ^
8182

8283
// Almost static vars (he), the value is always the same in all objects
@@ -463,6 +464,7 @@ struct path_translator_basic : public path_translator_base
463464
bFindFirstFile = (Symbol == aFindFirstFileA) || (Symbol == aFindFirstFileW);
464465
bFindNextFile = (Symbol == aFindNextFileA) || (Symbol == aFindNextFileW);
465466
bFindClose = (Symbol == aFindClose);
467+
bLoadLibrary = (Symbol == aLoadLibraryA) || (Symbol == aLoadLibraryW) || (Symbol == aLoadLibraryExA) || (Symbol == aLoadLibraryExW);
466468
bIniOperations = false;
467469
}
468470
}

src/plugins/gta3/std.asi/args_translator/translator_stdcall.hpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,21 @@ struct path_translator_stdcall<Symbol, LibName, Ret(Args...)> : public path_tran
7272
}
7373

7474
if(!bDetoured)
75-
info.TranslateForCall(a...); // Translate the paths
75+
{
76+
if(info.base->bLoadLibrary)
77+
{
78+
auto f = (func_type) info.base->fun;
79+
result = f(a...);
80+
if(result != NULL)
81+
{
82+
// If DLL loaded without translating the path, abort translation and don't try to call it again
83+
bDetoured = true;
84+
}
85+
86+
}
87+
if(!bDetoured)
88+
info.TranslateForCall(a...); // Translate the paths
89+
}
7690
}
7791

7892
if(!bDetoured)

src/plugins/gta3/std.stream/backend.cpp

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,53 @@ extern "C"
7878
};
7979

8080

81+
void __stdcall CdStreamShutdownSync_Stub( CdStream* stream, size_t idx )
82+
{
83+
streaming->cdStreamSyncFuncs.Shutdown( &stream[idx] );
84+
}
85+
86+
uint32_t CdStreamSync( int32_t streamID )
87+
{
88+
static bool bInitFields = false;
89+
static CdStream** ppStreams;
90+
static BOOL* streamingInitialized;
91+
static BOOL* overlappedIO;
92+
93+
if ( !bInitFields )
94+
{
95+
if( gvm.IsSA() )
96+
{
97+
auto& cdinfo = *memory_pointer(0x8E3FE0).get<CdStreamInfoSA>();
98+
ppStreams = &cdinfo.pStreams;
99+
streamingInitialized = &cdinfo.streamingInitialized;
100+
overlappedIO = &cdinfo.overlappedIO;
101+
}
102+
else if( gvm.IsVC() || gvm.IsIII() )
103+
{
104+
ppStreams = memory_pointer(xVc(0x6F76FC)).get<CdStream*>();
105+
streamingInitialized = memory_pointer(xVc(0x6F7718)).get<BOOL>();
106+
overlappedIO = memory_pointer(xVc(0x6F7714)).get<BOOL>();
107+
}
108+
109+
bInitFields = true;
110+
}
111+
112+
CdStream* stream = &((*ppStreams)[streamID]);
113+
if ( *streamingInitialized )
114+
{
115+
scoped_lock lock( streaming->cdStreamSyncLock );
116+
streaming->cdStreamSyncFuncs.SleepCS( stream, &streaming->cdStreamSyncLock );
117+
stream->bInUse = 0;
118+
return stream->status;
119+
}
120+
121+
if ( *overlappedIO && stream->hFile != nullptr )
122+
{
123+
DWORD numBytesRead;
124+
return GetOverlappedResult( stream->hFile, &stream->overlapped, &numBytesRead, TRUE ) != 0 ? 0 : 254;
125+
}
126+
return 0;
127+
}
81128

82129
/*
83130
* Streaming thread
@@ -92,7 +139,7 @@ int __stdcall CdStreamThread()
92139
// Get reference to the addresses we'll use.
93140
if(gvm.IsSA())
94141
{
95-
auto& cdinfo = *memory_pointer(0x8E3FEC).get<CdStreamInfoSA>();
142+
auto& cdinfo = *memory_pointer(0x8E3FE0).get<CdStreamInfoSA>();
96143
pSemaphore = &cdinfo.semaphore;
97144
pQueue = &cdinfo.queue;
98145
ppStreams = &cdinfo.pStreams;
@@ -192,9 +239,14 @@ int __stdcall CdStreamThread()
192239

193240
// Cleanup
194241
if(bIsAbstract) streaming->CloseModel(sfile);
195-
cd->nSectorsToRead = 0;
196-
if(cd->bLocked) ReleaseSemaphore(cd->semaphore, 1, 0);
197-
cd->bInUse = false;
242+
{
243+
// This critical section fixes a deadlock with CdStreamThread present in original code
244+
scoped_lock xlock(streaming->cdStreamSyncLock);
245+
246+
cd->nSectorsToRead = 0;
247+
streaming->cdStreamSyncFuncs.Wake(cd);
248+
cd->bInUse = false;
249+
}
198250
}
199251
return 0;
200252
}
@@ -442,6 +494,36 @@ void CAbstractStreaming::Patch()
442494
// Making our our code for the stream thread would make things so much better
443495
MakeJMP(0x406560, raw_ptr(CdStreamThread));
444496

497+
// These are required so we can fix CdStream race condition
498+
MakeJMP( 0x406460, raw_ptr(CdStreamSync) );
499+
if( gvm.IsSA() )
500+
{
501+
const uint8_t mem[] = { 0xFF, 0x15 };
502+
WriteMemoryRaw( 0x406910, mem, sizeof(mem), true );
503+
WriteMemory( 0x406910 + 2, &streaming->cdStreamSyncFuncs.Initialize, true );
504+
MakeNOP( 0x406910 + 6, 4 );
505+
MakeNOP( 0x406910 + 0x16, 2 );
506+
}
507+
else if( gvm.IsVC() || gvm.IsIII() )
508+
{
509+
MakeNOP( xVc(0x4088F7), 8 );
510+
WriteMemory( xVc(0x4088F7) + 10, &streaming->cdStreamSyncFuncs.Initialize, true );
511+
WriteMemory( xVc(0x408919), uint8_t(0xEB), true );
512+
}
513+
514+
if( gvm.IsSA() )
515+
{
516+
const uint8_t mem[] = { 0x56, 0x50 };
517+
WriteMemoryRaw( 0x4063B5, mem, sizeof(mem), true );
518+
MakeCALL( 0x4063B5 + 2, raw_ptr(CdStreamShutdownSync_Stub), true );
519+
}
520+
else if( gvm.IsVC() || gvm.IsIII() )
521+
{
522+
const uint8_t mem[] = { 0x8D, 0x04, 0x29, 0x90 };
523+
WriteMemoryRaw( xVc(0x4086B6), mem, sizeof(mem), true );
524+
WriteMemory( xVc(0x4086B6) + 5 + 2, &streaming->cdStreamSyncFuncs.Shutdown, true );
525+
}
526+
445527
// We need to know the next model to be read before the CdStreamRead call happens
446528
if(gvm.IsSA())
447529
{
@@ -731,3 +813,12 @@ void CAbstractStreaming::Patch()
731813
}
732814
}
733815

816+
/*
817+
* Export for SilentPatch so it knows that this ML version patches the race condition
818+
*/
819+
extern "C" __declspec(dllexport)
820+
uint32_t CdStreamRaceConditionAware()
821+
{
822+
return 1;
823+
}
824+

src/plugins/gta3/std.stream/streaming.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77
#include <stdinc.hpp>
88
#include "streaming.hpp"
9+
#include "cdstreamsync.inl"
910
using namespace modloader;
1011

1112
CAbstractStreaming* streaming;
@@ -17,10 +18,13 @@ CAbstractStreaming* streaming;
1718
CAbstractStreaming::CAbstractStreaming()
1819
{
1920
InitializeCriticalSection(&cs);
21+
InitializeCriticalSectionAndSpinCount(&cdStreamSyncLock, 10);
22+
cdStreamSyncFuncs = CdStreamSyncFix::InitializeSyncFuncs();
2023
}
2124

2225
CAbstractStreaming::~CAbstractStreaming()
2326
{
27+
DeleteCriticalSection(&cdStreamSyncLock);
2428
DeleteCriticalSection(&cs);
2529
Fastman92LimitAdjusterDestroy(this->f92la);
2630
}

src/plugins/gta3/std.stream/streaming.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include <traits/gta3/vc.hpp>
2121
#include <traits/gta3/iii.hpp>
2222

23+
#include "cdstreamsync.hpp"
24+
2325
using namespace modloader;
2426

2527
// Streaming file type
@@ -124,6 +126,8 @@ class CAbstractStreaming
124126
public: // Friends
125127
template<class T> friend class Refresher;
126128
friend int __stdcall CdStreamThread();
129+
friend void __stdcall CdStreamShutdownSync_Stub( CdStream* stream, size_t idx );
130+
friend uint32_t CdStreamSync( int32_t streamID );
127131

128132
private:
129133
LibF92LA f92la; //
@@ -134,6 +138,9 @@ class CAbstractStreaming
134138
std::string fbuffer; // File buffer to avoid a dynamic allocation everytime we open a model
135139
std::list<const modloader::file*> imgFiles; // List of img files imported with Mod Loader
136140

141+
CRITICAL_SECTION cdStreamSyncLock; // Used to bugfix a deadlock in CdStream
142+
CdStreamSyncFix::SyncFuncs cdStreamSyncFuncs; // Initialize/Finalize/Sleep/Wake functions for CdStream synchronization
143+
137144
public:
138145
// Basic types
139146
using id_t = uint32_t; // should have sizeof(int) to allow -1 comparision

src/shared/cdstreamsync.hpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#pragma once
2+
#include <windows.h>
3+
#include "CdStreamInfo.h"
4+
5+
struct CdStream;
6+
7+
namespace CdStreamSyncFix
8+
{
9+
union SyncObj
10+
{
11+
HANDLE semaphore;
12+
CONDITION_VARIABLE cv;
13+
};
14+
15+
struct SyncFuncs
16+
{
17+
SyncObj (__stdcall* Initialize)();
18+
void (__stdcall* Shutdown)( CdStream* stream );
19+
void (__stdcall* SleepCS)( CdStream* stream, PCRITICAL_SECTION critSec );
20+
void (__stdcall* Wake)( CdStream* stream );
21+
};
22+
23+
namespace Sema
24+
{
25+
SyncObj __stdcall Initialize();
26+
void __stdcall Shutdown( CdStream* stream );
27+
void __stdcall SleepCS( CdStream* stream, PCRITICAL_SECTION critSec );
28+
void __stdcall Wake( CdStream* stream );
29+
}
30+
31+
namespace CV
32+
{
33+
SyncObj __stdcall Initialize();
34+
void __stdcall Shutdown( CdStream* stream );
35+
void __stdcall SleepCS( CdStream* stream, PCRITICAL_SECTION critSec );
36+
void __stdcall Wake( CdStream* stream );
37+
}
38+
39+
bool TryInitCV();
40+
41+
inline SyncFuncs InitializeSyncFuncs()
42+
{
43+
SyncFuncs funcs;
44+
if ( TryInitCV() )
45+
{
46+
using namespace CV;
47+
funcs.Initialize = Initialize;
48+
funcs.Shutdown = Shutdown;
49+
funcs.SleepCS = SleepCS;
50+
funcs.Wake = Wake;
51+
}
52+
else
53+
{
54+
using namespace Sema;
55+
funcs.Initialize = Initialize;
56+
funcs.Shutdown = Shutdown;
57+
funcs.SleepCS = SleepCS;
58+
funcs.Wake = Wake;
59+
}
60+
return funcs;
61+
}
62+
}
63+
64+
static_assert(sizeof(CdStreamSyncFix::SyncObj) == sizeof(HANDLE), "Incorrect struct size: CdStreamSync::SyncObj");

0 commit comments

Comments
 (0)