Browse Source

Add symbol lookup and call functions

nosoop 4 years ago
parent
commit
9f09cc9e40
2 changed files with 153 additions and 10 deletions
  1. 1 1
      AMBuildScript
  2. 152 9
      mmsplugin.cpp

+ 1 - 1
AMBuildScript

@@ -397,7 +397,7 @@ class MMSConfig(object):
 
     dynamic_libs = [] # todo: this whole section is slightly different, but I imagine it is "more correct"
     if builder.target.platform == 'linux':
-      compiler.linkflags[0:0] = ['-lm', '-ldl'] # todo: do we need -ldl?
+      binary.compiler.linkflags[0:0] = ['-lm', '-ldl', '-lelf'] # todo: do we need -ldl?
       if sdk.name in ['css', 'hl2dm', 'dods', 'tf2', 'sdk2013', 'bms', 'nucleardawn', 'l4d2', 'insurgency', 'doi']:
         dynamic_libs = ['libtier0_srv.so', 'libvstdlib_srv.so']
       elif arch == 'x64' and sdk.name == 'csgo':

+ 152 - 9
mmsplugin.cpp

@@ -9,34 +9,155 @@
 #include <stdio.h>
 #include "mmsplugin.h"
 
+#include <fcntl.h>
+#include <gelf.h>
+#include <utlmap.h>
+#include <utlstring.h>
+#include <KeyValues.h>
+#include <filesystem.h>
+
 SH_DECL_HOOK3_void(IServerGameDLL, ServerActivate, SH_NOATTRIB, 0, edict_t *, int, int);
 SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, 0, bool, char const *, char const *, char const *, char const *, bool, bool);
 
 DynSchema g_Plugin;
-IServerGameDLL *server = NULL;
+IServerGameDLL *server = nullptr;
+IVEngineServer *engine = NULL;
+IBaseFileSystem *basefilesystem = nullptr;
 
 PLUGIN_EXPOSE(DynSchema, g_Plugin);
 
+class ISchemaAttributeType;
+
+// this may need to be updated in the future
+class CEconItemAttributeDefinition
+{
+public:
+	/* 0x00 */ KeyValues *m_KeyValues;
+	/* 0x04 */ unsigned short m_iIndex;
+	/* 0x08 */ ISchemaAttributeType *m_AttributeType;
+	/* 0x0c */ bool m_bHidden;
+	/* 0x0d */ bool m_bForceOutputDescription;
+	/* 0x0e */ bool m_bStoreAsInteger;
+	/* 0x0f */ bool m_bInstanceData;
+	/* 0x10 */ int m_iAssetClassExportType;
+	/* 0x14 */ int m_iAssetClassBucket;
+	/* 0x18 */ bool m_bIsSetBonus;
+	/* 0x1c */ int m_iIsUserGenerated;
+	/* 0x20 */ int m_iEffectType;
+	/* 0x24 */ int m_iDescriptionFormat;
+	/* 0x28 */ char *m_pszDescriptionString;
+	/* 0x2c */ char *m_pszArmoryDesc;
+	/* 0x30 */ char *m_pszName;
+	/* 0x34 */ char *m_pszAttributeClass;
+	/* 0x38 */ bool m_bCanAffectMarketName;
+	/* 0x39 */ bool m_bCanAffectRecipeCompName;
+	/* 0x3c */ int m_nTagHandle;
+	/* 0x40 */ string_t m_iszAttributeClass;
+};
+
+// binary refers to 0x58 when iterating over the attribute map, so we'll refer to that value
+// we could also do a runtime assertion
+static_assert(sizeof(CEconItemAttributeDefinition) + 0x14 == 0x58, "CEconItemAttributeDefinition size mismatch");
+
+// pointer to item schema attribute map singleton
+using AttributeMap = CUtlMap<int, CEconItemAttributeDefinition, int>;
+AttributeMap *g_SchemaAttributes;
+
+using GetEconItemSchemaFn_t = uintptr_t();
+GetEconItemSchemaFn_t *fnGetEconItemSchema = nullptr;
+
+// https://www.unknowncheats.me/wiki/Calling_Functions_From_Injected_Library_Using_Function_Pointers_in_C%2B%2B
+#ifdef WIN32
+typedef bool (__thiscall *CEconItemAttributeInitFromKV_fn)(CEconItemAttributeDefinition* pThis, KeyValues* pAttributeKeys, CUtlVector<CUtlString>* pErrors);
+#elif defined(_LINUX)
+typedef bool (__cdecl *CEconItemAttributeInitFromKV_fn)(CEconItemAttributeDefinition* pThis, KeyValues* pAttributeKeys, CUtlVector<CUtlString>* pErrors);
+#endif
+CEconItemAttributeInitFromKV_fn fnItemAttributeInitFromKV = nullptr;
+
 bool DynSchema::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late)
 {
 	PLUGIN_SAVEVARS();
 
-	/* Make sure we build on MM:S 1.4 */
-#if defined METAMOD_PLAPI_VERSION
+	GET_V_IFACE_CURRENT(GetEngineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER);
 	GET_V_IFACE_ANY(GetServerFactory, server, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL);
-#else
-	GET_V_IFACE_ANY(serverFactory, server, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL);
-#endif
+	GET_V_IFACE_CURRENT(GetFileSystemFactory, basefilesystem, IBaseFileSystem, BASEFILESYSTEM_INTERFACE_VERSION);
 
 	SH_ADD_HOOK_MEMFUNC(IServerGameDLL, LevelInit, server, this, &DynSchema::Hook_LevelInitPost, true);
 
+	// get the base address of the server
+	// TODO windows support
+	Dl_info info;
+	if (!dladdr(server, &info)) {
+		return false;
+	}
+	
+	// locate symbols within our server binary
+	Elf_Scn     *scn = NULL;
+	GElf_Shdr   shdr;
+
+	elf_version(EV_CURRENT);
+
+	int fd = open(info.dli_fname, O_RDONLY);
+	Elf *elf = elf_begin(fd, ELF_C_READ, NULL);
+
+	while ((scn = elf_nextscn(elf, scn)) != NULL) {
+		gelf_getshdr(scn, &shdr);
+		if (shdr.sh_type == SHT_SYMTAB) {
+			break;
+		}
+	}
+
+	Elf_Data *data = elf_getdata(scn, NULL);
+	size_t count = shdr.sh_size / shdr.sh_entsize;
+
+	/* print the symbol names */
+	for (size_t ii = 0; ii < count; ++ii) {
+		GElf_Sym sym;
+		gelf_getsym(data, ii, &sym);
+		
+		const char *symname = elf_strptr(elf, shdr.sh_link, sym.st_name);
+		if (!strcmp(symname,
+				"_ZN28CEconItemAttributeDefinition11BInitFromKVEP9KeyValuesP10CUtlVectorI10CUtlString10CUtlMemoryIS3_iEE")) {
+			fnItemAttributeInitFromKV = (CEconItemAttributeInitFromKV_fn) (reinterpret_cast<uintptr_t>(info.dli_fbase) + sym.st_value);
+		} else if (!strcmp(symname, "_Z15GEconItemSchemav")) {
+			fnGetEconItemSchema = reinterpret_cast<GetEconItemSchemaFn_t*>(reinterpret_cast<uintptr_t>(info.dli_fbase) + sym.st_value);
+		}
+	}
+	elf_end(elf);
+	close(fd);
+	
+	if (!fnItemAttributeInitFromKV || !fnGetEconItemSchema) {
+		META_CONPRINTF("Failed to get either GEconItemSchema or BInitFromKeyValues\n");
+		return false;
+	}
+	
+	// is this late enough in the MM:S load stage?  we might just have to hold the function
+	g_SchemaAttributes = reinterpret_cast<AttributeMap*>(fnGetEconItemSchema() + 0x1BC);
+
 	return true;
 }
 
