mmsplugin.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /**
  2. * vim: set ts=4 sw=4 tw=99 noet :
  3. * ======================================================
  4. * TF2 Dynamic Schema Injector
  5. * Written by nosoop
  6. * ======================================================
  7. */
  8. #include <stdio.h>
  9. #include "mmsplugin.h"
  10. #include "econmanager.h"
  11. #include <filesystem.h>
  12. #include <map>
  13. void BindToSourceMod();
  14. bool SM_LoadExtension(char *error, size_t maxlength);
  15. void SM_UnloadExtension();
  16. SH_DECL_HOOK3_void(IServerGameDLL, ServerActivate, SH_NOATTRIB, 0, edict_t *, int, int);
  17. SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, 0, bool, char const *, char const *, char const *, char const *, bool, bool);
  18. DynSchema g_Plugin;
  19. IServerGameDLL *server = nullptr;
  20. IVEngineServer *engine = NULL;
  21. IFileSystem *filesystem = nullptr;
  22. PLUGIN_EXPOSE(DynSchema, g_Plugin);
  23. const char* NATIVE_ATTRIB_DIR = "addons/sourcemod/configs/tf2nativeattribs";
  24. bool DynSchema::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late)
  25. {
  26. PLUGIN_SAVEVARS();
  27. GET_V_IFACE_CURRENT(GetEngineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER);
  28. GET_V_IFACE_ANY(GetServerFactory, server, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL);
  29. GET_V_IFACE_CURRENT(GetFileSystemFactory, filesystem, IFileSystem, FILESYSTEM_INTERFACE_VERSION);
  30. SH_ADD_HOOK_MEMFUNC(IServerGameDLL, LevelInit, server, this, &DynSchema::Hook_LevelInitPost, true);
  31. return true;
  32. }
  33. bool DynSchema::OnExtensionLoad(IExtension *me, IShareSys *sys, char *error, size_t maxlength, bool late) {
  34. sharesys = sys;
  35. myself = me;
  36. /* Get the default interfaces from our configured SDK header */
  37. if (!SM_AcquireInterfaces(error, maxlength)) {
  38. return false;
  39. }
  40. /* Prepare our manager */
  41. if (!g_EconManager.Init(error, maxlength)) {
  42. return false;
  43. }
  44. return true;
  45. }
  46. bool DynSchema::Unload(char *error, size_t maxlen) {
  47. SM_UnloadExtension();
  48. SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, LevelInit, server, this, &DynSchema::Hook_LevelInitPost, true);
  49. return true;
  50. }
  51. bool DynSchema::Hook_LevelInitPost(const char *pMapName, char const *pMapEntities,
  52. char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background) {
  53. // this hook should fire shortly after the schema is (re)initialized
  54. char game_path[256];
  55. engine->GetGameDir(game_path, sizeof(game_path));
  56. char buffer[1024];
  57. g_SMAPI->PathFormat(buffer, sizeof(buffer), "%s/%s",
  58. game_path, "addons/dynattrs/items_dynamic.txt");
  59. // always initialize attributes -- it's better than losing attributes on schema reinit
  60. // coughhiddendevattributescough
  61. // read our own dynamic schema file -- this one supports other sections
  62. KeyValues::AutoDelete pKVMainConfig("DynamicSchema");
  63. if (pKVMainConfig->LoadFromFile(filesystem, buffer)) {
  64. KeyValues *pKVAttributes = pKVMainConfig->FindKey( "attributes" );
  65. if (pKVAttributes) {
  66. FOR_EACH_TRUE_SUBKEY(pKVAttributes, kv) {
  67. g_EconManager.InsertOrReplaceAttribute(kv);
  68. }
  69. META_CONPRINTF("Successfully injected custom schema %s\n", buffer);
  70. } else {
  71. META_CONPRINTF("Failed to inject custom schema %s\n", buffer);
  72. }
  73. }
  74. // iterate over TF2 Hidden Dev Attributes KV format
  75. // https://forums.alliedmods.net/showthread.php?t=326853
  76. g_SMAPI->PathFormat(buffer, sizeof(buffer), "%s/%s/*", game_path, NATIVE_ATTRIB_DIR);
  77. FileFindHandle_t findHandle;
  78. const char *filename = filesystem->FindFirst(buffer, &findHandle);
  79. while (filename) {
  80. char pathbuf[1024];
  81. g_SMAPI->PathFormat(pathbuf, sizeof(pathbuf), "%s/%s/%s", game_path,
  82. NATIVE_ATTRIB_DIR, filename);
  83. if (!filesystem->FindIsDirectory(findHandle)) {
  84. KeyValues::AutoDelete nativeAttribConfig("attributes");
  85. nativeAttribConfig->LoadFromFile(filesystem, pathbuf);
  86. FOR_EACH_TRUE_SUBKEY(nativeAttribConfig, kv) {
  87. g_EconManager.InsertOrReplaceAttribute(kv);
  88. }
  89. META_CONPRINTF("Discovered custom schema %s\n", pathbuf);
  90. }
  91. filename = filesystem->FindNext(findHandle);
  92. }
  93. filesystem->FindClose(findHandle);
  94. // perhaps add some other validations before we actually process our attributes?
  95. // TODO ensure the name doesn't clash with existing / newly injected attributes
  96. return true;
  97. }
  98. void DynSchema::AllPluginsLoaded() {
  99. /* This is where we'd do stuff that relies on the mod or other plugins
  100. * being initialized (for example, cvars added and events registered).
  101. */
  102. BindToSourceMod();
  103. }
  104. void* DynSchema::OnMetamodQuery(const char* iface, int *ret) {
  105. if (strcmp(iface, SOURCEMOD_NOTICE_EXTENSIONS) == 0) {
  106. BindToSourceMod();
  107. }
  108. if (ret != NULL) {
  109. *ret = IFACE_OK;
  110. }
  111. return NULL;
  112. }
  113. void BindToSourceMod() {
  114. char error[256];
  115. if (!SM_LoadExtension(error, sizeof(error))) {
  116. char message[512];
  117. snprintf(message, sizeof(message), "Could not load as a SourceMod extension: %s\n", error);
  118. engine->LogPrint(message);
  119. }
  120. }
  121. bool SM_LoadExtension(char *error, size_t maxlength) {
  122. if ((smexts = (IExtensionManager *)
  123. g_SMAPI->MetaFactory(SOURCEMOD_INTERFACE_EXTENSIONS, NULL, NULL)) == NULL) {
  124. if (error && maxlength) {
  125. snprintf(error, maxlength, SOURCEMOD_INTERFACE_EXTENSIONS " interface not found");
  126. }
  127. return false;
  128. }
  129. /* This could be more dynamic */
  130. char path[256];
  131. g_SMAPI->PathFormat(path, sizeof(path), "addons/dynattrs/tf2dynschema%s",
  132. #if defined __linux__
  133. "_mm.so"
  134. #else
  135. ".dll"
  136. #endif
  137. );
  138. if ((myself = smexts->LoadExternal(&g_Plugin, path, "dynschema.ext", error, maxlength))
  139. == NULL) {
  140. SM_UnsetInterfaces();
  141. return false;
  142. }
  143. return true;
  144. }
  145. void SM_UnloadExtension() {
  146. smexts->UnloadExtension(myself);
  147. }
  148. bool DynSchema::Pause(char *error, size_t maxlen) {
  149. return true;
  150. }
  151. bool DynSchema::Unpause(char *error, size_t maxlen) {
  152. return true;
  153. }
  154. const char *DynSchema::GetLicense() {
  155. return "GPLv3+";
  156. }
  157. const char *DynSchema::GetVersion() {
  158. return "1.3.0";
  159. }
  160. const char *DynSchema::GetDate() {
  161. return __DATE__;
  162. }
  163. const char *DynSchema::GetLogTag() {
  164. return "dynschema";
  165. }
  166. const char *DynSchema::GetAuthor() {
  167. return "nosoop";
  168. }
  169. const char *DynSchema::GetDescription() {
  170. return "Injects user-defined content into the game schema";
  171. }
  172. const char *DynSchema::GetName() {
  173. return "TF2 Dynamic Schema";
  174. }
  175. const char *DynSchema::GetURL() {
  176. return "https://git.csrd.science/";
  177. }
  178. void DynSchema::OnExtensionUnload() {
  179. SM_UnsetInterfaces();
  180. }
  181. void DynSchema::OnExtensionsAllLoaded() {
  182. // no-op
  183. }
  184. void DynSchema::OnExtensionPauseChange(bool pause) {
  185. // no-op
  186. }
  187. bool DynSchema::QueryRunning(char *error, size_t maxlength) {
  188. return true;
  189. }
  190. bool DynSchema::IsMetamodExtension() {
  191. return true;
  192. }
  193. const char *DynSchema::GetExtensionName() {
  194. return this->GetName();
  195. }
  196. const char *DynSchema::GetExtensionURL() {
  197. return this->GetURL();
  198. }
  199. const char *DynSchema::GetExtensionTag() {
  200. return this->GetLogTag();
  201. }
  202. const char *DynSchema::GetExtensionAuthor() {
  203. return this->GetAuthor();
  204. }
  205. const char *DynSchema::GetExtensionVerString() {
  206. return this->GetVersion();
  207. }
  208. const char *DynSchema::GetExtensionDescription() {
  209. return this->GetDescription();
  210. }
  211. const char *DynSchema::GetExtensionDateString() {
  212. return this->GetDate();
  213. }