econmanager.cpp 4.8 KB

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