First of all, i'm not a English native speaker so..just don't focous on my bad expression.
MS's Detours library is used.I'm not familiar with linux so no linux version here, but it's easy to make your own with IDA and some hook technique.
Why call it "fake" custom entity? because this kind of custom entity have no custom private data, you can just created an entity already defined in your gamedll, with a custom classname, that can be recognized by engine when loading level and parsing .bsp entity lump.
(Creating an entity with custom private data needs gamedll code like HLSDK\dlls\)
which means you need to read KeyValues and save them in
entvars_t *pev rather than in private data m_xxxx.
you can redirect your custom entity to "info_target", and hook info_target 's Spawn & KeyValues & ... using hamsandwich or fakemeta in AMXX, it's a simple trick but very useful.
Here are the relative code:
engine.h
PHP Code:
#include <com_model.h>
typedef void (*ENTITYINIT)(entvars_t *pev);
typedef struct
{
char *(*ED_ParseEdict)(char *, edict_t *);
ENTITYINIT (*GetEntityInit)(char *);
}hook_funcs_t;
extern hook_funcs_t gHookFuncs;
ENTITYINIT GetEntityInit(char *pClassName);
void Sys_Error(const char *error);
engine.cpp
PHP Code:
DWORD g_dwEngineBase = 0;
DWORD g_dwEngineSize = 0;
hook_funcs_t gHookFuncs;
void Sys_Error(const char *error)
{
MessageBox(NULL, error, "Error", MB_ICONERROR);
exit(0);
}
#define SIG_NOT_FOUND(name) Sys_Error(UTIL_VarArgs("Unable to find: %s", name));
void Engine_InstallHook(void)
{
DWORD addr;
HMODULE hEngine;
int swds = 1;
hEngine = GetModuleHandle("swds.dll");
if(!hEngine || hEngine == INVALID_HANDLE_VALUE)
{
swds = 0;
hEngine = GetModuleHandle("hw.dll");
}
if(!hEngine || hEngine == INVALID_HANDLE_VALUE)
{
g_dwEngineBase = 0x1D01000;
g_dwEngineSize = 0x1000000;
}
else
{
g_dwEngineBase = MH_GetModuleBase(hEngine);
g_dwEngineSize = MH_GetModuleSize(hEngine);
}
#define ED_PARSEEDICT_SIG "\xA1\x2A\x2A\x2A\x2A\x81\xEC\x24\x03\x00\x00\x53\x55\x8B\xAC\x24\x34\x03\x00\x00\x33\xDB\x56\x3B\xE8\x57"
#define ED_PARSEEDICT_SIG_NEW "\x55\x8B\xEC\x81\xEC\x10\x01\x00\x00\xA1\x2A\x2A\x2A\x2A\x56\x8B\x75\x0C\x57\x3B\xF0\x74\x16"
gHookFuncs.ED_ParseEdict = (char *(*)(char *, edict_t *))MH_SIGFind(g_dwEngineBase, g_dwEngineSize, ED_PARSEEDICT_SIG, sizeof(ED_PARSEEDICT_SIG)-1);
if(!gHookFuncs.ED_ParseEdict)
gHookFuncs.ED_ParseEdict = (char *(*)(char *, edict_t *))MH_SIGFind(g_dwEngineBase, g_dwEngineSize, ED_PARSEEDICT_SIG_NEW, sizeof(ED_PARSEEDICT_SIG_NEW)-1);
if(!gHookFuncs.ED_ParseEdict)
SIG_NOT_FOUND("ED_ParseEdict");
//E8 2A 2A 2A 2A call GetEntityInit
//83 C4 04 add esp, 4
//85 C0 test eax, eax
#define GETENTITYINIT_SIG "\xE8\x2A\x2A\x2A\x2A\x83\xC4\x04\x85\xC0"
//E8 D9 E7 03 00 call GetEntityInit
//83 C4 10 add esp, 10h
//3B C3 cmp eax, ebx
#define GETENTITYINIT_SIG2 "\xE8\x2A\x2A\x2A\x2A\x83\xC4\x10\x3B\xC3"
addr = MH_SIGFind((DWORD)gHookFuncs.ED_ParseEdict, 0x100, GETENTITYINIT_SIG, sizeof(GETENTITYINIT_SIG)-1);
if(!addr)
addr = MH_SIGFind((DWORD)gHookFuncs.ED_ParseEdict, 0x100, GETENTITYINIT_SIG2, sizeof(GETENTITYINIT_SIG2)-1);
if(!addr)
SIG_NOT_FOUND("GetEntityInit");
gHookFuncs.GetEntityInit = (ENTITYINIT (*)(char *))GetCallAddress(addr);
MH_InlineHook((void *)gHookFuncs.GetEntityInit, GetEntityInit, (void *&)gHookFuncs.GetEntityInit);
SERVER_PRINT("Engine hook installed.\n");
}
hook.h
PHP Code:
#pragma once
#pragma pack(push, 1)
struct tagIATDATA
{
void *pAPIInfoAddr;
};
struct tagCLASS
{
DWORD *pVMT;
};
struct tagVTABLEDATA
{
tagCLASS *pInstance;
void *pVFTInfoAddr;
};
#pragma pack(pop)
typedef struct hook_s
{
void *pOldFuncAddr;
void *pNewFuncAddr;
void *pClass;
int iTableIndex;
int iFuncIndex;
HMODULE hModule;
LPCWSTR pszModuleName;
LPCWSTR pszFuncName;
struct hook_s *pNext;
void *pInfo;
}hook_t;
hook_t *MH_NewHook(void);
void MH_FreeHook(hook_t *pHook);
void MH_FreeAllHook(void);
BOOL MH_UnHook(hook_t *pHook);
hook_t *MH_InlineHook(void *pOldFuncAddr, void *pNewFuncAddr, void *&pCallBackFuncAddr);
hook_t *MH_VFTHook(void *pClass, int iTableIndex, int iFuncIndex, void *pNewFuncAddr, void *&pCallBackFuncAddr);
DWORD MH_WriteMemory(void *pAddress, BYTE *pData, DWORD dwDataSize);
DWORD MH_ReadMemory(void *pAddress, BYTE *pData, DWORD dwDataSize);
DWORD MH_GetModuleBase(HMODULE hModule);
DWORD MH_GetModuleSize(HMODULE hModule);
DWORD MH_SIGFind(DWORD dwStartAddr, DWORD dwFindLen, char *sig, int len);
#define GetCallAddress(addr) (addr + (*(DWORD *)(addr+1)) + 5)
hook.cpp
PHP Code:
#include <windows.h>
#include "hook.h"
#include "detours.h"
static hook_t *g_pHookBase;
hook_t *MH_NewHook(void)
{
hook_t *h = new hook_t;
memset(h, 0, sizeof(hook_t));
h->pNext = g_pHookBase;
g_pHookBase = h;
return h;
}
void MH_FreeHook(hook_t *pHook)
{
if (pHook->pClass)
{
tagVTABLEDATA *info = (tagVTABLEDATA *)pHook->pInfo;
MH_WriteMemory(info->pVFTInfoAddr, (BYTE *)pHook->pOldFuncAddr, sizeof(DWORD));
}
else if (pHook->hModule)
{
tagIATDATA *info = (tagIATDATA *)pHook->pInfo;
MH_WriteMemory(info->pAPIInfoAddr, (BYTE *)pHook->pOldFuncAddr, sizeof(DWORD));
}
else
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(void *&)pHook->pOldFuncAddr, pHook->pNewFuncAddr);
DetourTransactionCommit();
}
if (pHook->pInfo)
delete pHook->pInfo;
delete pHook;
}
void MH_FreeAllHook(void)
{
hook_t *next = NULL;
for (hook_t *h = g_pHookBase; h; h = next)
{
next = h->pNext;
MH_FreeHook(h);
}
g_pHookBase = NULL;
}
BOOL MH_UnHook(hook_t *pHook)
{
if (!g_pHookBase)
return FALSE;
if (!g_pHookBase->pNext)
{
MH_FreeHook(pHook);
g_pHookBase = NULL;
return TRUE;
}
hook_t *last = NULL;
for (hook_t *h = g_pHookBase->pNext; h; h = h->pNext)
{
if (h->pNext != pHook)
{
last = h;
continue;
}
last->pNext = h->pNext;
MH_FreeHook(h);
return TRUE;
}
return FALSE;
}
hook_t *MH_InlineHook(void *pOldFuncAddr, void *pNewFuncAddr, void *&pCallBackFuncAddr)
{
hook_t *h = MH_NewHook();
h->pOldFuncAddr = pOldFuncAddr;
h->pNewFuncAddr = pNewFuncAddr;
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(void *&)h->pOldFuncAddr, pNewFuncAddr);
DetourTransactionCommit();
pCallBackFuncAddr = h->pOldFuncAddr;
return h;
}
hook_t *MH_VFTHook(void *pClass, int iTableIndex, int iFuncIndex, void *pNewFuncAddr, void *&pCallBackFuncAddr)
{
tagVTABLEDATA *info = new tagVTABLEDATA;
info->pInstance = (tagCLASS *)pClass;
DWORD *pVMT = ((tagCLASS *)pClass + iTableIndex)->pVMT;
info->pVFTInfoAddr = pVMT + iFuncIndex;
hook_t *h = MH_NewHook();
h->pOldFuncAddr = (void *)pVMT[iFuncIndex];
h->pNewFuncAddr = pNewFuncAddr;
h->pInfo = info;
h->pClass = pClass;
h->iTableIndex = iTableIndex;
h->iFuncIndex = iFuncIndex;
pCallBackFuncAddr = h->pOldFuncAddr;
MH_WriteMemory(info->pVFTInfoAddr, (BYTE *)&pNewFuncAddr, sizeof(DWORD));
return h;
}
DWORD MH_WriteMemory(void *pAddress, BYTE *pData, DWORD dwDataSize)
{
static DWORD dwProtect;
if (VirtualProtect(pAddress, dwDataSize, PAGE_EXECUTE_READWRITE, &dwProtect))
{
memcpy(pAddress, pData, dwDataSize);
VirtualProtect(pAddress, dwDataSize, dwProtect, &dwProtect);
}
return dwDataSize;
}
DWORD MH_ReadMemory(void *pAddress, BYTE *pData, DWORD dwDataSize)
{
static DWORD dwProtect;
if (VirtualProtect(pAddress, dwDataSize, PAGE_EXECUTE_READWRITE, &dwProtect))
{
memcpy(pData, pAddress, dwDataSize);
VirtualProtect(pAddress, dwDataSize, dwProtect, &dwProtect);
}
return dwDataSize;
}
DWORD MH_GetModuleBase(HMODULE hModule)
{
MEMORY_BASIC_INFORMATION mem;
if (!VirtualQuery(hModule, &mem, sizeof(MEMORY_BASIC_INFORMATION)))
return 0;
return (DWORD)mem.AllocationBase;
}
DWORD MH_GetModuleSize(HMODULE hModule)
{
return ((IMAGE_NT_HEADERS *)((DWORD)hModule + ((IMAGE_DOS_HEADER *)hModule)->e_lfanew))->OptionalHeader.SizeOfImage;
}
DWORD MH_SIGFind(DWORD dwStartAddr, DWORD dwFindLen, char *sig, int len)
{
DWORD dwEndAddr = dwStartAddr + dwFindLen - len;
BOOL found;
char code;
int i;
while (dwStartAddr < dwEndAddr)
{
found = TRUE;
for (i = 0; i < len; i++)
{
code = *(char *)(dwStartAddr + i);
if (sig[i] != (char)0x2A && sig[i] != code)
{
found = FALSE;
break;
}
}
if (found)
return dwStartAddr;
dwStartAddr++;
}
return 0;
}
Redirect your custom entity to an gamedll entity.
PHP Code:
ENTITYINIT GetEntityInit(char *pClassName)
{
if(!strcmp(pClassName, "sky_camera") || !strcmp(pClassName, "sky_box") || !strcmp(pClassName, "sky_center") || !strcmp(pClassName, "shadow_manager"))
{
return gHookFuncs.GetEntityInit("info_target");
}
return gHookFuncs.GetEntityInit(pClassName);
}
Dispatch their key values here or in AMXX plugin.
PHP Code:
void DispatchKeyValue_Post(edict_t *pentKeyvalue, KeyValueData *pkvd)
{
if(!pentKeyvalue)
RETURN_META(MRES_IGNORED);
if(!pkvd->szClassName)
RETURN_META(MRES_IGNORED);
if( !strcmp(pkvd->szClassName, "shadow_manager") )
{
if(!strcmp(pkvd->szKeyName, "affectmodel"))
pentKeyvalue->v.iuser1 = MAKE_STRING(pkvd->szValue);
else if(!strcmp(pkvd->szKeyName, "radius"))
pentKeyvalue->v.fuser1 = atof(pkvd->szValue);
else if(!strcmp(pkvd->szKeyName, "scale"))
pentKeyvalue->v.fuser2 = atof(pkvd->szValue);
else if(!strcmp(pkvd->szKeyName, "fard"))
pentKeyvalue->v.fuser3 = atof(pkvd->szValue);
else if(!strcmp(pkvd->szKeyName, "texsize"))
pentKeyvalue->v.iuser2 = atoi(pkvd->szValue);
}
RETURN_META(MRES_IGNORED);
}
Remember to call Engine_InstallHook in void OnAmxxAttach(void)
PHP Code:
void OnAmxxAttach(void)
{
Engine_InstallHook();
}
The sample code in attach files is a server-side plugin for my Meta Renderer mod, you can find this in moddb.