View Single Post
Author Message
Benoist3012
Veteran Member
Join Date: Mar 2014
Location: CWave::ForceFinish()
Old 01-31-2022 , 07:40   [TF2/ANY?] Disable items static attributes without removing them
Reply With Quote #1

Introduction

Have you ever wanted to strip attributes on a weapon, without screwing over the client's min view models, or any other cosmetic effects (like weapon skins) ?
Then this snippet is for you! With the addition of DHooks into sourcemod I can now share my method.

The code

PHP Code:
DynamicHook g_hDHookItemIterateAttribute;
int g_iCEconItem_m_Item;
int g_iCEconItemView_m_bOnlyIterateItemViewAttributes;

static 
void SDK_Init()
{
    
Handle hConfig = new GameData("yourgamedata");

    
int iOffset GameConfGetOffset(hConfig"CEconItemView::IterateAttributes");
    
g_hDHookItemIterateAttribute = new DynamicHook(iOffsetHookType_RawReturnType_VoidThisPointer_Address);
    if (
g_hDHookItemIterateAttribute == null)
    {
        
SetFailState("Failed to create hook CEconItemView::IterateAttributes offset from SF2 gamedata!");
    }
    
g_hDHookItemIterateAttribute.AddParam(HookParamType_ObjectPtr);

    
g_iCEconItem_m_Item FindSendPropInfo("CEconEntity""m_Item");
    
FindSendPropInfo("CEconEntity""m_bOnlyIterateItemViewAttributes"__g_iCEconItemView_m_bOnlyIterateItemViewAttributes);
    
    
delete hConfig;
}

public 
void OnEntityCreate(int iEntity, const char[] sClassname)
// OR (As long you have a method to detect the weapon/hat/medal/etc... entity)
public void TF2Items_OnGiveNamedItem_Post(int iClientchar[] sClassnameint iItemDefIndexint iLevelint iQualityint iEntity)
{
    if (
your special weapon detection method here)
    {
        
Address pCEconItemView GetEntityAddress(iEntity) + view_as<Address>(g_iCEconItem_m_Item);
        
g_hDHookItemIterateAttribute.HookRaw(Hook_PrepCEconItemViewCEconItemView_IterateAttributes);
        
g_hDHookItemIterateAttribute.HookRaw(Hook_PostpCEconItemViewCEconItemView_IterateAttributes_Post);
    }
}

static 
MRESReturn CEconItemView_IterateAttributes(Address pThisDHookParam hParams)
{
    
StoreToAddress(pThis view_as<Address>(g_iCEconItemView_m_bOnlyIterateItemViewAttributes), trueNumberType_Int8false);
    return 
MRES_Ignored;
}

static 
MRESReturn CEconItemView_IterateAttributes_Post(Address pThisDHookParam hParams)
{
    
StoreToAddress(pThis view_as<Address>(g_iCEconItemView_m_bOnlyIterateItemViewAttributes), falseNumberType_Int8false);
    return 
MRES_Ignored;

In depth explanation

Everytime the server or client needs to parse an item's attributes (both dynamic and static), it goes through the item's CEconItemView (m_Item) and then calls CEconItemView::IterateAttributes.

CEconItemView::IterateAttributes works like so : After parsing the dynamic attributes, it looks for the member property CEconItemView::m_bOnlyIterateItemViewAttribut es.
If it's true, the server won't parse the static attributes, if it is false it will.
CEconItemView::m_bOnlyIterateItemViewAttribut es is also networked, therefore if you force set it to true on the server the client will also not parse static attributes. Which we don't want, as we need static attributes like "min view model offset" to still work on the client side.
This is where DHook comes in...

PHP Code:
g_iCEconItem_m_Item FindSendPropInfo("CEconEntity""m_Item"); 
m_Item absolute offset retrieved so we may create a CEconItemView ptr with it, which is needed by DHook to detour the function CEconItemView::IterateAttributes.
PHP Code:
FindSendPropInfo("CEconEntity""m_bOnlyIterateItemViewAttributes"__g_iCEconItemView_m_bOnlyIterateItemViewAttributes); 
m_bOnlyIterateItemViewAttributes LOCAL offset is retrieved (relative to CEconItemView) so we can update the property value under DHook's callbacks.

The rest should now be very straight forward now.
- When CEconItemView_IterateAttributes fires, we change CEconItemView::m_bOnlyIterateItemViewAttribut es to true so the static attributes aren't parsed on the server.
- When CEconItemView_IterateAttributes_Post fires, we change CEconItemView::m_bOnlyIterateItemViewAttribut es back to false so the static attributes are still parsed on the client (remember, it's a networked property).

And voilą! You now have successfully created an item whose static attributes will be ignored by the server but not the client.

Where to find gamedata ?

Use IDA, or Asherkin's very useful tool vtable.
Look for CEconItemView::IterateAttributes and you're set.

Edit: This snippet is also technically valid on other source games that have items, but you will have to dig a bit to ensure the logic of attributes iteration I described above is valid there as well.
Raw hooks are also not cleaned upon entity deletion, so don't forget to store the return value of DynamicHook.HookRaw and remove the hooks when entity is deleted.
__________________

Last edited by Benoist3012; 03-17-2024 at 19:41. Reason: Small footnote for "any" games
Benoist3012 is offline