diff --git a/core/CTextParsers.cpp b/core/CTextParsers.cpp index 34f247bf..7f922eef 100644 --- a/core/CTextParsers.cpp +++ b/core/CTextParsers.cpp @@ -587,6 +587,8 @@ failed: *line = curline; } + smc->ReadSMC_ParseEnd(true, (err == SMCParse_Custom)); + if (col) { *col = curtok; diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj index 6fe6cae6..0a482bdd 100644 --- a/core/msvc8/sourcemod_mm.vcproj +++ b/core/msvc8/sourcemod_mm.vcproj @@ -41,7 +41,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="..\interfaces;..\;..\systems;..\..\sourcepawn\include" - PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;SOURCEMOD_MM_EXPORTS;_CRT_SECURE_NO_DEPRECATE" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;SOURCEMOD_MM_EXPORTS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE" MinimalRebuild="true" BasicRuntimeChecks="3" RuntimeLibrary="1" @@ -118,7 +118,7 @@ + + @@ -204,6 +208,10 @@ RelativePath="..\sm_globals.h" > + + @@ -247,6 +255,10 @@ RelativePath="..\systems\LibrarySys.h" > + + @@ -267,6 +279,10 @@ RelativePath="..\systems\LibrarySys.cpp" > + + diff --git a/core/sm_memtable.cpp b/core/sm_memtable.cpp new file mode 100644 index 00000000..4396eeb0 --- /dev/null +++ b/core/sm_memtable.cpp @@ -0,0 +1,85 @@ +#include +#include +#include "sm_memtable.h" + +BaseMemTable::BaseMemTable(unsigned int init_size) +{ + membase = (unsigned char *)malloc(init_size); + size = init_size; +} + +BaseMemTable::~BaseMemTable() +{ + free(membase); + membase = NULL; +} + +int BaseMemTable::CreateMem(unsigned int addsize, void **addr) +{ + int idx = (int)tail; + + if (idx < 0) + { + return -1; + } + + while (tail + addsize >= size) + { + size *= 2; + membase = (unsigned char *)realloc(membase, size); + } + + tail += addsize; + + if (addr) + { + *addr = (void *)&membase[idx]; + } + + return idx; +} + +void *BaseMemTable::GetAddress(int index) +{ + if (index < 0 || (unsigned int)index >= tail) + { + return NULL; + } + + return &membase[index]; +} + +void BaseMemTable::Reset() +{ + tail = 0; +} + +BaseStringTable::BaseStringTable(unsigned int init_size) : m_table(init_size) +{ +} + +BaseStringTable::~BaseStringTable() +{ +} + +int BaseStringTable::AddString(const char *string) +{ + size_t len = strlen(string) + 1; + int idx; + char *addr; + + idx = m_table.CreateMem(len, (void **)&addr); + strcpy(addr, string); + + return idx; +} + +const char *BaseStringTable::GetString(int str) +{ + return (const char *)m_table.GetAddress(str); +} + +void BaseStringTable::Reset() +{ + m_table.Reset(); +} diff --git a/core/sm_memtable.h b/core/sm_memtable.h new file mode 100644 index 00000000..0fe631a9 --- /dev/null +++ b/core/sm_memtable.h @@ -0,0 +1,69 @@ +#ifndef _INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ +#define _INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ + +class BaseMemTable +{ +public: + BaseMemTable(unsigned int init_size); + ~BaseMemTable(); +public: + /** + * Allocates 'size' bytes of memory. + * Optionally outputs the address through 'addr'. + * Returns an index >= 0 on success, < 0 on failure. + */ + int CreateMem(unsigned int size, void **addr); + + /** + * Given an index into the memory table, returns its address. + * Returns NULL if invalid. + */ + void *GetAddress(int index); + + /** + * Scraps the memory table. For caching purposes, the memory + * is not freed, however subsequent calls to CreateMem() will + * begin at the first index again. + */ + void Reset(); +private: + unsigned char *membase; + unsigned int size; + unsigned int tail; +}; + +class BaseStringTable +{ +public: + BaseStringTable(unsigned int init_size); + ~BaseStringTable(); +public: + /** + * Adds a string to the string table and returns its index. + */ + int AddString(const char *string); + + /** + * Given an index into the string table, returns the associated string. + */ + const char *GetString(int str); + + /** + * Scraps the string table. For caching purposes, the memory + * is not freed, however subsequent calls to AddString() will + * begin at the first index again. + */ + void Reset(); + + /** + * Returns the parent BaseMemTable that this string table uses. + */ + inline BaseMemTable *GetMemTable() + { + return &m_table; + } +private: + BaseMemTable m_table; +}; + +#endif //_INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ diff --git a/core/sm_platform.h b/core/sm_platform.h index 5e7e1962..1809e908 100644 --- a/core/sm_platform.h +++ b/core/sm_platform.h @@ -13,6 +13,7 @@ #if !defined snprintf #define snprintf _snprintf #endif +#define strcasecmp strcmpi #include #include #define PLATFORM_LIB_EXT "dll" diff --git a/core/systems/PluginInfoDatabase.cpp b/core/systems/PluginInfoDatabase.cpp new file mode 100644 index 00000000..03e43733 --- /dev/null +++ b/core/systems/PluginInfoDatabase.cpp @@ -0,0 +1,266 @@ +#include +#include +#include "PluginInfoDatabase.h" +#include "PluginSys.h" + +void PluginSettings::Init() +{ + name = -1; + pause_val = false; + type_val = PluginType_MapUpdated; + optarray = -1; + opts_num = 0; + opts_size = 0; +} + +/** + * :TODO: write the logger, make these errors log instead of being static + * NOTE: once we do that, we will have to change some code to ignore sections + */ + +CPluginInfoDatabase::CPluginInfoDatabase() +{ + m_strtab = NULL; + m_infodb_count = 0; + m_infodb_size = 0; + m_infodb = -1; +} + +CPluginInfoDatabase::~CPluginInfoDatabase() +{ + delete m_strtab; +} + +void CPluginInfoDatabase::ReadSMC_ParseStart() +{ + /* Create or reset our string table */ + if (m_strtab) + { + m_strtab->Reset(); + } else { + m_strtab = new BaseStringTable(1024); + } + + /* Set our internal states to the beginning */ + in_plugins = false; + cur_plugin = -1; + in_options = false; +} + +SMCParseResult CPluginInfoDatabase::MakeError(const char *fmt, ...) +{ + char buffer[512]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + m_errmsg = m_strtab->AddString(buffer); + + return SMCParse_HaltFail; +} + +unsigned int CPluginInfoDatabase::GetSettingsNum() +{ + return m_infodb_count; +} + +PluginSettings *CPluginInfoDatabase::GetSettingsIfMatch(unsigned int index, const char *filename) +{ + BaseMemTable *memtab = m_strtab->GetMemTable(); + PluginSettings *table = (PluginSettings *)memtab->GetAddress(m_infodb); + + if (!table || index >= m_infodb_count) + { + return NULL; + } + + const char *name = m_strtab->GetString(table[index].name); + + if (!name) + { + return NULL; + } + + if (!g_PluginMngr.TestAliasMatch(name, filename)) + { + return NULL; + } + + return &table[index]; +} + +void CPluginInfoDatabase::GetOptionsForPlugin(PluginSettings *settings, unsigned int opt_num, const char **key, const char **val) +{ + PluginOpts *table = (PluginOpts *)m_strtab->GetMemTable()->GetAddress(settings->optarray); + if (!table) + { + *key = NULL; + *val = NULL; + return; + } + + if (opt_num >= settings->opts_num) + { + *key = NULL; + *val = NULL; + return; + } + + *key = m_strtab->GetString(table[opt_num].key); + *val = m_strtab->GetString(table[opt_num].val); +} + +SMCParseResult CPluginInfoDatabase::ReadSMC_KeyValue(const char *key, + const char *value, + bool key_quotes, + bool value_quotes) +{ + if (cur_plugin != -1) + { + PluginSettings *plugin = (PluginSettings *)m_strtab->GetMemTable()->GetAddress(cur_plugin); + if (!in_options) + { + if (strcmp(key, "pause") == 0) + { + if (strcasecmp(value, "yes") == 0) + { + plugin->pause_val = true; + } else { + plugin->pause_val = false; + } + } else if (strcmp(key, "lifetime") == 0) { + if (strcasecmp(value, "private") == 0) + { + plugin->type_val = PluginType_Private; + } else if (strcasecmp(value, "mapsync") == 0) { + plugin->type_val = PluginType_MapUpdated; + } else if (strcasecmp(value, "maponly") == 0) { + plugin->type_val = PluginType_MapOnly; + } else if (strcasecmp(value, "global") == 0) { + plugin->type_val = PluginType_Global; + } else { + return MakeError("Unknown value for key \"lifetime\": \"%s\"", value); + } + } else { + return MakeError("Unknown property key: \"%s\"", key); + } + } else { + /* Cache every option, valid or not */ + int keyidx = m_strtab->AddString(key); + int validx = m_strtab->AddString(value); + PluginOpts *table; + BaseMemTable *memtab = m_strtab->GetMemTable(); + if (plugin->opts_num + 1 > plugin->opts_size) + { + unsigned int oldsize = plugin->opts_size; + if (oldsize == 0) + { + //right now we don't have many + plugin->opts_size = 2; + } else { + plugin->opts_size *= 2; + } + int newidx = memtab->CreateMem(plugin->opts_size * sizeof(PluginOpts), (void **)&table); + if (plugin->optarray != -1) + { + void *oldtable = memtab->GetAddress(plugin->optarray); + memcpy(table, oldtable, oldsize * sizeof(PluginOpts)); + } + plugin->optarray = newidx; + } else { + table = (PluginOpts *)memtab->GetAddress(plugin->optarray); + } + PluginOpts *opt = &table[plugin->opts_num++]; + opt->key = keyidx; + opt->val = validx; + } + } else if (in_plugins) { + return MakeError("Unknown property key: \"%s\"", key); + } else { + /* Ignore anything we don't know about! */ + } + + return SMCParse_Continue; +} + +SMCParseResult CPluginInfoDatabase::ReadSMC_LeavingSection() +{ + if (in_plugins) + { + if (cur_plugin == -1) + { + if (in_options) + { + in_options = false; + } else { + /* If the plugin is ending, add it to the table */ + BaseMemTable *memtab = m_strtab->GetMemTable(); + int *table; + if (m_infodb_count + 1 > m_infodb_size) + { + unsigned int oldsize = m_infodb_size; + if (!m_infodb_size) + { + m_infodb_size = 8; + } else { + m_infodb_size *= 2; + } + int newidx = memtab->CreateMem(m_infodb_size, (void **)&table); + if (m_infodb != -1) + { + void *oldtable = (int *)memtab->GetAddress(m_infodb); + memcpy(table, oldtable, oldsize * sizeof(int)); + } + m_infodb = newidx; + } else { + table = (int *)memtab->GetAddress(m_infodb); + } + /* Assign to table and scrap the current plugin */ + table[m_infodb_count++] = cur_plugin; + cur_plugin = -1; + } + } else { + in_plugins = false; + } + } + + return SMCParse_Continue; +} + +SMCParseResult CPluginInfoDatabase::ReadSMC_NewSection(const char *name, bool opt_quotes) +{ + if (!in_plugins) + { + /* If we're not in the main Plugins section, and we don't get it for the name, error out */ + if (strcmp(name, "Plugins") != 0) + { + return MakeError("Unknown root section: \"%s\"", name); + } else { + /* Otherwise set our states */ + in_plugins = true; + cur_plugin = -1; + in_options = false; + } + } else { + if (cur_plugin == -1) + { + /* If we get a plugin node and we don't have a current plugin, create a new one */ + PluginSettings *plugin; + cur_plugin = m_strtab->GetMemTable()->CreateMem(sizeof(PluginSettings), (void **)&plugin); + plugin->Init(); + plugin->name = m_strtab->AddString(name); + in_options = false; + } else { + if (!in_options && strcmp(name, "Options") == 0) + { + in_options = true; + } else { + return MakeError("Unknown plugin sub-section: \"%s\"", name); + } + } + } + + return SMCParse_Continue; +} diff --git a/core/systems/PluginInfoDatabase.h b/core/systems/PluginInfoDatabase.h new file mode 100644 index 00000000..572bb7b7 --- /dev/null +++ b/core/systems/PluginInfoDatabase.h @@ -0,0 +1,74 @@ +#ifndef _INCLUDE_SOURCEMOD_CORE_SYSTEM_PLUGININFODATABASE_H_ +#define _INCLUDE_SOURCEMOD_CORE_SYSTEM_PLUGININFODATABASE_H_ + +/** + * This file parses plugin_settings.cfg and stores the information in cached memory. + * It provides simplistic abstraction to retrieving the info. + * :TODO: currently untested + */ + +#include "sm_memtable.h" +#include "ITextParsers.h" +#include "IPluginSys.h" +#include "sm_globals.h" + +struct PluginOpts +{ + int key; + int val; +}; + +struct PluginSettings +{ + void Init(); + int name; + bool pause_val; + PluginType type_val; + int optarray; + size_t opts_num; + size_t opts_size; +}; + +class CPluginInfoDatabase : public ITextListener_SMC +{ +public: + CPluginInfoDatabase(); + ~CPluginInfoDatabase(); +public: //ITextListener_SMC + void ReadSMC_ParseStart(); + SMCParseResult ReadSMC_NewSection(const char *name, bool opt_quotes); + SMCParseResult ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes); + SMCParseResult ReadSMC_LeavingSection(); +public: + /** + * Returns the number of plugin settings available. + */ + unsigned int GetSettingsNum(); + + /** + * Given an index, returns the plugin settings block if the filename matches. + * Otherwise, returns NULL. + */ + PluginSettings *GetSettingsIfMatch(unsigned int index, const char *filename); + + /** + * Given a plugin settings struct and an index, + * returns the given JIT key/value option pair at that index. + * If the input is invalid, key and val will be set to NULL. + */ + void GetOptionsForPlugin(PluginSettings *settings, unsigned int opt_num, const char **key, const char **val); +private: + SMCParseResult MakeError(const char *fmt, ...); +private: + BaseStringTable *m_strtab; + int m_errmsg; + bool in_plugins; + bool in_options; + unsigned int m_infodb; + size_t m_infodb_count; + size_t m_infodb_size; + int cur_plugin; +}; + + +#endif //_INCLUDE_SOURCEMOD_CORE_SYSTEM_PLUGININFODATABASE_H_ diff --git a/core/systems/PluginSys.cpp b/core/systems/PluginSys.cpp index 2a374f35..7b4769ec 100644 --- a/core/systems/PluginSys.cpp +++ b/core/systems/PluginSys.cpp @@ -2,6 +2,7 @@ #include "PluginSys.h" #include "LibrarySys.h" #include "sourcemm_api.h" +#include "CTextParsers.h" CPluginManager g_PluginMngr; @@ -288,9 +289,19 @@ void CPluginManager::CPluginIterator::Reset() * PLUGIN MANAGER * ******************/ -void CPluginManager::RefreshOrLoadPlugins(const char *basedir) +void CPluginManager::RefreshOrLoadPlugins(const char *config, const char *basedir) { - IDirectory *dir = g_LibSys.OpenDirectory(basedir); + /* First read in the database of plugin settings */ + SMCParseError err; + unsigned int line, col; + if ((err=g_TextParse.ParseFile_SMC(config, &m_PluginInfo, &line, &col)) != SMCParse_Okay) + { + /* :TODO: log the error, don't bail out though */ + } + + + //:TODO: move this to a separate recursive function and do stuff + /*IDirectory *dir = g_LibSys.OpenDirectory(basedir); while (dir->MoreFiles()) { if (dir->IsEntryDirectory() && (strcmp(dir->GetEntryName(), "disabled") != 0)) @@ -300,7 +311,7 @@ void CPluginManager::RefreshOrLoadPlugins(const char *basedir) RefreshOrLoadPlugins(basedir); } } - g_LibSys.CloseDirectory(dir); + g_LibSys.CloseDirectory(dir);*/ } IPlugin *CPluginManager::LoadPlugin(const char *path, bool debug, PluginType type, char error[], size_t err_max) @@ -446,6 +457,215 @@ CFunction *CPluginManager::GetFunctionFromPool(funcid_t f, CPlugin *plugin) } } +bool CPluginManager::TestAliasMatch(const char *alias, const char *localpath) +{ + /* As an optimization, we do not call strlen, but compute the length in the first pass */ + size_t alias_len = 0; + size_t local_len = 0; + + const char *ptr = alias; + unsigned int alias_explicit_paths = 0; + unsigned int alias_path_end = 0; + while (*ptr != '\0') + { + if (*ptr == '\\' || *ptr == '/') + { + alias_explicit_paths++; + alias_path_end = alias_len; + } + alias_len++; + ptr++; + } + + if (alias_path_end == alias_len - 1) + { + /* Trailing slash is totally invalid here */ + return false; + } + + ptr = localpath; + unsigned int local_explicit_paths = 0; + unsigned int local_path_end = 0; + while (*ptr != '\0') + { + if (*ptr == '\\' || *ptr == '/') + { + local_explicit_paths++; + local_path_end = local_len; + } + local_len++; + ptr++; + } + + /* If the alias has more explicit paths than the real path, + * no match will be possible. + */ + if (alias_explicit_paths > local_explicit_paths) + { + return false; + } + + if (alias_explicit_paths) + { + /* We need to find if the paths match now. For example, these should all match: + * csdm csdm + * csdm optional/csdm + * csdm/ban optional/crab/csdm/ban + */ + const char *aliasptr = alias; + const char *localptr = localpath; + bool match = true; + do + { + if (*aliasptr != *localptr) + { + /* We have to knock one path off */ + local_explicit_paths--; + if (alias_explicit_paths > local_explicit_paths) + { + /* Skip out if we're gonna have an impossible match */ + return false; + } + /* Eat up localptr tokens until we get a result */ + while (((localptr - localpath) < (int)local_path_end) + && *localptr != '/' + && *localptr != '\\') + { + localptr++; + } + /* Check if we hit the end of our searchable area. + * This probably isn't possible because of the path + * count check, but it's a good idea anyway. + */ + if ((localptr - localpath) >= (int)local_path_end) + { + return false; + } else { + /* Consume the slash token */ + localptr++; + } + /* Reset the alias pointer so we can continue consuming */ + aliasptr = alias; + match = false; + continue; + } + /* Note: + * This is safe because if localptr terminates early, aliasptr will too + */ + do + { + /* We should never reach the end of the string because of this check. */ + bool aliasend = (aliasptr - alias) > (int)alias_path_end; + bool localend = (localptr - localpath) > (int)local_path_end; + if (aliasend || localend) + { + if (aliasend && localend) + { + /* we matched, and we can break out now */ + match = true; + break; + } + /* Otherwise, we've hit the end somehow and rest won't match up. Break out. */ + match = false; + break; + } + + /* If we got here, it's safe to compare the next two tokens */ + if (*localptr != *aliasptr) + { + match = false; + break; + } + localptr++; + aliasptr++; + } while (true); + } while (!match); + } + + /* If we got here, it's time to compare filenames */ + const char *aliasptr = alias; + const char *localptr = localpath; + + if (alias_explicit_paths) + { + aliasptr = &alias[alias_path_end + 1]; + } + + if (local_explicit_paths) + { + localptr = &localpath[local_path_end + 1]; + } + + while (true) + { + if (*aliasptr == '*') + { + /* First, see if this is the last character */ + if (aliasptr - alias == alias_len - 1) + { + /* If so, there's no need to match anything else */ + return true; + } + /* Otherwise, we need to search for an appropriate matching sequence in local. + * Note that we only need to search up to the next asterisk. + */ + aliasptr++; + bool match = true; + const char *local_orig = localptr; + do + { + match = true; + while (*aliasptr != '\0' && *aliasptr != '*') + { + /* Since aliasptr is never '\0', localptr hitting the end will fail */ + if (*aliasptr != *localptr) + { + match = false; + break; + } + aliasptr++; + localptr++; + } + if (!match) + { + /* If we didn't get a match, we need to advance the search stream. + * This will let us skip tokens while still searching for another match. + */ + localptr = ++local_orig; + /* Make sure we don't go out of bounds */ + if (*localptr == '\0') + { + break; + } + } + } while (!match); + + if (!match) + { + return false; + } else { + /* If we got a match, move on to the next token */ + continue; + } + } else if (*aliasptr == '\0') { + if (*localptr == '\0' + || + strcmp(localptr, ".smx") == 0) + { + return true; + } else { + return false; + } + } else if (*aliasptr != *localptr) { + return false; + } + aliasptr++; + localptr++; + } + + return true; +} + CPluginManager::~CPluginManager() { //:TODO: we need a good way to free what we're holding diff --git a/core/systems/PluginSys.h b/core/systems/PluginSys.h index 51dd8fd8..ced80618 100644 --- a/core/systems/PluginSys.h +++ b/core/systems/PluginSys.h @@ -6,6 +6,7 @@ #include #include "sm_globals.h" #include "CFunction.h" +#include "PluginInfoDatabase.h" using namespace SourceHook; @@ -60,6 +61,13 @@ private: CFunction **m_pub_funcs; }; +struct PluginDBInfo +{ + bool pause; + PluginType lifetime; + +}; + class CPluginManager : public IPluginManager { friend class CPlugin; @@ -67,6 +75,7 @@ public: CPluginManager(); ~CPluginManager(); public: + /* Implements iterator class */ class CPluginIterator : public IPluginIterator { public: @@ -83,7 +92,7 @@ public: List::iterator current; }; friend class CPluginManager::CPluginIterator; -public: +public: //IPluginManager virtual IPlugin *LoadPlugin(const char *path, bool debug, PluginType type, @@ -96,7 +105,21 @@ public: virtual void AddPluginsListener(IPluginsListener *listener); virtual void RemovePluginsListener(IPluginsListener *listener); public: - virtual void RefreshOrLoadPlugins(const char *basedir); + /** + * Refreshes and loads plugins, usually used on mapchange + */ + virtual void RefreshOrLoadPlugins(const char *config, const char *basedir); + + /** + * Tests a plugin file mask against a local folder. + * The alias is searched backwards from localdir - i.e., given this input: + * csdm/ban csdm/ban + * ban csdm/ban + * csdm/ban optional/csdm/ban + * All of these will return true for an alias match. + * Wildcards are allowed in the filename. + */ + virtual bool TestAliasMatch(const char *alias, const char *localdir); protected: void ReleaseIterator(CPluginIterator *iter); CFunction *GetFunctionFromPool(funcid_t f, CPlugin *plugin); @@ -106,6 +129,7 @@ private: List m_plugins; CStack m_iters; CStack m_funcpool; + CPluginInfoDatabase m_PluginInfo; }; extern CPluginManager g_PluginMngr;