econmanager.cpp 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. #include "econmanager.h"
  2. #include <IGameConfigs.h>
  3. #include <map>
  4. CEconManager g_EconManager;
  5. // pointer to item schema attribute map singleton
  6. using AttributeMap = CUtlMap<int, CEconItemAttributeDefinition, int>;
  7. AttributeMap *g_SchemaAttributes;
  8. size_t g_nAutoAttributeBase = 4000;
  9. std::map<std::string, int> g_AutoNumberedAttributes{};
  10. typedef uintptr_t (*GetEconItemSchema_fn)(void);
  11. GetEconItemSchema_fn fnGetEconItemSchema = nullptr;
  12. // https://www.unknowncheats.me/wiki/Calling_Functions_From_Injected_Library_Using_Function_Pointers_in_C%2B%2B
  13. #ifdef WIN32
  14. typedef bool (__thiscall *CEconItemAttributeInitFromKV_fn)(CEconItemAttributeDefinition* pThis, KeyValues* pAttributeKeys, CUtlVector<CUtlString>* pErrors);
  15. #elif defined(_LINUX)
  16. typedef bool (__cdecl *CEconItemAttributeInitFromKV_fn)(CEconItemAttributeDefinition* pThis, KeyValues* pAttributeKeys, CUtlVector<CUtlString>* pErrors);
  17. #endif
  18. CEconItemAttributeInitFromKV_fn fnItemAttributeInitFromKV = nullptr;
  19. bool CEconManager::Init(char *error, size_t maxlength) {
  20. // get the base address of the server
  21. SourceMod::IGameConfig *conf;
  22. if (!gameconfs->LoadGameConfigFile("tf2.econ_dynamic", &conf, error, maxlength)) {
  23. return false;
  24. }
  25. if (!conf->GetMemSig("GEconItemSchema()", (void**) &fnGetEconItemSchema)) {
  26. snprintf(error, maxlength, "Failed to setup call to GetEconItemSchema()");
  27. return false;
  28. } else if (!conf->GetMemSig("CEconItemAttributeDefinition::BInitFromKV()", (void**) &fnItemAttributeInitFromKV)) {
  29. snprintf(error, maxlength, "Failed to setup call to CEconItemAttributeDefinition::BInitFromKV");
  30. return false;
  31. }
  32. gameconfs->CloseGameConfigFile(conf);
  33. // is this late enough in the MM:S load stage? we might just have to hold the function
  34. g_SchemaAttributes = reinterpret_cast<AttributeMap*>(fnGetEconItemSchema() + 0x1BC);
  35. return true;
  36. }
  37. /**
  38. * Initializes a CEconItemAttributeDefinition from a KeyValues definition, then inserts or
  39. * replaces the appropriate entry in the schema.
  40. */
  41. bool CEconManager::InsertOrReplaceAttribute(KeyValues *pAttribKV) {
  42. KeyValues::AutoDelete attribute{pAttribKV->MakeCopy()};
  43. const char* attrID = attribute->GetName();
  44. const char* attrName = attribute->GetString("name");
  45. int attrdef;
  46. if (strcmp(attrID, "auto") == 0) {
  47. /**
  48. * Have the plugin automatically allocate an attribute ID.
  49. * - if the name is already mapped to an ID, then use that
  50. * - otherwise, continue to increment our counter until we find an unused one
  51. */
  52. auto search = g_AutoNumberedAttributes.find(attrName);
  53. if (search != g_AutoNumberedAttributes.end()) {
  54. attrdef = search->second;
  55. } else {
  56. while (g_SchemaAttributes->Find(g_nAutoAttributeBase) != g_SchemaAttributes->InvalidIndex()) {
  57. g_nAutoAttributeBase++;
  58. }
  59. attrdef = g_nAutoAttributeBase;
  60. g_AutoNumberedAttributes[attrName] = attrdef;
  61. }
  62. } else {
  63. attrdef = atoi(attrID);
  64. if (attrdef <= 0) {
  65. META_CONPRINTF("Attribute '%s' has invalid index string '%s'\n", attrName, attrID);
  66. return false;
  67. }
  68. }
  69. attribute->SetName(std::to_string(attrdef).c_str());
  70. // only replace existing injected attributes; fail on schema attributes
  71. auto existingIndex = g_SchemaAttributes->Find(attrdef);
  72. if (existingIndex != g_SchemaAttributes->InvalidIndex()) {
  73. auto &existingAttr = g_SchemaAttributes->Element(existingIndex);
  74. if (!existingAttr.m_KeyValues->GetBool("injected")) {
  75. META_CONPRINTF("WARN: Not overriding native attribute '%s'\n",
  76. existingAttr.m_pszName);
  77. return false;
  78. }
  79. }
  80. // embed additional custom data into attribute KV; econdata and the like can deal with this
  81. // one could also add this data into the file itself, but this leaves less room for error
  82. attribute->SetBool("injected", true);
  83. CEconItemAttributeDefinition def;
  84. fnItemAttributeInitFromKV(&def, attribute, nullptr);
  85. // TODO verify that this doesn't leak, or just shrug it off
  86. g_SchemaAttributes->InsertOrReplace(attrdef, def);
  87. return true;
  88. }
  89. bool CEconManager::RegisterAttribute(KeyValues* pAttribKV) {
  90. AutoKeyValues kv{pAttribKV};
  91. std::string attrName = kv->GetString("name");
  92. if (attrName.empty()) {
  93. attrName = kv->GetString("attribute_class");
  94. if (attrName.empty()) {
  95. return false;
  96. }
  97. kv->SetString("name", attrName.c_str());
  98. }
  99. this->m_RegisteredAttributes[attrName] = std::move(kv);
  100. return true;
  101. }
  102. void CEconManager::InstallAttributes() {
  103. for (const auto& pair : m_RegisteredAttributes) {
  104. InsertOrReplaceAttribute(pair.second);
  105. }
  106. }