8 Commits 60afbb4080 ... e3d6a9572b

Author SHA1 Message Date
  nosoop e3d6a9572b Add attribute file compatibility shim 1 year ago
  nosoop dca66cb7ca Remove in-extension attribute loading component 1 year ago
  nosoop bf3f136ee0 Avoid overzealous / leaked copy 1 year ago
  nosoop 8a6c4f2865 Implement copy assignment for AutoKeyValues 1 year ago
  nosoop 6ae8a98d8c Fix stored attribute definition index being zero 1 year ago
  nosoop 3ee72cf531 Add natives for creating attributes 1 year ago
  nosoop 60afbb4080 Fix stored attribute definition index being zero 1 year ago
  nosoop 510dff7c4d Add natives for creating attributes 1 year ago
8 changed files with 241 additions and 73 deletions
  1. 0 4
      PackageScript
  2. 1 1
      econmanager.cpp
  3. 13 4
      econmanager.h
  4. 1 62
      mmsplugin.cpp
  5. 1 1
      mmsplugin.h
  6. 19 0
      scripting/include/tf_econ_dynamic.inc
  7. 206 0
      scripting/tf_econ_dynamic_compat.sp
  8. 0 1
      tf2econdynamic.autoload

+ 0 - 4
PackageScript

@@ -30,10 +30,6 @@ CopyFiles('gamedata', 'addons/sourcemod/gamedata', [
   'tf2.econ_dynamic.txt',
 ])
 
-CopyFiles('', 'addons/sourcemod/extensions', [
-  'tf2econdynamic.autoload',
-])
-
 # Copy binaries.
 for cxx_task in Extension.extensions:
   builder.AddCopy(cxx_task.binary, folder_map['addons/sourcemod/extensions'])

+ 1 - 1
econmanager.cpp

