From fd6273db748e03af491d0530750173a24c1c6509 Mon Sep 17 00:00:00 2001 From: aixxe Date: Sun, 3 Jan 2016 22:19:38 +0000 Subject: [PATCH] Add IGameEventManager2::FireEventClientSide hook to fix kill icons. Moved things into separate files to make Chameleon.cpp a bit cleaner. --- Chameleon/Chameleon.cpp | 159 ++++++---------------------- Chameleon/Chameleon.vcxproj | 6 ++ Chameleon/Chameleon.vcxproj.filters | 26 ++++- Chameleon/FireEventClientSide.h | 15 +++ Chameleon/FrameStageNotify.h | 62 +++++++++++ Chameleon/Functions.h | 82 ++++++++++++++ Chameleon/IGameEvents.h | 27 +++++ Chameleon/IVEngineClient.h | 4 + Chameleon/SDK.h | 3 + Chameleon/Skins.h | 7 ++ README.md | 6 ++ 11 files changed, 269 insertions(+), 128 deletions(-) create mode 100644 Chameleon/FireEventClientSide.h create mode 100644 Chameleon/FrameStageNotify.h create mode 100644 Chameleon/Functions.h create mode 100644 Chameleon/IGameEvents.h diff --git a/Chameleon/Chameleon.cpp b/Chameleon/Chameleon.cpp index 136d868..5d71074 100644 --- a/Chameleon/Chameleon.cpp +++ b/Chameleon/Chameleon.cpp @@ -17,120 +17,12 @@ // Include the NetVar proxy functions. #include "Proxies.h" -// Define the calling convention for the FrameStageNotify function. -typedef void(__thiscall *FrameStageNotify)(void*, ClientFrameStage_t); -FrameStageNotify fnOriginalFunction = NULL; - -// Function to apply skin data to weapons. -inline bool ApplyCustomSkin(CBaseAttributableItem* pWeapon, int nWeaponIndex) { - // Check if this weapon has a valid override defined. - if (g_SkinChangerCfg.find(nWeaponIndex) == g_SkinChangerCfg.end()) - return false; - - // Apply our changes to the fallback variables. - *pWeapon->GetFallbackPaintKit() = g_SkinChangerCfg[nWeaponIndex].nFallbackPaintKit; - *pWeapon->GetEntityQuality() = g_SkinChangerCfg[nWeaponIndex].iEntityQuality; - *pWeapon->GetFallbackSeed() = g_SkinChangerCfg[nWeaponIndex].nFallbackSeed; - *pWeapon->GetFallbackStatTrak() = g_SkinChangerCfg[nWeaponIndex].nFallbackStatTrak; - *pWeapon->GetFallbackWear() = g_SkinChangerCfg[nWeaponIndex].flFallbackWear; - - if (g_SkinChangerCfg[nWeaponIndex].iItemDefinitionIndex) - *pWeapon->GetItemDefinitionIndex() = g_SkinChangerCfg[nWeaponIndex].iItemDefinitionIndex; - - // If a name is defined, write it now. - if (g_SkinChangerCfg[nWeaponIndex].szCustomName) { - sprintf_s(pWeapon->GetCustomName(), 32, "%s", g_SkinChangerCfg[nWeaponIndex].szCustomName); - } - - // Edit "m_iItemIDHigh" so fallback values will be used. - *pWeapon->GetItemIDHigh() = -1; - - return true; -} - -// Function to apply custom view models to weapons. -inline bool ApplyCustomModel(CBasePlayer* pLocal, CBaseAttributableItem* pWeapon, int nWeaponIndex) { - // Get the view model of this weapon. - CBaseViewModel* pViewModel = pLocal->GetViewModel(); - - if (!pViewModel) - return false; - - // Get the weapon belonging to this view model. - DWORD hViewModelWeapon = pViewModel->GetWeapon(); - CBaseAttributableItem* pViewModelWeapon = (CBaseAttributableItem*)g_EntityList->GetClientEntityFromHandle(hViewModelWeapon); - - if (pViewModelWeapon != pWeapon) - return false; - - // Check if an override exists for this view model. - int nViewModelIndex = pViewModel->GetModelIndex(); - - if (g_ViewModelCfg.find(nViewModelIndex) == g_ViewModelCfg.end()) - return false; - - // Set the replacement model. - pViewModel->SetWeaponModel(g_ViewModelCfg[nViewModelIndex], pWeapon); - - return true; -} - -void __fastcall FrameStageNotifyThink(void* ecx, void* edx, ClientFrameStage_t Stage) { - while (Stage == FRAME_NET_UPDATE_POSTDATAUPDATE_START) { - // Populate g_ViewModelCfg while in-game so IVModelInfoClient::GetModelIndex returns correctly. - if (g_ViewModelCfg.size() == 0) - SetModelConfig(); - - // Get our player entity. - int nLocalPlayerID = g_EngineClient->GetLocalPlayer(); - CBasePlayer* pLocal = (CBasePlayer*)g_EntityList->GetClientEntity(nLocalPlayerID); - - // Don't change anything if we're not alive. - if (!pLocal || pLocal->GetLifeState() != LIFE_ALIVE) - break; +// Include the "get replacement value" functions. +#include "Functions.h" - // Get handles to weapons we're carrying. - UINT* hWeapons = pLocal->GetWeapons(); - - if (!hWeapons) - break; - - // Retrieve our player information which will be used for ownership checking. - player_info_t LocalPlayerInfo; - g_EngineClient->GetPlayerInfo(nLocalPlayerID, &LocalPlayerInfo); - - // Loop through weapons and run our skin function on them. - for (int nIndex = 0; hWeapons[nIndex]; nIndex++) { - // Get the weapon entity from the provided handle. - CBaseAttributableItem* pWeapon = (CBaseAttributableItem*)g_EntityList->GetClientEntityFromHandle(hWeapons[nIndex]); - - if (!pWeapon) - continue; - - // Get the weapons item definition index. - int nWeaponIndex = *pWeapon->GetItemDefinitionIndex(); - - ApplyCustomModel(pLocal, pWeapon, nWeaponIndex); - - // Compare original owner XUIDs. - if (LocalPlayerInfo.m_nXuidLow != *pWeapon->GetOriginalOwnerXuidLow()) - continue; - - if (LocalPlayerInfo.m_nXuidHigh != *pWeapon->GetOriginalOwnerXuidHigh()) - continue; - - ApplyCustomSkin(pWeapon, nWeaponIndex); - - // Fix up the account ID so StatTrak will display correctly. - *pWeapon->GetAccountID() = LocalPlayerInfo.m_nXuidLow; - } - - break; - } - - // Run the original FrameStageNotify function. - fnOriginalFunction(ecx, Stage); -} +// Include the game function hooks. +#include "FrameStageNotify.h" +#include "FireEventClientSide.h" void Initialise() { // Get the "CreateInterface" function from "client.dll" and "engine.dll". @@ -142,36 +34,53 @@ void Initialise() { g_EntityList = (IClientEntityList*)fnClientFactory("VClientEntityList003", NULL); g_EngineClient = (IVEngineClient*)fnEngineFactory("VEngineClient013", NULL); g_ModelInfo = (IVModelInfoClient*)fnEngineFactory("VModelInfoClient004", NULL); + g_GameEventMgr = (IGameEventManager2*)fnEngineFactory("GAMEEVENTSMANAGER002", NULL); - // Get the virtual method table for IBaseClientDLL. + // Get the virtual method tables for the classes we're going to hook from. PDWORD* pClientDLLVMT = (PDWORD*)g_BaseClient; + PDWORD* pGameEventMgrVMT = (PDWORD*)g_GameEventMgr; - // Save the untouched table so we know where the original functions are. + // Save the untouched tables so we know where the original functions are. PDWORD pOriginalClientDLLVMT = *pClientDLLVMT; + PDWORD pOriginalGameEventMgrVMT = *pGameEventMgrVMT; + + // Calculate the size of the tables. + size_t dwClientDLLVMTSize = 0; + size_t dwGameEventMgrVMTSize = 0; - // Calculate the size of the table. - size_t dwVMTSize = 0; + while ((PDWORD)(*pClientDLLVMT)[dwClientDLLVMTSize]) + dwClientDLLVMTSize++; - while ((PDWORD)(*pClientDLLVMT)[dwVMTSize]) - dwVMTSize++; + while ((PDWORD)(*pGameEventMgrVMT)[dwGameEventMgrVMTSize]) + dwGameEventMgrVMTSize++; - // Create the replacement table. - PDWORD pNewClientDLLVMT = new DWORD[dwVMTSize]; + // Create the replacement tables. + PDWORD pNewClientDLLVMT = new DWORD[dwClientDLLVMTSize]; + PDWORD pNewGameEventMgrVMT = new DWORD[dwGameEventMgrVMTSize]; // Copy the original table into the replacement table. - CopyMemory(pNewClientDLLVMT, pOriginalClientDLLVMT, (sizeof(DWORD) * dwVMTSize)); + CopyMemory(pNewClientDLLVMT, pOriginalClientDLLVMT, (sizeof(DWORD) * dwClientDLLVMTSize)); + CopyMemory(pNewGameEventMgrVMT, pOriginalGameEventMgrVMT, (sizeof(DWORD) * dwGameEventMgrVMTSize)); // Change the FrameStageNotify function in the new table to point to our function. pNewClientDLLVMT[36] = (DWORD)FrameStageNotifyThink; + + // Change the FireEventClientSide function in the new table to point to our function. + pNewGameEventMgrVMT[8] = (DWORD)FireEventClientSideThink; - // Backup the original function from the untouched table. - fnOriginalFunction = (FrameStageNotify)pOriginalClientDLLVMT[36]; + // Backup the original function from the untouched tables. + fnOriginalFrameStageNotify = (FrameStageNotify)pOriginalClientDLLVMT[36]; + fnOriginalFireEventClientSide = (FireEventClientSide)pOriginalGameEventMgrVMT[8]; - // Write the virtual method table. + // Write the virtual method tables. *pClientDLLVMT = pNewClientDLLVMT; + *pGameEventMgrVMT = pNewGameEventMgrVMT; // Import skins to use. SetSkinConfig(); + + // Import replacement kill icons. + SetKillIconCfg(); // Search for the 'CBaseViewModel' class. for (ClientClass* pClass = g_BaseClient->GetAllClasses(); pClass; pClass = pClass->m_pNext) { diff --git a/Chameleon/Chameleon.vcxproj b/Chameleon/Chameleon.vcxproj index 731d613..81c75ab 100644 --- a/Chameleon/Chameleon.vcxproj +++ b/Chameleon/Chameleon.vcxproj @@ -82,6 +82,9 @@ + + + @@ -94,6 +97,9 @@ + + + diff --git a/Chameleon/Chameleon.vcxproj.filters b/Chameleon/Chameleon.vcxproj.filters index d3fd857..4248192 100644 --- a/Chameleon/Chameleon.vcxproj.filters +++ b/Chameleon/Chameleon.vcxproj.filters @@ -22,11 +22,14 @@ {9cdf94f5-40a3-44f6-ade0-773138e9347f} + + {4e0ec2c9-16f8-49d3-bf2c-ace50bf0bf31} + + + {09f801b2-5fb0-4da3-97cd-1496c1bf2d66} + - - Header Files - Header Files\Source SDK\Generic @@ -57,10 +60,27 @@ Header Files + + Header Files + + + Header Files\Source SDK\Classes + + + Header Files\Hooks + + + Header Files + Source Files + + + Header Files\Hooks + + \ No newline at end of file diff --git a/Chameleon/FireEventClientSide.h b/Chameleon/FireEventClientSide.h new file mode 100644 index 0000000..c411702 --- /dev/null +++ b/Chameleon/FireEventClientSide.h @@ -0,0 +1,15 @@ +#pragma once + +// Define the calling convention for the FireEventClientSide function. +typedef bool(__thiscall *FireEventClientSide)(void*, IGameEvent*); +FireEventClientSide fnOriginalFireEventClientSide = NULL; + +// Perform kill icon replacements in here. +void __fastcall FireEventClientSideThink(void* ecx, void* edx, IGameEvent* pEvent) { + // Run our replacement function when a "player_death" event is fired. + if (pEvent && !strcmp(pEvent->GetName(), "player_death")) + ApplyCustomKillIcon(pEvent); + + // Run the original FireEventClientSide function. + fnOriginalFireEventClientSide(ecx, pEvent); +}; \ No newline at end of file diff --git a/Chameleon/FrameStageNotify.h b/Chameleon/FrameStageNotify.h new file mode 100644 index 0000000..240a907 --- /dev/null +++ b/Chameleon/FrameStageNotify.h @@ -0,0 +1,62 @@ +#pragma once + +// Define the calling convention for the FrameStageNotify function. +typedef void(__thiscall *FrameStageNotify)(void*, ClientFrameStage_t); +FrameStageNotify fnOriginalFrameStageNotify = NULL; + +void __fastcall FrameStageNotifyThink(void* ecx, void* edx, ClientFrameStage_t Stage) { + while (Stage == FRAME_NET_UPDATE_POSTDATAUPDATE_START) { + // Populate g_ViewModelCfg while in-game so IVModelInfoClient::GetModelIndex returns correctly. + if (g_ViewModelCfg.size() == 0) + SetModelConfig(); + + // Get our player entity. + int nLocalPlayerID = g_EngineClient->GetLocalPlayer(); + CBasePlayer* pLocal = (CBasePlayer*)g_EntityList->GetClientEntity(nLocalPlayerID); + + // Don't change anything if we're not alive. + if (!pLocal || pLocal->GetLifeState() != LIFE_ALIVE) + break; + + // Get handles to weapons we're carrying. + UINT* hWeapons = pLocal->GetWeapons(); + + if (!hWeapons) + break; + + // Retrieve our player information which will be used for ownership checking. + player_info_t LocalPlayerInfo; + g_EngineClient->GetPlayerInfo(nLocalPlayerID, &LocalPlayerInfo); + + // Loop through weapons and run our skin function on them. + for (int nIndex = 0; hWeapons[nIndex]; nIndex++) { + // Get the weapon entity from the provided handle. + CBaseAttributableItem* pWeapon = (CBaseAttributableItem*)g_EntityList->GetClientEntityFromHandle(hWeapons[nIndex]); + + if (!pWeapon) + continue; + + // Get the weapons item definition index. + int nWeaponIndex = *pWeapon->GetItemDefinitionIndex(); + + ApplyCustomModel(pLocal, pWeapon, nWeaponIndex); + + // Compare original owner XUIDs. + if (LocalPlayerInfo.m_nXuidLow != *pWeapon->GetOriginalOwnerXuidLow()) + continue; + + if (LocalPlayerInfo.m_nXuidHigh != *pWeapon->GetOriginalOwnerXuidHigh()) + continue; + + ApplyCustomSkin(pWeapon, nWeaponIndex); + + // Fix up the account ID so StatTrak will display correctly. + *pWeapon->GetAccountID() = LocalPlayerInfo.m_nXuidLow; + } + + break; + } + + // Run the original FrameStageNotify function. + fnOriginalFrameStageNotify(ecx, Stage); +} \ No newline at end of file diff --git a/Chameleon/Functions.h b/Chameleon/Functions.h new file mode 100644 index 0000000..52f88bb --- /dev/null +++ b/Chameleon/Functions.h @@ -0,0 +1,82 @@ +#pragma once + +// Function to apply skin data to weapons. +inline bool ApplyCustomSkin(CBaseAttributableItem* pWeapon, int nWeaponIndex) { + // Check if this weapon has a valid override defined. + if (g_SkinChangerCfg.find(nWeaponIndex) == g_SkinChangerCfg.end()) + return false; + + // Apply our changes to the fallback variables. + *pWeapon->GetFallbackPaintKit() = g_SkinChangerCfg[nWeaponIndex].nFallbackPaintKit; + *pWeapon->GetEntityQuality() = g_SkinChangerCfg[nWeaponIndex].iEntityQuality; + *pWeapon->GetFallbackSeed() = g_SkinChangerCfg[nWeaponIndex].nFallbackSeed; + *pWeapon->GetFallbackStatTrak() = g_SkinChangerCfg[nWeaponIndex].nFallbackStatTrak; + *pWeapon->GetFallbackWear() = g_SkinChangerCfg[nWeaponIndex].flFallbackWear; + + if (g_SkinChangerCfg[nWeaponIndex].iItemDefinitionIndex) + *pWeapon->GetItemDefinitionIndex() = g_SkinChangerCfg[nWeaponIndex].iItemDefinitionIndex; + + // If a name is defined, write it now. + if (g_SkinChangerCfg[nWeaponIndex].szCustomName) { + sprintf_s(pWeapon->GetCustomName(), 32, "%s", g_SkinChangerCfg[nWeaponIndex].szCustomName); + } + + // Edit "m_iItemIDHigh" so fallback values will be used. + *pWeapon->GetItemIDHigh() = -1; + + return true; +} + +// Function to apply custom view models to weapons. +inline bool ApplyCustomModel(CBasePlayer* pLocal, CBaseAttributableItem* pWeapon, int nWeaponIndex) { + // Get the view model of this weapon. + CBaseViewModel* pViewModel = pLocal->GetViewModel(); + + if (!pViewModel) + return false; + + // Get the weapon belonging to this view model. + DWORD hViewModelWeapon = pViewModel->GetWeapon(); + CBaseAttributableItem* pViewModelWeapon = (CBaseAttributableItem*)g_EntityList->GetClientEntityFromHandle(hViewModelWeapon); + + if (pViewModelWeapon != pWeapon) + return false; + + // Check if an override exists for this view model. + int nViewModelIndex = pViewModel->GetModelIndex(); + + if (g_ViewModelCfg.find(nViewModelIndex) == g_ViewModelCfg.end()) + return false; + + // Set the replacement model. + pViewModel->SetWeaponModel(g_ViewModelCfg[nViewModelIndex], pWeapon); + + return true; +} + +// Function to apply custom kill icons to events. +inline bool ApplyCustomKillIcon(IGameEvent* pEvent) { + // Get the user ID of the attacker. + int nUserID = pEvent->GetInt("attacker"); + + if (!nUserID) + return false; + + // Only continue if we were the attacker. + if (g_EngineClient->GetPlayerForUserID(nUserID) != g_EngineClient->GetLocalPlayer()) + return false; + + // Get the original weapon used to kill. + const char* szWeapon = pEvent->GetString("weapon"); + + for (auto ReplacementIcon: g_KillIconCfg) { + // Search for a valid replacement. + if (!strcmp(szWeapon, ReplacementIcon.first)) { + // Replace with user defined value. + pEvent->SetString("weapon", ReplacementIcon.second); + break; + } + } + + return true; +} \ No newline at end of file diff --git a/Chameleon/IGameEvents.h b/Chameleon/IGameEvents.h new file mode 100644 index 0000000..91beb16 --- /dev/null +++ b/Chameleon/IGameEvents.h @@ -0,0 +1,27 @@ +#pragma once + +class IGameEvent { + public: + const char* GetName() { + return CallVirtualFunction(this, 1)(this); + } + + int GetInt(const char* szKeyName, int nDefault = 0) { + return CallVirtualFunction(this, 6)(this, szKeyName, nDefault); + } + + const char* GetString(const char* szKeyName) { + return CallVirtualFunction(this, 9)(this, szKeyName, 0); + } + + void SetString(const char* szKeyName, const char* szValue) { + return CallVirtualFunction(this, 15)(this, szKeyName, szValue); + } +}; + +class IGameEventManager2 { + public: + bool FireEventClientSide(IGameEvent* pEvent) { + return CallVirtualFunction(this, 8)(this, pEvent); + } +}; \ No newline at end of file diff --git a/Chameleon/IVEngineClient.h b/Chameleon/IVEngineClient.h index a7e3c6a..1e28bbf 100644 --- a/Chameleon/IVEngineClient.h +++ b/Chameleon/IVEngineClient.h @@ -6,6 +6,10 @@ class IVEngineClient { return CallVirtualFunction(this, 8)(this, Index, PlayerInfo); } + inline int GetPlayerForUserID(int UserID) { + return CallVirtualFunction(this, 9)(this, UserID); + } + inline int GetLocalPlayer() { return CallVirtualFunction(this, 12)(this); } diff --git a/Chameleon/SDK.h b/Chameleon/SDK.h index 735efff..ff48104 100644 --- a/Chameleon/SDK.h +++ b/Chameleon/SDK.h @@ -7,12 +7,14 @@ class IBaseClientDLL; class IVEngineClient; class IClientEntityList; class IVModelInfoClient; +class IGameEventManager2; class IClientEntity; IBaseClientDLL* g_BaseClient = nullptr; IVEngineClient* g_EngineClient = nullptr; IClientEntityList* g_EntityList = nullptr; IVModelInfoClient* g_ModelInfo = nullptr; +IGameEventManager2* g_GameEventMgr = nullptr; #include "Defines.h" #include "DataTable.h" @@ -20,5 +22,6 @@ IVModelInfoClient* g_ModelInfo = nullptr; #include "IBaseClientDLL.h" #include "IVEngineClient.h" #include "IVModelInfoClient.h" +#include "IGameEvents.h" #include "IClientEntityList.h" #include "IClientEntity.h" \ No newline at end of file diff --git a/Chameleon/Skins.h b/Chameleon/Skins.h index d5778e7..77e80e1 100644 --- a/Chameleon/Skins.h +++ b/Chameleon/Skins.h @@ -14,6 +14,7 @@ struct EconomyItemCfg { std::unordered_map g_SkinChangerCfg; std::unordered_map g_ViewModelCfg; +std::unordered_map g_KillIconCfg; inline void SetSkinConfig() { // StatTrak™ AWP | Dragon Lore @@ -58,4 +59,10 @@ inline void SetModelConfig() { // Configure model replacements. g_ViewModelCfg[nOriginalKnifeCT] = "models/weapons/v_knife_karam.mdl"; g_ViewModelCfg[nOriginalKnifeT] = "models/weapons/v_knife_m9_bay.mdl"; +} + +inline void SetKillIconCfg() { + // Define replacement kill icons. (these only apply to you) + g_KillIconCfg["knife_default_ct"] = "knife_karambit"; + g_KillIconCfg["knife_t"] = "knife_m9_bayonet"; } \ No newline at end of file diff --git a/README.md b/README.md index 891e683..a3afbbd 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,12 @@ inline void SetModelConfig() { int nOriginalKnifeCT = g_ModelInfo->GetModelIndex("models/weapons/v_knife_default_ct.mdl"); g_ViewModelCfg[nOriginalKnifeCT] = "models/weapons/v_knife_karam.mdl"; } + +inline void SetKillIconCfg() { + // Define replacement kill icons. (these only apply to you) + g_KillIconCfg["knife_default_ct"] = "knife_karambit"; + g_KillIconCfg["knife_t"] = "knife_m9_bayonet"; +} ``` * Compile and inject into `csgo.exe` using your preferred injector.