mmsplugin.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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.RegisterAttribute(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.RegisterAttribute(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. g_EconManager.InstallAttributes();
  97. return true;
  98. }
  99. void DynSchema::AllPluginsLoaded() {
  100. /* This is where we'd do stuff that relies on the mod or other plugins
  101. * being initialized (for example, cvars added and events registered).
  102. */
  103. BindToSourceMod();
  104. }
  105. void* DynSchema::OnMetamodQuery(const char* iface, int *ret) {
  106. if (strcmp(iface, SOURCEMOD_NOTICE_EXTENSIONS) == 0) {
  107. BindToSourceMod();
  108. }
  109. if (ret != NULL) {
  110. *ret = IFACE_OK;
  111. }
  112. return NULL;
  113. }
  114. void BindToSourceMod() {
  115. char error[256];
  116. if (!SM_LoadExtension(error, sizeof(error))) {
  117. char message[512];
  118. snprintf(message, sizeof(message), "Could not load as a SourceMod extension: %s\n", error);
  119. engine->LogPrint(message);
  120. }
  121. }
  122. bool SM_LoadExtension(char *error, size_t maxlength) {
  123. if ((smexts = (IExtensionManager *)
  124. g_SMAPI->MetaFactory(SOURCEMOD_INTERFACE_EXTENSIONS, NULL, NULL)) == NULL) {
  125. if (error && maxlength) {
  126. snprintf(error, maxlength, SOURCEMOD_INTERFACE_EXTENSIONS " interface not found");
  127. }
  128. return false;
  129. }
  130. /* This could be more dynamic */
  131. char path[256];
  132. g_SMAPI->PathFormat(path, sizeof(path), "addons/dynattrs/tf2dynschema%s",
  133. #if defined __linux__
  134. "_mm.so"
  135. #else
  136. ".dll"
  137. #endif
  138. );
  139. if ((myself = smexts->LoadExternal(&g_Plugin, path, "dynschema.ext", error, maxlength))
  140. == NULL) {
  141. SM_UnsetInterfaces();
  142. return false;
  143. }
  144. return true;
  145. }
  146. void SM_UnloadExtension() {
  147. smexts->UnloadExtension(myself);
  148. }
  149. bool DynSchema::Pause(char *error, size_t maxlen) {
  150. return true;
  151. }
  152. bool DynSchema::Unpause(char *error, size_t maxlen) {
  153. return true;
  154. }
  155. const char *DynSchema::GetLicense() {
  156. return "GPLv3+";
  157. }
  158. const char *DynSchema::GetVersion() {
  159. return "1.3.0";
  160. }
  161. const char *DynSchema::GetDate() {
  162. return __DATE__;
  163. }
  164. const char *DynSchema::GetLogTag() {
  165. return "dynschema";
  166. }
  167. const char *DynSchema::GetAuthor() {
  168. return "nosoop";
  169. }
  170. const char *DynSchema::GetDescription() {
  171. return "Injects user-defined content into the game schema";
  172. }
  173. const char *DynSchema::GetName() {
  174. return "TF2 Dynamic Schema";
  175. }
  176. const char *DynSchema::GetURL() {
  177. return "https://git.csrd.science/";
  178. }
  179. void DynSchema::OnExtensionUnload() {
  180. SM_UnsetInterfaces();
  181. }
  182. void DynSchema::OnExtensionsAllLoaded() {
  183. // no-op
  184. }
  185. void DynSchema::OnExtensionPauseChange(bool pause) {
  186. // no-op
  187. }
  188. bool DynSchema::QueryRunning(char *error, size_t maxlength) {
  189. return true;
  190. }
  191. bool DynSchema::IsMetamodExtension() {
  192. return true;
  193. }
  194. const char *DynSchema::GetExtensionName() {
  195. return this->GetName();
  196. }
  197. const char *DynSchema::GetExtensionURL() {
  198. return this->GetURL();
  199. }
  200. const char *DynSchema::GetExtensionTag() {
  201. return this->GetLogTag();
  202. }
  203. const char *DynSchema::GetExtensionAuthor() {
  204. return this->GetAuthor();
  205. }
  206. const char *DynSchema::GetExtensionVerString() {
  207. return this->GetVersion();
  208. }
  209. const char *DynSchema::GetExtensionDescription() {
  210. return this->GetDescription();
  211. }
  212. const char *DynSchema::GetExtensionDateString() {
  213. return this->GetDate();
  214. }