From 1a1914cd46ef8c3ab3d4b8a3fba0c4f354220e3f Mon Sep 17 00:00:00 2001 From: GT610 Date: Tue, 7 Jul 2026 17:04:23 +0800 Subject: [PATCH 1/2] Validate consumed tool item types --- csgo_gc/inventory.cpp | 137 +++++++++++++++++++++++++++++++++++----- csgo_gc/inventory.h | 5 +- csgo_gc/item_schema.cpp | 95 ++++++++++++++++++++++++++++ csgo_gc/item_schema.h | 5 ++ 4 files changed, 225 insertions(+), 17 deletions(-) diff --git a/csgo_gc/inventory.cpp b/csgo_gc/inventory.cpp index bbfaeeb..1d6fc19 100644 --- a/csgo_gc/inventory.cpp +++ b/csgo_gc/inventory.cpp @@ -509,6 +509,23 @@ bool Inventory::UnlockCrate(uint64_t crateId, return false; } + if (keyId) + { + auto key = m_items.find(keyId); + if (key == m_items.end()) + { + Platform::Print("UnlockCrate: key item %llu not found for crate %llu\n", keyId, crateId); + return false; + } + + if (!m_itemSchema.IsKeyToolDefIndex(key->second.def_index())) + { + Platform::Print("UnlockCrate: item %llu def %u is not a key tool for crate %llu\n", + keyId, key->second.def_index(), crateId); + return false; + } + } + // CASE OPENING CaseOpening caseOpening{ m_itemSchema, m_random }; @@ -532,9 +549,8 @@ bool Inventory::UnlockCrate(uint64_t crateId, { DestroyItem(crate, destroyCrate); - // remove the key if one was used (yes, we don't validate keys...) auto key = m_items.find(keyId); - if (key != m_items.end()) + if (keyId && key != m_items.end()) { DestroyItem(key, destroyKey); } @@ -822,6 +838,21 @@ bool Inventory::ApplySticker(const CMsgApplySticker &message, return false; } + if (message.sticker_slot() >= MaxStickers) + { + Platform::Print("ApplySticker: invalid slot %u for tool %llu\n", + message.sticker_slot(), message.sticker_item_id()); + return false; + } + + if (sticker->second.def_index() != ItemSchema::ItemSticker + && sticker->second.def_index() != ItemSchema::ItemPatch) + { + Platform::Print("ApplySticker: item %llu def %u is not a sticker or patch tool\n", + message.sticker_item_id(), sticker->second.def_index()); + return false; + } + CSOEconItem *item = nullptr; if (message.baseitem_defidx()) @@ -856,6 +887,8 @@ bool Inventory::ApplySticker(const CMsgApplySticker &message, if (!stickerKit) { + Platform::Print("ApplySticker: item %llu def %u has no sticker kit attribute\n", + message.sticker_item_id(), sticker->second.def_index()); assert(false); return false; } @@ -1119,6 +1152,21 @@ bool Inventory::NameItem(uint64_t nameTagId, CMsgSOSingleObject &destroy, CMsgGCItemCustomizationNotification ¬ification) { + auto tag = m_items.find(nameTagId); + if (tag == m_items.end()) + { + Platform::Print("NameItem: name tag item %llu not found for target %llu\n", + nameTagId, itemId); + return false; + } + + if (!m_itemSchema.IsNameTagToolDefIndex(tag->second.def_index())) + { + Platform::Print("NameItem: item %llu def %u is not a name tag for target %llu\n", + nameTagId, tag->second.def_index(), itemId); + return false; + } + auto it = m_items.find(itemId); if (it == m_items.end()) { @@ -1132,13 +1180,6 @@ bool Inventory::NameItem(uint64_t nameTagId, if (GetConfig().DestroyUsedItems()) { - auto tag = m_items.find(nameTagId); - if (tag == m_items.end()) - { - assert(false); - return false; - } - DestroyItem(tag, destroy); } @@ -1155,6 +1196,21 @@ bool Inventory::NameBaseItem(uint64_t nameTagId, CMsgSOSingleObject &destroy, CMsgGCItemCustomizationNotification ¬ification) { + auto tag = m_items.find(nameTagId); + if (tag == m_items.end()) + { + Platform::Print("NameBaseItem: name tag item %llu not found for base def %u\n", + nameTagId, defIndex); + return false; + } + + if (!m_itemSchema.IsNameTagToolDefIndex(tag->second.def_index())) + { + Platform::Print("NameBaseItem: item %llu def %u is not a name tag for base def %u\n", + nameTagId, tag->second.def_index(), defIndex); + return false; + } + CSOEconItem &item = CreateItem(defIndex, ItemOriginBaseItem, UnacknowledgedInvalid); item.mutable_custom_name()->assign(name); @@ -1163,13 +1219,6 @@ bool Inventory::NameBaseItem(uint64_t nameTagId, if (GetConfig().DestroyUsedItems()) { - auto tag = m_items.find(nameTagId); - if (tag == m_items.end()) - { - assert(false); - return false; - } - DestroyItem(tag, destroy); } @@ -1664,6 +1713,23 @@ Inventory::CounterSwapResult Inventory::PerformCounterSwap(uint64_t toolId, uint CounterSwapResult result{}; result.weaponAId = weaponAId; result.weaponBId = weaponBId; + + auto toolIt = m_items.find(toolId); + if (toolIt == m_items.end()) + { + Platform::Print("StatTrakSwap: tool item %llu not found for weapons %llu and %llu\n", + toolId, weaponAId, weaponBId); + result.status = CounterSwapStatus::ToolMissing; + return result; + } + + if (!m_itemSchema.IsStatTrakSwapToolDefIndex(toolIt->second.def_index())) + { + Platform::Print("StatTrakSwap: item %llu def %u is not a StatTrak Swap Tool\n", + toolId, toolIt->second.def_index()); + result.status = CounterSwapStatus::InvalidTool; + return result; + } auto itA = m_items.find(weaponAId); auto itB = m_items.find(weaponBId); @@ -1677,9 +1743,20 @@ Inventory::CounterSwapResult Inventory::PerformCounterSwap(uint64_t toolId, uint CSOEconItem &weaponA = itA->second; CSOEconItem &weaponB = itB->second; + + if (weaponA.quality() != ItemSchema::QualityStrange + || weaponB.quality() != ItemSchema::QualityStrange) + { + Platform::Print("StatTrakSwap: weapons must both be Strange quality (weapon %llu quality %u, weapon %llu quality %u)\n", + weaponAId, weaponA.quality(), weaponBId, weaponB.quality()); + result.status = CounterSwapStatus::InvalidWeaponState; + return result; + } CSOEconItemAttribute *attrA = nullptr; CSOEconItemAttribute *attrB = nullptr; + bool weaponAScoreType = false; + bool weaponBScoreType = false; for (int i = 0; i < weaponA.attribute_size(); ++i) { @@ -1689,6 +1766,16 @@ Inventory::CounterSwapResult Inventory::PerformCounterSwap(uint64_t toolId, uint break; } } + + for (int i = 0; i < weaponA.attribute_size(); ++i) + { + if (weaponA.attribute(i).def_index() == ItemSchema::AttributeKillEaterScoreType + && m_itemSchema.AttributeUint32(&weaponA.attribute(i)) == 0) + { + weaponAScoreType = true; + break; + } + } for (int i = 0; i < weaponB.attribute_size(); ++i) { @@ -1698,6 +1785,16 @@ Inventory::CounterSwapResult Inventory::PerformCounterSwap(uint64_t toolId, uint break; } } + + for (int i = 0; i < weaponB.attribute_size(); ++i) + { + if (weaponB.attribute(i).def_index() == ItemSchema::AttributeKillEaterScoreType + && m_itemSchema.AttributeUint32(&weaponB.attribute(i)) == 0) + { + weaponBScoreType = true; + break; + } + } bool bothHaveCounters = attrA && attrB; if (!bothHaveCounters) @@ -1705,6 +1802,14 @@ Inventory::CounterSwapResult Inventory::PerformCounterSwap(uint64_t toolId, uint result.status = CounterSwapStatus::CounterAttributeAbsent; return result; } + + if (!weaponAScoreType || !weaponBScoreType) + { + Platform::Print("StatTrakSwap: weapons must both use weapon kill counters (weapon %llu score type ok=%d, weapon %llu score type ok=%d)\n", + weaponAId, weaponAScoreType ? 1 : 0, weaponBId, weaponBScoreType ? 1 : 0); + result.status = CounterSwapStatus::InvalidWeaponState; + return result; + } uint32_t valA = m_itemSchema.AttributeUint32(attrA); uint32_t valB = m_itemSchema.AttributeUint32(attrB); diff --git a/csgo_gc/inventory.h b/csgo_gc/inventory.h index 7249537..08b5d72 100644 --- a/csgo_gc/inventory.h +++ b/csgo_gc/inventory.h @@ -103,8 +103,11 @@ class Inventory enum class CounterSwapStatus { Completed, + ToolMissing, + InvalidTool, WeaponMissing, - CounterAttributeAbsent + CounterAttributeAbsent, + InvalidWeaponState }; struct CounterSwapResult diff --git a/csgo_gc/item_schema.cpp b/csgo_gc/item_schema.cpp index aa51d94..2981e93 100644 --- a/csgo_gc/item_schema.cpp +++ b/csgo_gc/item_schema.cpp @@ -680,6 +680,8 @@ void ItemSchema::ParseItemRecursive(ItemInfo &info, const KeyValue &itemKey, con std::vector prefabNames = SplitString(prefabString, ' '); for (std::string_view prefabName : prefabNames) { + info.m_prefabs.emplace_back(prefabName); + const KeyValue *prefabKey = prefabsKey->GetSubkey(prefabName); if (prefabKey) { @@ -722,6 +724,7 @@ void ItemSchema::ParseItemRecursive(ItemInfo &info, const KeyValue &itemKey, con std::string_view itemType = itemKey.GetString("item_type"); if (itemType.size()) { + info.m_itemType = itemType; info.m_isCoupon = (itemType == "coupon"); } @@ -1296,3 +1299,95 @@ uint32_t ItemSchema::GetPaintedRarity(uint32_t defIndex, uint32_t paintKitDefInd return PaintedItemRarity(itemInfo->m_rarity, paintKitInfo->m_rarity); } + +static char LowerAscii(char c) +{ + if (c >= 'A' && c <= 'Z') + { + return c - 'A' + 'a'; + } + + return c; +} + +static bool ContainsInsensitive(std::string_view haystack, std::string_view needle) +{ + if (needle.empty() || haystack.size() < needle.size()) + { + return false; + } + + for (size_t i = 0; i <= haystack.size() - needle.size(); i++) + { + bool match = true; + for (size_t j = 0; j < needle.size(); j++) + { + if (LowerAscii(haystack[i + j]) != LowerAscii(needle[j])) + { + match = false; + break; + } + } + + if (match) + { + return true; + } + } + + return false; +} + +static bool ItemInfoContains(const ItemInfo &info, std::string_view text) +{ + if (ContainsInsensitive(info.m_name, text) || ContainsInsensitive(info.m_itemType, text)) + { + return true; + } + + for (const std::string &prefab : info.m_prefabs) + { + if (ContainsInsensitive(prefab, text)) + { + return true; + } + } + + return false; +} + +bool ItemSchema::IsKeyToolDefIndex(uint32_t defIndex) const +{ + const ItemInfo *info = ItemInfoByDefIndex(defIndex); + if (!info) + { + return false; + } + + return ItemInfoContains(*info, "key"); +} + +bool ItemSchema::IsNameTagToolDefIndex(uint32_t defIndex) const +{ + const ItemInfo *info = ItemInfoByDefIndex(defIndex); + if (!info) + { + return false; + } + + return ItemInfoContains(*info, "name tag") + || ItemInfoContains(*info, "name_tag") + || ItemInfoContains(*info, "nametag"); +} + +bool ItemSchema::IsStatTrakSwapToolDefIndex(uint32_t defIndex) const +{ + const ItemInfo *info = ItemInfoByDefIndex(defIndex); + if (!info) + { + return false; + } + + return ItemInfoContains(*info, "stattrak") + && ItemInfoContains(*info, "swap"); +} diff --git a/csgo_gc/item_schema.h b/csgo_gc/item_schema.h index 420edf1..4ca68e9 100644 --- a/csgo_gc/item_schema.h +++ b/csgo_gc/item_schema.h @@ -31,6 +31,8 @@ class ItemInfo uint32_t m_quality; uint32_t m_level; uint32_t m_supplyCrateSeries; // cases only + std::string m_itemType; + std::vector m_prefabs; // kludge for coupons so we can buy stuff from the store bool m_isCoupon; @@ -154,6 +156,9 @@ class ItemSchema bool GetTradeUpCandidates(std::string_view collectionName, uint32_t outputRarity, std::vector &outCandidates) const; uint32_t GetPaintedRarity(uint32_t defIndex, uint32_t paintKitDefIndex, uint32_t fallbackRarity) const; + bool IsKeyToolDefIndex(uint32_t defIndex) const; + bool IsNameTagToolDefIndex(uint32_t defIndex) const; + bool IsStatTrakSwapToolDefIndex(uint32_t defIndex) const; public: From 1aece15c39fce0433ad5d72bc88a73e4d0e09f16 Mon Sep 17 00:00:00 2001 From: GT610 Date: Tue, 7 Jul 2026 17:42:05 +0800 Subject: [PATCH 2/2] Address tool validation review feedback --- csgo_gc/inventory.cpp | 78 ++++++++++++++++++----------------------- csgo_gc/item_schema.cpp | 41 +++++++++++++++++++++- 2 files changed, 74 insertions(+), 45 deletions(-) diff --git a/csgo_gc/inventory.cpp b/csgo_gc/inventory.cpp index 1d6fc19..bf7c95c 100644 --- a/csgo_gc/inventory.cpp +++ b/csgo_gc/inventory.cpp @@ -1708,6 +1708,33 @@ void Inventory::ConsumeToolItem(uint64_t toolId, CMsgSOSingleObject &removalMsg) DestroyItem(it, removalMsg); } +struct CounterSwapWeaponCounters +{ + CSOEconItemAttribute *killEater = nullptr; + bool usesWeaponKillCounter = false; +}; + +static CounterSwapWeaponCounters FindCounterSwapWeaponCounters(ItemSchema &schema, CSOEconItem &weapon) +{ + CounterSwapWeaponCounters counters{}; + + for (int i = 0; i < weapon.attribute_size(); ++i) + { + CSOEconItemAttribute *attribute = weapon.mutable_attribute(i); + if (attribute->def_index() == ItemSchema::AttributeKillEater) + { + counters.killEater = attribute; + } + else if (attribute->def_index() == ItemSchema::AttributeKillEaterScoreType + && schema.AttributeUint32(attribute) == 0) + { + counters.usesWeaponKillCounter = true; + } + } + + return counters; +} + Inventory::CounterSwapResult Inventory::PerformCounterSwap(uint64_t toolId, uint64_t weaponAId, uint64_t weaponBId) { CounterSwapResult result{}; @@ -1753,48 +1780,10 @@ Inventory::CounterSwapResult Inventory::PerformCounterSwap(uint64_t toolId, uint return result; } - CSOEconItemAttribute *attrA = nullptr; - CSOEconItemAttribute *attrB = nullptr; - bool weaponAScoreType = false; - bool weaponBScoreType = false; - - for (int i = 0; i < weaponA.attribute_size(); ++i) - { - if (weaponA.attribute(i).def_index() == ItemSchema::AttributeKillEater) - { - attrA = weaponA.mutable_attribute(i); - break; - } - } - - for (int i = 0; i < weaponA.attribute_size(); ++i) - { - if (weaponA.attribute(i).def_index() == ItemSchema::AttributeKillEaterScoreType - && m_itemSchema.AttributeUint32(&weaponA.attribute(i)) == 0) - { - weaponAScoreType = true; - break; - } - } - - for (int i = 0; i < weaponB.attribute_size(); ++i) - { - if (weaponB.attribute(i).def_index() == ItemSchema::AttributeKillEater) - { - attrB = weaponB.mutable_attribute(i); - break; - } - } - - for (int i = 0; i < weaponB.attribute_size(); ++i) - { - if (weaponB.attribute(i).def_index() == ItemSchema::AttributeKillEaterScoreType - && m_itemSchema.AttributeUint32(&weaponB.attribute(i)) == 0) - { - weaponBScoreType = true; - break; - } - } + CounterSwapWeaponCounters countersA = FindCounterSwapWeaponCounters(m_itemSchema, weaponA); + CounterSwapWeaponCounters countersB = FindCounterSwapWeaponCounters(m_itemSchema, weaponB); + CSOEconItemAttribute *attrA = countersA.killEater; + CSOEconItemAttribute *attrB = countersB.killEater; bool bothHaveCounters = attrA && attrB; if (!bothHaveCounters) @@ -1803,10 +1792,11 @@ Inventory::CounterSwapResult Inventory::PerformCounterSwap(uint64_t toolId, uint return result; } - if (!weaponAScoreType || !weaponBScoreType) + if (!countersA.usesWeaponKillCounter || !countersB.usesWeaponKillCounter) { Platform::Print("StatTrakSwap: weapons must both use weapon kill counters (weapon %llu score type ok=%d, weapon %llu score type ok=%d)\n", - weaponAId, weaponAScoreType ? 1 : 0, weaponBId, weaponBScoreType ? 1 : 0); + weaponAId, countersA.usesWeaponKillCounter ? 1 : 0, + weaponBId, countersB.usesWeaponKillCounter ? 1 : 0); result.status = CounterSwapStatus::InvalidWeaponState; return result; } diff --git a/csgo_gc/item_schema.cpp b/csgo_gc/item_schema.cpp index 2981e93..005bf58 100644 --- a/csgo_gc/item_schema.cpp +++ b/csgo_gc/item_schema.cpp @@ -1338,6 +1338,24 @@ static bool ContainsInsensitive(std::string_view haystack, std::string_view need return false; } +static bool EqualsInsensitive(std::string_view left, std::string_view right) +{ + if (left.size() != right.size()) + { + return false; + } + + for (size_t i = 0; i < left.size(); i++) + { + if (LowerAscii(left[i]) != LowerAscii(right[i])) + { + return false; + } + } + + return true; +} + static bool ItemInfoContains(const ItemInfo &info, std::string_view text) { if (ContainsInsensitive(info.m_name, text) || ContainsInsensitive(info.m_itemType, text)) @@ -1356,6 +1374,24 @@ static bool ItemInfoContains(const ItemInfo &info, std::string_view text) return false; } +static bool ItemInfoMatchesIdentifier(const ItemInfo &info, std::string_view text) +{ + if (EqualsInsensitive(info.m_name, text) || EqualsInsensitive(info.m_itemType, text)) + { + return true; + } + + for (const std::string &prefab : info.m_prefabs) + { + if (EqualsInsensitive(prefab, text)) + { + return true; + } + } + + return false; +} + bool ItemSchema::IsKeyToolDefIndex(uint32_t defIndex) const { const ItemInfo *info = ItemInfoByDefIndex(defIndex); @@ -1364,7 +1400,10 @@ bool ItemSchema::IsKeyToolDefIndex(uint32_t defIndex) const return false; } - return ItemInfoContains(*info, "key"); + return ItemInfoMatchesIdentifier(*info, "weapon_case_key") + || ItemInfoMatchesIdentifier(*info, "weaponcasekey") + || ItemInfoMatchesIdentifier(*info, "#CSGO_Type_WeaponCaseKey") + || ItemInfoMatchesIdentifier(*info, "CSGO_Type_WeaponCaseKey"); } bool ItemSchema::IsNameTagToolDefIndex(uint32_t defIndex) const