Skip to content
This repository has been archived by the owner on Jul 8, 2022. It is now read-only.

Commit

Permalink
Add IGameEventManager2::FireEventClientSide hook to fix kill icons.
Browse files Browse the repository at this point in the history
Moved things into separate files to make Chameleon.cpp a bit cleaner.
  • Loading branch information
aixxe committed Jan 3, 2016
1 parent 6cc05e1 commit fd6273d
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 128 deletions.
159 changes: 34 additions & 125 deletions Chameleon/Chameleon.cpp
Expand Up @@ -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".
Expand All @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions Chameleon/Chameleon.vcxproj
Expand Up @@ -82,6 +82,9 @@
<ClInclude Include="ClientClass.h" />
<ClInclude Include="DataTable.h" />
<ClInclude Include="Defines.h" />
<ClInclude Include="FireEventClientSide.h" />
<ClInclude Include="Functions.h" />
<ClInclude Include="IGameEvents.h" />
<ClInclude Include="Proxies.h" />
<ClInclude Include="IBaseClientDLL.h" />
<ClInclude Include="IClientEntity.h" />
Expand All @@ -94,6 +97,9 @@
<ItemGroup>
<ClCompile Include="Chameleon.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="FrameStageNotify.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
Expand Down
26 changes: 23 additions & 3 deletions Chameleon/Chameleon.vcxproj.filters
Expand Up @@ -22,11 +22,14 @@
<Filter Include="Header Files\Source SDK\Classes">
<UniqueIdentifier>{9cdf94f5-40a3-44f6-ade0-773138e9347f}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Hooks">
<UniqueIdentifier>{4e0ec2c9-16f8-49d3-bf2c-ace50bf0bf31}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Functions">
<UniqueIdentifier>{09f801b2-5fb0-4da3-97cd-1496c1bf2d66}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Skins.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Defines.h">
<Filter>Header Files\Source SDK\Generic</Filter>
</ClInclude>
Expand Down Expand Up @@ -57,10 +60,27 @@
<ClInclude Include="Proxies.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Skins.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="IGameEvents.h">
<Filter>Header Files\Source SDK\Classes</Filter>
</ClInclude>
<ClInclude Include="FireEventClientSide.h">
<Filter>Header Files\Hooks</Filter>
</ClInclude>
<ClInclude Include="Functions.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Chameleon.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="FrameStageNotify.h">
<Filter>Header Files\Hooks</Filter>
</None>
</ItemGroup>
</Project>
15 changes: 15 additions & 0 deletions 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);
};
62 changes: 62 additions & 0 deletions 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);
}

0 comments on commit fd6273d

Please sign in to comment.