Skip to content

Commit

Permalink
Merge pull request #71 from CookiePLMonster/cdstream-deadlock
Browse files Browse the repository at this point in the history
Fixed a deadlock in CdStream and LoadLibrary path translation heuristics
  • Loading branch information
thelink2012 authored Jan 20, 2018
2 parents 37d9ee9 + 6e6ecd5 commit 0e4baf5
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 27 deletions.
4 changes: 2 additions & 2 deletions include/modloader/gta3/gta3.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ namespace modloader

template<uintptr_t addr, class Traits = dtraits::WinCreateFileA>
using WinCreateFileA = modloader::basic_file_detour<Traits,
injector::function_hooker_stdcall<addr, HANDLE(LPCTSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE)>,
HANDLE, LPCTSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE>;
injector::function_hooker_stdcall<addr, HANDLE(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE)>,
HANDLE, LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE>;


template<uintptr_t addr, class Traits = dtraits::LoadAtomic2Return>
Expand Down
2 changes: 1 addition & 1 deletion include/modloader/modloader.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extern "C" {
/* Version */
#define MODLOADER_VERSION_MAJOR 0
#define MODLOADER_VERSION_MINOR 3
#define MODLOADER_VERSION_REVISION 5
#define MODLOADER_VERSION_REVISION 7
#ifdef NDEBUG
#define MODLOADER_VERSION_ISDEV 0
#else
Expand Down
17 changes: 9 additions & 8 deletions include/modloader/util/path.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,9 @@ namespace modloader
* WinAPI-like function to check if a directory exists
* @szPath: Directory to check
*/
inline BOOL IsDirectoryA(LPCTSTR szPath)
inline BOOL IsDirectoryA(LPCSTR szPath)
{
DWORD dwAttrib = GetFileAttributes(szPath);
DWORD dwAttrib = GetFileAttributesA(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
Expand All @@ -323,7 +323,7 @@ namespace modloader
* WinAPI-like function to check if a file or directory exists
* @szPath: Directory to check
*/
inline BOOL IsPathA(LPCTSTR szPath)
inline BOOL IsPathA(LPCSTR szPath)
{
DWORD dwAttrib = GetFileAttributesA(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES);
Expand Down Expand Up @@ -352,7 +352,7 @@ namespace modloader
* WinAPI-like function to make sure a directory exists, if not, create it
* @szPath: Directory to check
*/
inline BOOL MakeSureDirectoryExistA(LPCTSTR szPath)
inline BOOL MakeSureDirectoryExistA(LPCSTR szPath)
{
if(!IsDirectoryA(szPath))
{
Expand All @@ -371,7 +371,7 @@ namespace modloader
* WinAPI-like function that copies the full directory @szFrom to @szTo
* If @szTo doesn't exist, it is created
*/
inline BOOL CopyDirectoryA(LPCTSTR szFrom, LPCTSTR szTo)
inline BOOL CopyDirectoryA(LPCSTR szFrom, LPCSTR szTo)
{
if(CreateDirectoryA(szTo, NULL))
{
Expand Down Expand Up @@ -403,7 +403,7 @@ namespace modloader
* DestroyDirectoryA
* WinAPI-like function that deletes the path @szPath fully
*/
inline BOOL DestroyDirectoryA(LPCTSTR szPath)
inline BOOL DestroyDirectoryA(LPCSTR szPath)
{
FilesWalk(szPath, "*.*", false, [&szPath](FileWalkInfo& file)
{
Expand All @@ -429,7 +429,7 @@ namespace modloader
* GetFileSize
* WinAPI-like function that gets the file size of @szPath
*/
inline LONGLONG GetFileSize(LPCTSTR szPath)
inline LONGLONG GetFileSize(LPCSTR szPath)
{
WIN32_FILE_ATTRIBUTE_DATA fad;
return GetFileAttributesExA(szPath, GetFileExInfoStandard, &fad)?
Expand Down Expand Up @@ -471,7 +471,8 @@ namespace modloader

/* Enter on ctor, Leave on dtor */
scoped_lock(CRITICAL_SECTION& cs)
{ c = &cs; EnterCriticalSection(&cs); }
: c(&cs)
{ EnterCriticalSection(&cs); }
~scoped_lock()
{ LeaveCriticalSection(c); }
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ namespace hacks
* Hacked FindFirstFileA
*/
template<>
bool FindCleoScripts::Finder<aFindFirstFileA>(HANDLE& result, LPCTSTR& lpFileName, LPWIN32_FIND_DATAA& lpFindFileData)
bool FindCleoScripts::Finder<aFindFirstFileA>(HANDLE& result, LPCSTR& lpFileName, LPWIN32_FIND_DATAA& lpFindFileData)
{
char version;

Expand Down
2 changes: 2 additions & 0 deletions src/plugins/gta3/std.asi/args_translator/translator_basic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ struct path_translator_base
bool bFindFirstFile; // ^
bool bFindNextFile; // ^
bool bFindClose; // ^
bool bLoadLibrary; // ^
bool bDoBassHack; // ^

// Almost static vars (he), the value is always the same in all objects
Expand Down Expand Up @@ -463,6 +464,7 @@ struct path_translator_basic : public path_translator_base
bFindFirstFile = (Symbol == aFindFirstFileA) || (Symbol == aFindFirstFileW);
bFindNextFile = (Symbol == aFindNextFileA) || (Symbol == aFindNextFileW);
bFindClose = (Symbol == aFindClose);
bLoadLibrary = (Symbol == aLoadLibraryA) || (Symbol == aLoadLibraryW) || (Symbol == aLoadLibraryExA) || (Symbol == aLoadLibraryExW);
bIniOperations = false;
}
}
Expand Down
16 changes: 15 additions & 1 deletion src/plugins/gta3/std.asi/args_translator/translator_stdcall.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,21 @@ struct path_translator_stdcall<Symbol, LibName, Ret(Args...)> : public path_tran
}

if(!bDetoured)
info.TranslateForCall(a...); // Translate the paths
{
if(info.base->bLoadLibrary)
{
auto f = (func_type) info.base->fun;
result = f(a...);
if(result != NULL)
{
// If DLL loaded without translating the path, abort translation and don't try to call it again
bDetoured = true;
}

}
if(!bDetoured)
info.TranslateForCall(a...); // Translate the paths
}
}

if(!bDetoured)
Expand Down
99 changes: 95 additions & 4 deletions src/plugins/gta3/std.stream/backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,53 @@ extern "C"
};


void __stdcall CdStreamShutdownSync_Stub( CdStream* stream, size_t idx )
{
streaming->cdStreamSyncFuncs.Shutdown( &stream[idx] );
}

uint32_t CdStreamSync( int32_t streamID )
{
static bool bInitFields = false;
static CdStream** ppStreams;
static BOOL* streamingInitialized;
static BOOL* overlappedIO;

if ( !bInitFields )
{
if( gvm.IsSA() )
{
auto& cdinfo = *memory_pointer(0x8E3FE0).get<CdStreamInfoSA>();
ppStreams = &cdinfo.pStreams;
streamingInitialized = &cdinfo.streamingInitialized;
overlappedIO = &cdinfo.overlappedIO;
}
else if( gvm.IsVC() || gvm.IsIII() )
{
ppStreams = memory_pointer(xVc(0x6F76FC)).get<CdStream*>();
streamingInitialized = memory_pointer(xVc(0x6F7718)).get<BOOL>();
overlappedIO = memory_pointer(xVc(0x6F7714)).get<BOOL>();
}

bInitFields = true;
}

CdStream* stream = &((*ppStreams)[streamID]);
if ( *streamingInitialized )
{
scoped_lock lock( streaming->cdStreamSyncLock );
streaming->cdStreamSyncFuncs.SleepCS( stream, &streaming->cdStreamSyncLock );
stream->bInUse = 0;
return stream->status;
}

if ( *overlappedIO && stream->hFile != nullptr )
{
DWORD numBytesRead;
return GetOverlappedResult( stream->hFile, &stream->overlapped, &numBytesRead, TRUE ) != 0 ? 0 : 254;
}
return 0;
}

/*
* Streaming thread
Expand All @@ -92,7 +139,7 @@ int __stdcall CdStreamThread()
// Get reference to the addresses we'll use.
if(gvm.IsSA())
{
auto& cdinfo = *memory_pointer(0x8E3FEC).get<CdStreamInfoSA>();
auto& cdinfo = *memory_pointer(0x8E3FE0).get<CdStreamInfoSA>();
pSemaphore = &cdinfo.semaphore;
pQueue = &cdinfo.queue;
ppStreams = &cdinfo.pStreams;
Expand Down Expand Up @@ -192,9 +239,14 @@ int __stdcall CdStreamThread()

// Cleanup
if(bIsAbstract) streaming->CloseModel(sfile);
cd->nSectorsToRead = 0;
if(cd->bLocked) ReleaseSemaphore(cd->semaphore, 1, 0);
cd->bInUse = false;
{
// This critical section fixes a deadlock with CdStreamThread present in original code
scoped_lock xlock(streaming->cdStreamSyncLock);

cd->nSectorsToRead = 0;
streaming->cdStreamSyncFuncs.Wake(cd);
cd->bInUse = false;
}
}
return 0;
}
Expand Down Expand Up @@ -442,6 +494,36 @@ void CAbstractStreaming::Patch()
// Making our our code for the stream thread would make things so much better
MakeJMP(0x406560, raw_ptr(CdStreamThread));

// These are required so we can fix CdStream race condition
MakeJMP( 0x406460, raw_ptr(CdStreamSync) );
if( gvm.IsSA() )
{
const uint8_t mem[] = { 0xFF, 0x15 };
WriteMemoryRaw( 0x406910, mem, sizeof(mem), true );
WriteMemory( 0x406910 + 2, &streaming->cdStreamSyncFuncs.Initialize, true );
MakeNOP( 0x406910 + 6, 4 );
MakeNOP( 0x406910 + 0x16, 2 );
}
else if( gvm.IsVC() || gvm.IsIII() )
{
MakeNOP( xVc(0x4088F7), 8 );
WriteMemory( xVc(0x4088F7) + 10, &streaming->cdStreamSyncFuncs.Initialize, true );
WriteMemory( xVc(0x408919), uint8_t(0xEB), true );
}

if( gvm.IsSA() )
{
const uint8_t mem[] = { 0x56, 0x50 };
WriteMemoryRaw( 0x4063B5, mem, sizeof(mem), true );
MakeCALL( 0x4063B5 + 2, raw_ptr(CdStreamShutdownSync_Stub), true );
}
else if( gvm.IsVC() || gvm.IsIII() )
{
const uint8_t mem[] = { 0x8D, 0x04, 0x29, 0x90 };
WriteMemoryRaw( xVc(0x4086B6), mem, sizeof(mem), true );
WriteMemory( xVc(0x4086B6) + 5 + 2, &streaming->cdStreamSyncFuncs.Shutdown, true );
}

// We need to know the next model to be read before the CdStreamRead call happens
if(gvm.IsSA())
{
Expand Down Expand Up @@ -731,3 +813,12 @@ void CAbstractStreaming::Patch()
}
}

/*
* Export for SilentPatch so it knows that this ML version patches the race condition
*/
extern "C" __declspec(dllexport)
uint32_t CdStreamRaceConditionAware()
{
return 1;
}

4 changes: 4 additions & 0 deletions src/plugins/gta3/std.stream/streaming.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
#include <stdinc.hpp>
#include "streaming.hpp"
#include "cdstreamsync.inl"
using namespace modloader;

CAbstractStreaming* streaming;
Expand All @@ -17,10 +18,13 @@ CAbstractStreaming* streaming;
CAbstractStreaming::CAbstractStreaming()
{
InitializeCriticalSection(&cs);
InitializeCriticalSectionAndSpinCount(&cdStreamSyncLock, 10);
cdStreamSyncFuncs = CdStreamSyncFix::InitializeSyncFuncs();
}

CAbstractStreaming::~CAbstractStreaming()
{
DeleteCriticalSection(&cdStreamSyncLock);
DeleteCriticalSection(&cs);
Fastman92LimitAdjusterDestroy(this->f92la);
}
Expand Down
7 changes: 7 additions & 0 deletions src/plugins/gta3/std.stream/streaming.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include <traits/gta3/vc.hpp>
#include <traits/gta3/iii.hpp>

#include "cdstreamsync.hpp"

using namespace modloader;

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

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

CRITICAL_SECTION cdStreamSyncLock; // Used to bugfix a deadlock in CdStream
CdStreamSyncFix::SyncFuncs cdStreamSyncFuncs; // Initialize/Finalize/Sleep/Wake functions for CdStream synchronization

public:
// Basic types
using id_t = uint32_t; // should have sizeof(int) to allow -1 comparision
Expand Down
64 changes: 64 additions & 0 deletions src/shared/cdstreamsync.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma once
#include <windows.h>
#include "CdStreamInfo.h"

struct CdStream;

namespace CdStreamSyncFix
{
union SyncObj
{
HANDLE semaphore;
CONDITION_VARIABLE cv;
};

struct SyncFuncs
{
SyncObj (__stdcall* Initialize)();
void (__stdcall* Shutdown)( CdStream* stream );
void (__stdcall* SleepCS)( CdStream* stream, PCRITICAL_SECTION critSec );
void (__stdcall* Wake)( CdStream* stream );
};

namespace Sema
{
SyncObj __stdcall Initialize();
void __stdcall Shutdown( CdStream* stream );
void __stdcall SleepCS( CdStream* stream, PCRITICAL_SECTION critSec );
void __stdcall Wake( CdStream* stream );
}

namespace CV
{
SyncObj __stdcall Initialize();
void __stdcall Shutdown( CdStream* stream );
void __stdcall SleepCS( CdStream* stream, PCRITICAL_SECTION critSec );
void __stdcall Wake( CdStream* stream );
}

bool TryInitCV();

inline SyncFuncs InitializeSyncFuncs()
{
SyncFuncs funcs;
if ( TryInitCV() )
{
using namespace CV;
funcs.Initialize = Initialize;
funcs.Shutdown = Shutdown;
funcs.SleepCS = SleepCS;
funcs.Wake = Wake;
}
else
{
using namespace Sema;
funcs.Initialize = Initialize;
funcs.Shutdown = Shutdown;
funcs.SleepCS = SleepCS;
funcs.Wake = Wake;
}
return funcs;
}
}

static_assert(sizeof(CdStreamSyncFix::SyncObj) == sizeof(HANDLE), "Incorrect struct size: CdStreamSync::SyncObj");
Loading

0 comments on commit 0e4baf5

Please sign in to comment.