-bool DynSchema::Unload(char *error, size_t maxlen)
-{
+bool DynSchema::Unload(char *error, size_t maxlen) {
 	SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, LevelInit, server, this, &DynSchema::Hook_LevelInitPost, true);
+	return true;
+}
 
+bool AddAttribute(KeyValues *pAttribKV) {
+	int attrdef = atoi(pAttribKV->GetName());
+	
+	// TODO add a copy of these tests in native
+	if (attrdef <= 0) {
+		return false;
+	}
+	
+	if (g_SchemaAttributes->IsValidIndex(g_SchemaAttributes->Find(attrdef))) {
+		return false;
+	}
+	
+	CEconItemAttributeDefinition def;
+	fnItemAttributeInitFromKV(&def, pAttribKV, nullptr);
+	
+	g_SchemaAttributes->Insert(attrdef, def);
 	return true;
 }
 
@@ -48,6 +169,28 @@ bool DynSchema::Hook_LevelInitPost(const char *pMapName, char const *pMapEntitie
 	// - adding a sentinel attribute that we test the existence of later, or
 	// - check in LevelInitPre if we have a non-null CEconItemSchema::m_pDelayedSchemaData
 	
+	// TODO create a map of existing attribute names
+	
+	char game_path[256];
+	engine->GetGameDir(game_path, sizeof(game_path));
+	
+	char buffer[1024];
+	g_SMAPI->PathFormat(buffer, sizeof(buffer), "%s/%s",
+			game_path, "addons/dynattrs/items_dynamic.txt");
+	
+	KeyValues::AutoDelete pItemKV("DynamicSchema");
+	if (pItemKV->LoadFromFile(basefilesystem, buffer)) {
+		KeyValues *pKVAttributes = pItemKV->FindKey( "attributes" );
+		if (pKVAttributes) {
+			FOR_EACH_TRUE_SUBKEY(pKVAttributes, pKVAttribute) {
+				AddAttribute(pKVAttribute);
+			}
+		}
+		META_CONPRINTF("Successfully injected custom schema %s\n", buffer);
+	} else {
+		META_CONPRINTF("Failed to locate not inject custom schema %s\n", buffer);
+	}
+	
 	return true;
 }
 
@@ -86,7 +229,7 @@ const char *DynSchema::GetAuthor() {
 }
 
 const char *DynSchema::GetDescription() {
-	return "Injects user-defined attributes into the game schema";
+	return "Injects user-defined content into the game schema";
 }
 
 const char *DynSchema::GetName() {