DEVELOPER MODE v0.2: Dev T-Shirts, SCTV, Creator | incl. SOURCE for HOOKING NATIVES
Features
- R* Dev T-Shirts
- SCTV
- Special Creator Mode (bugged?)
Requirements
- Visual C++ Redistributable 2015
- .NET Framework 4.5.2
- Any injector of your choice
Instructions
- Load GTA
- Inject using your favourite injector
Dev T-Shirt Notes
- Simply join GTA Online as usual (i.e. do NOT enter SCTV) and visit Ponsonbys. R* Dev T-Shirts will be available from Ponsonbys in the "Special Tops" section
SCTV Notes
- Load Story mode, open the game menu, select "ONLINE" and "Play GTA Online". There will be a new blank option. Selecting this blank option will load SCTV.
- SCTV seems to be a special observer mode used by R*. You can join any session and spectate others. They won't notice you're there and you won't be visible in the online player list.
- To open the game menu, to e.g. find a new session, press "P"
- To exit SCTV don't press ESC (you will re-join another SCTV session), but press "P" and chose "Leave GTO Online" / exit to Story mode.
Creator Notes
- There is another blank option when starting the Creator. It didn't work for me though, but I didn't spent much time trying..
Other Notes
- This .dll doesn't rely on the same methods other mods use, but works totally different. Therefor it can be injected simultanously to another mods. That said,
you can even use another mod menu while in SCTV and players won't be able to tell who is modding - they won't even know they're currently beeing spectated!
- Injection using "manual map" works!
Changelog
v0.2:
- Avoid crashes that occured when injecting the dll prior to the game menu. Injection is now possible any time.
Credits
sn00x
Alexander Blade, NTAuthority/citizenMP, s0beit, gir489 - For m0d-s0beit-v Redux
Virus Scans
VirusTotal
Jotti
Source Code
For any developers wondering how I did this, here's the code I wrote to hook into natives.
#include "stdafx.h"
#define MAX_HOOKS 1000
typedef struct _HOOK_INFO
{
ULONG_PTR Function;
ULONG_PTR Hook;
ULONG_PTR OrigBytes;
} HOOK_INFO, *PHOOK_INFO;
HOOK_INFO HookInfo[MAX_HOOKS];
UINT NumberOfHooks = 0;
bool hookFailed = false;
BYTE *pOrigBytesBuffer = NULL;
HOOK_INFO *GetHookInfoFromFunction(ULONG_PTR OriginalFunction)
{
if (NumberOfHooks == 0)
return NULL;
for (UINT x = 0; x < NumberOfHooks; x++)
{
if (HookInfo[x].Function == OriginalFunction)
return &HookInfo[x];
}
return NULL;
}
void WriteJump(void *pAddress, ULONG_PTR JumpTo)
{
// be extra safe and overwrite memory with a single memcpy
BYTE *pJumpInstructionBuffer = (BYTE *)malloc(14);
BYTE *pCur = pJumpInstructionBuffer;
*pCur = 0xff; // jmp [rip+addr]
*(++pCur) = 0x25;
*((DWORD *) ++pCur) = 0; // addr = 0
pCur += sizeof(DWORD);
*((ULONG_PTR *)pCur) = JumpTo;
DWORD dwOldProtect = 0;
VirtualProtect(pAddress, 14, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy(pAddress, pJumpInstructionBuffer, 14);
DWORD dwBuf = 0;
VirtualProtect(pAddress, 14, dwOldProtect, &dwBuf);
free(pJumpInstructionBuffer);
}
void *BackupOrigBytes(ULONG_PTR originalFunction)
{
if (pOrigBytesBuffer == NULL) {
pOrigBytesBuffer = (BYTE *)malloc(MAX_HOOKS * 14);
}
VOID *pOrigBytes = (VOID *)&pOrigBytesBuffer[NumberOfHooks * 14];
memcpy(&pOrigBytesBuffer[NumberOfHooks * 14], (void *)originalFunction, 14);
return pOrigBytes;
}
bool hookNative(UINT64 hash, ULONG_PTR hookFunction = NULL)
{
auto originalFunction = GetNativeHandler(hash);
if (originalFunction == 0) {
hookFailed = true;
return false;
}
HOOK_INFO *hinfo = GetHookInfoFromFunction((ULONG_PTR) originalFunction);
if (hinfo) {
WriteJump(originalFunction, hinfo->Hook);
}
else
{
if (NumberOfHooks == (MAX_HOOKS - 1))
return false;
VOID *pOrigBytes = BackupOrigBytes((ULONG_PTR)originalFunction);
HookInfo[NumberOfHooks].Function = (ULONG_PTR)originalFunction;
HookInfo[NumberOfHooks].OrigBytes = (ULONG_PTR)pOrigBytes;
HookInfo[NumberOfHooks].Hook = hookFunction;
NumberOfHooks++;
WriteJump(originalFunction, hookFunction);
}
return true;
}
void unhookNative(UINT64 hash) {
auto originalFunction = GetNativeHandler(hash);
HOOK_INFO *hinfo = GetHookInfoFromFunction((ULONG_PTR)originalFunction);
DWORD dwOldProtect = 0;
VirtualProtect(originalFunction, 14, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy((void *)hinfo->Function, (void *)hinfo->OrigBytes, 14);
DWORD dwBuf = 0;
VirtualProtect(originalFunction, 14, dwOldProtect, &dwBuf);
}
void* __cdecl MY_IS_DLC_PRESENT(NativeContext *cxt)
{
Hash DlcHash = cxt->GetArgument<Hash>(0);
if (DlcHash == 2532323046) { // DEV
// game game requested dev dlc -> return true;
cxt->SetResult(0, true);
return cxt;
}
// restore original function
unhookNative(0x2D6859674806FDCE);
// get result of original function
BOOL result = DLC2::IS_DLC_PRESENT(cxt->GetArgument<Hash>(0));
cxt->SetResult(0, result);
// hook us up again
hookNative(0x2D6859674806FDCE);
return cxt;
}
bool AttemptHookNatives() {
static DWORD64 dwThreadCollectionPtr = 0;
if (!dwThreadCollectionPtr) {
// scan for GTA Thread Pool
dwThreadCollectionPtr = Pattern::Scan(g_MainModuleInfo, "48 8B 05 ? ? ? ? 8B CA 4C 8B 0C C8 45 39 51 08");
}
if ( !dwThreadCollectionPtr
|| !Pattern::Scan(g_MainModuleInfo, "76 61 49 8B 7A 40 48 8D 0D") // scan for Native Registration Table
) {
// too early. GetNativeHandler would log a fatal error and exit the process
return false;
}
hookFailed = false;
hookNative(0x2D6859674806FDCE, (ULONG_PTR)&MY_IS_DLC_PRESENT);
// add more hooks here
return !hookFailed;
}
DWORD WINAPI lpHookNatives(LPVOID lpParam) {
while (!AttemptHookNatives()) {
Sleep(100);
}
return 0;
}
void SpawnHookNatives() {
CreateThread(0, 0, lpHookNatives, 0, 0, 0);
}
Adding this code as a new cpp to m0d-s0beit-v Redux and calling SpawnHookNatives() in dllmain actually makes the client think you are a developer (Dev T-Shirts, SCTV and something in the Creator). The game just checks for a specific DLC to set the dev flag.
I got many ideas from Daniel Pistelli's NtHookEngine, but decided to leave out the disassambly and bridge/trampoline part and instead just create a simple unhook/hook. So my code isn't thread-safe and theoretically another thread could call the native while it is unhooked to call the original function from within the hook, resulting in an unhooked call to the original function. However, my tests showed that only one thread is calling the natives anyway.
Should work for most natives this way! There will be access violations for natives that consist of less than 14 bytes though (You would be overwriting unrelated memory). Arguments may need to be fetched in reversed order for functions with multiple parameters. Didn't try yet though.
Feel free to make this bulletproof, use for your menu bases and build new interesting features using hooked natives!