@@ -107,7 +107,7 @@ bool CEconManager::InsertOrReplaceAttribute(KeyValues *pAttribKV) {
 }
 
 bool CEconManager::RegisterAttribute(KeyValues* pAttribKV) {
-	AutoKeyValues kv{pAttribKV->MakeCopy()};
+	AutoKeyValues kv{pAttribKV};
 	std::string attrName = kv->GetString("name");
 	if (attrName.empty()) {
 		attrName = kv->GetString("attribute_class");

+ 13 - 4
econmanager.h

@@ -44,6 +44,7 @@ public:
 
 /**
  * Copied implementation of KeyValues::AutoDelete.
+ * This implementation creates copies of any KeyValues pointers passed into it.
  */
 class AutoKeyValues {
 	public:
@@ -52,11 +53,19 @@ class AutoKeyValues {
 	AutoKeyValues(KeyValues *pKeyValues) : m_pKeyValues{pKeyValues->MakeCopy()} {}
 	AutoKeyValues(const AutoKeyValues &other) : m_pKeyValues{other.m_pKeyValues->MakeCopy()} {}
 	
+	/**
+	 * Copy assignment.  The contents of the KeyValues* needs to be copied; the default
+	 * implementation seems to cause a use-after-free.
+	 */
+	AutoKeyValues& operator =(const AutoKeyValues &other) {
+		this->Assign(other);
+		return *this;
+	}
+	
 	~AutoKeyValues() {
-		// TODO: figure out why doing this blows up the server
-		// if (m_pKeyValues) {
-			// m_pKeyValues->deleteThis();
-		// }
+		if (m_pKeyValues) {
+			m_pKeyValues->deleteThis();
+		}
 	}
 	
 	void Assign(KeyValues *pKeyValues) {

+ 1 - 62
mmsplugin.cpp

@@ -11,25 +11,14 @@
 #include "econmanager.h"
 #include "natives.h"
 
-#include <filesystem.h>
-
 #include <map>
 
 SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, 0, bool, char const *, char const *, char const *, char const *, bool, bool);
 
 DynSchema g_Plugin;
-IFileSystem *filesystem = nullptr;
 
 SMEXT_LINK(&g_Plugin);
 
-const char* NATIVE_ATTRIB_DIR = "addons/sourcemod/configs/tf2nativeattribs";
-
-bool DynSchema::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) {
-	GET_V_IFACE_CURRENT(GetFileSystemFactory, filesystem, IFileSystem, FILESYSTEM_INTERFACE_VERSION);
-	
-	return true;
-}
-
 bool DynSchema::SDK_OnLoad(char *error, size_t maxlen, bool late)
 {
 	SH_ADD_HOOK_MEMFUNC(IServerGameDLL, LevelInit, gamedll, this, &DynSchema::Hook_LevelInitPost, true);
@@ -53,57 +42,7 @@ void DynSchema::SDK_OnUnload() {
 
 bool DynSchema::Hook_LevelInitPost(const char *pMapName, char const *pMapEntities,
 		char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background) {
-	// this hook should fire shortly after the schema is (re)initialized
-	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");
-	
-	// always initialize attributes -- it's better than losing attributes on schema reinit
-	// coughhiddendevattributescough
-	
-	// read our own dynamic schema file -- this one supports other sections
-	KeyValues::AutoDelete pKVMainConfig("DynamicSchema");
-	if (pKVMainConfig->LoadFromFile(filesystem, buffer)) {
-		KeyValues *pKVAttributes = pKVMainConfig->FindKey( "attributes" );
-		if (pKVAttributes) {
-			FOR_EACH_TRUE_SUBKEY(pKVAttributes, kv) {
-				g_EconManager.RegisterAttribute(kv);
-			}
-			META_CONPRINTF("Successfully injected custom schema %s\n", buffer);
-		} else {
-			META_CONPRINTF("Failed to inject custom schema %s\n", buffer);
-		}
-	}
-	
-	// iterate over TF2 Hidden Dev Attributes KV format
-	// https://forums.alliedmods.net/showthread.php?t=326853
-	g_SMAPI->PathFormat(buffer, sizeof(buffer), "%s/%s/*", game_path, NATIVE_ATTRIB_DIR);
-	FileFindHandle_t findHandle;
-	const char *filename = filesystem->FindFirst(buffer, &findHandle);
-	while (filename) {
-		char pathbuf[1024];
-		g_SMAPI->PathFormat(pathbuf, sizeof(pathbuf), "%s/%s/%s", game_path,
-				NATIVE_ATTRIB_DIR, filename);
-		if (!filesystem->FindIsDirectory(findHandle)) {
-			KeyValues::AutoDelete nativeAttribConfig("attributes");
-			nativeAttribConfig->LoadFromFile(filesystem, pathbuf);
-			
-			FOR_EACH_TRUE_SUBKEY(nativeAttribConfig, kv) {
-				g_EconManager.RegisterAttribute(kv);
-			}
-			
-			META_CONPRINTF("Discovered custom schema %s\n", pathbuf);
-		}
-		filename = filesystem->FindNext(findHandle);
-	}
-	filesystem->FindClose(findHandle);
-	
-	// perhaps add some other validations before we actually process our attributes?
-	// TODO ensure the name doesn't clash with existing / newly injected attributes
-	
+	// reinstall attributes as needed
 	g_EconManager.InstallAttributes();
 	
 	return true;

+ 1 - 1
mmsplugin.h

@@ -81,7 +81,7 @@ public:
 	 * @param late			Whether or not Metamod considers this a late load.
 	 * @return				True to succeed, false to fail.
 	 */
-	virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late);
+	//virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late);
 
 	/**
 	 * @brief Called when Metamod is detaching, after the extension version is called.

+ 19 - 0
scripting/include/tf_econ_dynamic.inc

@@ -96,3 +96,22 @@ methodmap EconInjectedAttribute < Handle {
 	 */
 	public native void Clear();
 }
+
+/**
+ * Do not edit below this line!
+ */
+public Extension __ext_tf_econ_dynamic =
+{
+	name = "TF2 Econ Dynamic",
+	file = "tf2econdynamic.ext",
+#if defined AUTOLOAD_EXTENSIONS
+	autoload = 1,
+#else
+	autoload = 0,
+#endif
+#if defined REQUIRE_EXTENSIONS
+	required = 1,
+#else
+	required = 0,
+#endif
+};

+ 206 - 0
scripting/tf_econ_dynamic_compat.sp

@@ -0,0 +1,206 @@
+/**
+ * [TF2] Econ Dynamic Compatibility Shim
+ * 
+ * Implements compatibility shims to load attribute definitions from files.
+ */
+#pragma semicolon 1
+#include <sourcemod>
+
+#pragma newdecls required
+
+#include <tf_econ_dynamic>
+
+#define PLUGIN_VERSION "1.0.0"
+public Plugin myinfo = {
+	name = "[TF2] Econ Dynamic Compatibility Shim",
+	author = "nosoop",
+	description = "Compatibility shim to install attributes specified by other plugins",
+	version = PLUGIN_VERSION,
+	url = "https://github.com/nosoop/SMExt-TFEconDynamic"
+}
+
+static EconInjectedAttribute s_AttributeContext;
+
+public void OnPluginStart() {
+	s_AttributeContext = new EconInjectedAttribute();
+	
+	CreateAttributeConfigParsers();
+}
+
+public void OnMapStart() {
+	// this loads relatively late, but it should be fine
+	LoadAttributes();
+}
+
+enum HiddenAttributeConfigParseState {
+	HiddenAttributeConfigParseState_Root,
+	
+	HiddenAttributeConfigParseState_AttributeList,
+	HiddenAttributeConfigParseState_AttributeProperties,
+}
+
+static SMCParser s_HiddenDevAttributeParser;
+
+static HiddenAttributeConfigParseState s_ParseState = HiddenAttributeConfigParseState_Root;
+static int s_nParseStateIgnoreNestedSections;
+
+void CreateAttributeConfigParsers() {
+	s_HiddenDevAttributeParser = new SMCParser();
+	
+	
+	s_HiddenDevAttributeParser.OnStart = OnAttributeConfigStartParse;
+	s_HiddenDevAttributeParser.OnEnd = OnAttributeConfigEndParse;
+	
+	s_HiddenDevAttributeParser.OnEnterSection = OnAttributeConfigEnterSection;
+	s_HiddenDevAttributeParser.OnLeaveSection = OnAttributeConfigLeaveSection;
+	
+	s_HiddenDevAttributeParser.OnKeyValue = OnAttributeConfigKeyValue;
+}
+
+void OnAttributeConfigStartParse(SMCParser smc) {
+	s_ParseState = HiddenAttributeConfigParseState_Root;
+	s_nParseStateIgnoreNestedSections = 0;
+}
+
+/**
+ * Push new parse state depending on the current section.
+ */
+SMCResult OnAttributeConfigEnterSection(SMCParser smc, const char[] name, bool opt_quotes) {
+	/**
+	 * If we're ignoring a parent section, increment and don't emit a change in parse state.
+	 */
+	if (s_nParseStateIgnoreNestedSections) {
+		s_nParseStateIgnoreNestedSections++;
+		return SMCParse_Continue;
+	}
+	
+	switch (s_ParseState) {
+		case HiddenAttributeConfigParseState_Root: {
+			if (StrEqual(name, "attributes", .caseSensitive = false)) {
+				s_ParseState = HiddenAttributeConfigParseState_AttributeList;
+			} else {
+				LogError("Entering unexpected section '%s' while in parse state %d", name, s_ParseState);
+				return SMCParse_HaltFail;
+			}
+		}
+		case HiddenAttributeConfigParseState_AttributeList: {
+			s_AttributeContext.Clear();
+			
+			int attrdef;
+			if (StrEqual(name, "auto", .caseSensitive = false)) {
+				// do nothing
+			} else if (StringToIntEx(name, attrdef)) {
+				s_AttributeContext.SetDefIndex(attrdef);
+#if defined DEBUG
+				LogMessage("Setting attribute context to itemdef %d", attrdef);
+#endif
+			} else {
+				LogError("Attempting to declare attribute with invalid defindex '%s'", name);
+				return SMCParse_HaltFail;
+			}
+			s_ParseState = HiddenAttributeConfigParseState_AttributeProperties;
+		}
+		case HiddenAttributeConfigParseState_AttributeProperties: {
+			// ignore nested properties for now
+			LogMessage("Warning: Nested keys are not currently supported");
+			s_nParseStateIgnoreNestedSections++;
+		}
+		default: {
+			LogError("Entering unexpected section '%s' while in parse state %d", name,
+					s_ParseState);
+			return SMCParse_HaltFail;
+		}
+	}
+	return SMCParse_Continue;
+}
+
+/**
+ * Pop parse state and go back to previous one.
+ */
+SMCResult OnAttributeConfigLeaveSection(SMCParser smc) {
+	/**
+	 * If we're leaving an ignored section, decrement and don't emit a change in parse state.
+	 */
+	if (s_nParseStateIgnoreNestedSections) {
+		s_nParseStateIgnoreNestedSections--;
+		return SMCParse_Continue;
+	}
+	
+	switch (s_ParseState) {
+		case HiddenAttributeConfigParseState_AttributeProperties: {
+			s_ParseState = HiddenAttributeConfigParseState_AttributeList;
+			s_AttributeContext.Register();
+		}
+		case HiddenAttributeConfigParseState_AttributeList: {
+			s_ParseState = HiddenAttributeConfigParseState_Root;
+		}
+		case HiddenAttributeConfigParseState_Root: {
+			LogError("Leaving section while in root parse state");
+		}
+		default: {
+			LogError("Leaving section while in parse state %d", s_ParseState);
+		}
+	}
+	return SMCParse_Continue;
+}
+
+SMCResult OnAttributeConfigKeyValue(SMCParser smc, const char[] key, const char[] value,
+		bool key_quotes, bool value_quotes) {
+	switch (s_ParseState) {
+		case HiddenAttributeConfigParseState_AttributeProperties: {
+			s_AttributeContext.SetCustom(key, value);
+#if defined DEBUG
+			LogMessage("Adding attribute context key / value: '%s' = '%s'", key, value);
+#endif
+		}
+		default: {
+			LogError("Unexpected key / value pair while in parse state %d", s_ParseState);
+		}
+	}
+	return SMCParse_Continue;
+}
+
+void OnAttributeConfigEndParse(SMCParser smc, bool halted, bool failed) {
+	if (halted || failed) {
+		return;
+	}
+	
+	if (s_ParseState != HiddenAttributeConfigParseState_Root) {
+		LogError("Parse state not at root on end parse");
+	}
+}
+
+void LoadAttributes() {
+	char configDir[PLATFORM_MAX_PATH];
+	BuildPath(Path_SM, configDir, sizeof(configDir), "configs/tf2nativeattribs");
+
+	if (!DirExists(configDir, false)) {
+		return;
+	}
+	
+	DirectoryListing list = OpenDirectory(configDir, false);
+	FileType fileType;
+	char filename[PLATFORM_MAX_PATH];
+
+	while (list.GetNext(filename, sizeof(filename), fileType)) {
+		if (fileType != FileType_File) {
+			continue;
+		}
+		
+		Format(filename, sizeof(filename), "%s/%s", configDir, filename);
+
+		if (strncmp(filename[strlen(filename) - 4], ".txt", 4, false) != 0) {
+			PrintToServer("[TF2 Econ Dynamic] "
+					... "Attributes file %s does not have a txt extension. "
+					... "Attributes will not be loaded",
+					filename);
+			continue;
+		}
+
+		if (s_HiddenDevAttributeParser.ParseFile(filename) != SMCError_Okay) {
+			PrintToServer("[TF2 Econ Dynamic] "
+					... "Failed to load file %s. Attributes will not be loaded",
+					filename);
+		}
+	}
+}

+ 0 - 1
tf2econdynamic.autoload

@@ -1 +0,0 @@
-ECHO is on.