Hello there.
I've stumbled upon a problem. This is actually my first time writing an AMXX module, but I don't think the mistake comes out from my lack of experience, but actually this might be a game bug.
My code:
EntityInit_amxx.cpp
PHP Code:
#include "sdk/amxxmodule.h"
#include "CConquestBase.h"
#include "chooker.h"
CHooker HookerClass;
CHooker *Hooker = &HookerClass;
typedef void(*ENTITYINIT)(struct entvars_s *);
typedef void(*Func_DispatchKeyValue)(struct edict_s *, KeyValueData *);
typedef ENTITYINIT(*Func_GetEntityInit)(char *);
Func_GetEntityInit pfnGetEntityInit = NULL;
CFunc *funcDispatchKeyValue = NULL;
CFunc *funcGetEntityInit = NULL;
ENTITYINIT GetEntityInit_Hook(char *pClassName);
void DispatchKeyValue_Hook(edict_t *pentKeyvalue, KeyValueData *pkvd);
void OnMetaAttach(void) {
#ifdef _WIN32
pfnGetEntityInit = Hooker->MemorySearch<Func_GetEntityInit>("0x55,0x8B,0xEC,0x8B,0x45,*,0x50,0xE8,0x34,0xFD,0xFF,0xFF,0x83,0xC4,0x04,0x5D,0xC3", (void *)gpGlobals, FALSE);
#else
#error Platforms other than Win32 are not supported (yet).
#endif
funcGetEntityInit = Hooker->CreateHook((void *)pfnGetEntityInit, (void *)GetEntityInit_Hook, TRUE);
funcDispatchKeyValue = Hooker->CreateHook((void *)gpGamedllFuncs->dllapi_table->pfnKeyValue, (void *)DispatchKeyValue_Hook, TRUE);
if (funcGetEntityInit == NULL) {
SERVER_PRINT("Failed to create GetEntityInit() hook...\n");
}
if (funcDispatchKeyValue = NULL) {
SERVER_PRINT("Failed to create DispatchKeyValue() hook...\n");
}
}
void OnMetaDetach(void) {
funcGetEntityInit->Restore();
}
LINK_ENTITY_TO_CLASS(conquest_base, CConquestBase);
ENTITYINIT GetEntityInit_Hook(char *pClassName) {
GET_ORIG_FUNC(func);
if (func->Restore()) {
ENTITYINIT init = NULL;
if (!strcmp(pClassName, "conquest_base")) {
init = (ENTITYINIT)&conquest_base;
}
else {
init = pfnGetEntityInit(pClassName);
}
func->Patch();
return init;
}
// Damn, here comes the crash (I think) :( But should never happen I guess
return NULL;
}
void DispatchKeyValue_Hook(edict_t *pentKeyvalue, KeyValueData *pkvd) {
GET_ORIG_FUNC(func);
if (func->Restore()) {
gpGamedllFuncs->dllapi_table->pfnKeyValue(pentKeyvalue, pkvd);
if (pentKeyvalue != NULL && !pentKeyvalue->free) {
char szOutput[256];
if (!stricmp(STRING(pentKeyvalue->v.classname), "conquest_base")) {
CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentKeyvalue);
if (pEntity != NULL && pkvd->fHandled == FALSE) {
pEntity->KeyValue(pkvd);
}
sprintf_s(szOutput, sizeof(szOutput), "class: %s key: %s value: %s handled: %i\n", pkvd->szClassName, pkvd->szKeyName, pkvd->szValue, pkvd->fHandled);
SERVER_PRINT(szOutput);
}
}
func->Patch();
}
}
CConquestBase.h
PHP Code:
#ifndef CCONQUESTBASE_H
#define CCONQUESTBASE_H
#include <cbase.h>
#include <extdll.h>
#include <meta_api.h>
class CConquestBase : public CBaseEntity {
private:
float m_range;
int m_team;
string_t m_somevalue;
public:
void Spawn(void) {
SERVER_PRINT("CConquestBase spawned with classname: ");
SERVER_PRINT(STRING(pev->classname));
SERVER_PRINT("!\n");
}
void KeyValue(KeyValueData *pkvd) {
SERVER_PRINT("KeyValue called for CConquestBase!\n");
if (!stricmp(pkvd->szKeyName, "cq_team")) {
m_team = atoi(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (!stricmp(pkvd->szKeyName, "cq_range")) {
m_range = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (!stricmp(pkvd->szKeyName, "cq_prefixedvalue")) {
m_somevalue = ALLOC_STRING(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else {
CBaseEntity::KeyValue(pkvd);
}
}
};
/* Linker can't find these functions */
const Vector g_vecZero;
BOOL CBaseEntity::FVisible(CBaseEntity* pEntity) {
return FALSE;
}
BOOL CBaseEntity::FVisible(const Vector &vecOrigin) {
return FALSE;
}
CBaseEntity *CBaseEntity::GetNextTarget(void) {
return (CBaseEntity*)VARS(CREATE_ENTITY());
}
BOOL CBaseEntity::IsInWorld(void) {
return TRUE;
}
void CBaseEntity::SetObjectCollisionBox(void) {
}
void CBaseEntity::Killed(entvars_t *pevAttacker, int iGib) {
}
int CBaseEntity::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) {
return 0;
}
int CBaseEntity::TakeHealth(float flHealth, int bitsDamageType) {
return 0;
}
int CBaseEntity::Save(CSave &save) {
return 0;
}
int CBaseEntity::Restore(CRestore &restore) {
return 0;
}
int CBaseEntity::DamageDecal(int bitsDamageType) {
return -1;
}
void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) {
}
void CBaseEntity::TraceBleed(float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) {
}
#endif
My real intention is to actually create more entity types. For instance, if you call pfnCreateNamedEntity with a non-registered classname (like "my_custom_ent"), the engine won't even create the entity - it has to be a "registered" classname, that is, you have to write an exported C function with the same name as entity type (that is what LINK_ENTITY_TO_CLASS from HLSDK does).
What I do with my code is hook GetEntityInit(char *pClassName) from engine, and check if classname is equal to my own custom entity, and return my own function if it is. I know for sure this works because CConquestBase::Spawn() gets called.
The problem is that CConquestBase::KeyValue(KeyValueData *) doesn't get called at all. I just don't get why! That is also the reason I hooked DispatchKeyValue (pfnKeyValue) - to check if there's anything wrong with parameters passed to that function. Apparently there's not.
My hooked function's code actually does exactly the same as
the real function does - check if pEntity is null, and if it is not, calls KeyValue. If I let the game do it - CConquestBase::KeyValue doesn't get called, but when I do it on my own (as you can see in my code), it works flawlessly.
Any explanation for this?