diff --git a/core/CoreConfig.cpp b/core/CoreConfig.cpp index f2b32f72b9..fbdf308d21 100644 --- a/core/CoreConfig.cpp +++ b/core/CoreConfig.cpp @@ -318,6 +318,284 @@ inline bool IsPathSepChar(char c) #endif } +void SM_ParseConfig(FILE *fp, ke::HashMap &cvars_list) +{ + char line[4096]; + char cvar_name[256]; + + while (fgets(line, sizeof(line), fp)) + { + char *ptr = line; + + // Ignore whitespaces and tabs + while (*ptr == ' ' || *ptr == '\t') + { + ptr++; + } + + // Ignore empty lines if found + if (*ptr == '\0' || *ptr == '\n' || *ptr == '\r') + { + continue; + } + + // Ignore comments + if (*ptr == '/' && *(ptr + 1) == '/') + { + continue; + } + + size_t i = 0; + while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r' && *ptr != ' ' && *ptr != '\t' && i < (sizeof(cvar_name) - 1)) + { + cvar_name[i++] = *ptr++; + } + + cvar_name[i] = '\0'; + + if (!i) + { + continue; + } + + auto p = cvars_list.findForAdd(cvar_name); + if (p.found()) + { + continue; + } + + // Remove spaces and tabs before the value + while (*ptr == ' ' || *ptr == '\t') + { + ptr++; + } + + char *value_start = ptr; + char *value_end = value_start + strlen(value_start); + + while (value_end > value_start && + (*(value_end - 1) == '\n' || + *(value_end - 1) == '\r' || + *(value_end - 1) == ' ' || + *(value_end - 1) == '\t' || + *(value_end - 1) == '"')) + { + value_end--; + } + + *value_end = '\0'; + + if (*value_start == '"') + { + value_start++; + } + + cvars_list.add(p, cvar_name, value_start); + } +} + +inline bool SM_CvarExistsInConfig(ke::HashMap &cvars, const char *cvar_name) +{ + return cvars.find(cvar_name).found(); +} + +bool SM_CvarExistsInPlugin(List &convars, const char *cvar_name) +{ + for (List::iterator iter = convars.begin(); iter != convars.end(); iter++) + { + const ConVar *cvar = (*iter); +#if SOURCE_ENGINE >= SE_ORANGEBOX + if (cvar->IsFlagSet(FCVAR_DONTRECORD)) +#else + if (cvar->IsBitSet(FCVAR_DONTRECORD)) +#endif + { + continue; + } + + if (strcasecmp(cvar_name, cvar->GetName()) == 0) + { + return true; + } + } + + return false; +} + +bool SM_ConfigCheckRegeneration( + const char *file, + ke::HashMap &existing_cvars, + List &convars) +{ + FILE *fp = fopen(file, "rt"); + if (!fp) + { + logger->LogError("Failed to read config for checking regeneration, make sure the directory has read permission."); + return true; + } + + SM_ParseConfig(fp, existing_cvars); + fclose(fp); + + /* #1. Check if the plugin's cvar list has a cvar that doesn't exist in the config file. */ + for (List::iterator iter = convars.begin(); iter != convars.end(); iter++) + { + const ConVar *cvar = (*iter); +#if SOURCE_ENGINE >= SE_ORANGEBOX + if (cvar->IsFlagSet(FCVAR_DONTRECORD)) +#else + if (cvar->IsBitSet(FCVAR_DONTRECORD)) +#endif + { + continue; + } + + if (!SM_CvarExistsInConfig(existing_cvars, cvar->GetName())) + { + return true; + } + } + + /* #2. Check if the config file has a cvar that doesn't exist in the plugin's cvar list. */ + for (auto it = existing_cvars.iter(); !it.empty(); it.next()) + { + if (!SM_CvarExistsInPlugin(convars, it->key.c_str())) + { + return true; + } + } + + return false; +} + +inline void SM_ExecuteConfigFile(const char *file) +{ + char cmd[255]; + ke::SafeSprintf(cmd, sizeof(cmd), "exec %s\n", file); + engine->ServerCommand(cmd); +} + +bool SM_GenerateConfigFile( + IPlugin *pl, + const char *file, + List &convars, + bool needs_regeneration = false, + ke::HashMap *existing_cvars = NULL) +{ + FILE *fp = NULL; + char tmp_file[PLATFORM_MAX_PATH]; + if (!needs_regeneration) + { + fp = fopen(file, "wt"); + if (!fp) + { + logger->LogError("Failed to auto generate config for %s, make sure the directory has write permission.", pl->GetFilename()); + return false; + } + } + else + { + /* Create a .tmp file with the new contents of the new config file and then override it to the existing file */ + ke::path::Format(tmp_file, sizeof(tmp_file), "%s.tmp", file); + fp = fopen(tmp_file, "wt"); + if (!fp) + { + logger->LogError("Failed to create temp config file for %s, make sure the directory has write permission.", pl->GetFilename()); + return false; + } + } + + fprintf(fp, "// This file was auto-generated by SourceMod (v%s)\n", SOURCEMOD_VERSION); + fprintf(fp, "// ConVars for plugin \"%s\"\n", pl->GetFilename()); + fprintf(fp, "\n\n"); + + float x; + for (List::iterator iter = convars.begin(); iter != convars.end(); iter++) + { + const ConVar *cvar = (*iter); +#if SOURCE_ENGINE >= SE_ORANGEBOX + if (cvar->IsFlagSet(FCVAR_DONTRECORD)) +#else + if (cvar->IsBitSet(FCVAR_DONTRECORD)) +#endif + { + continue; + } + + char descr[255]; + char *dptr = descr; + + /* Print comments until there is no more */ + ke::SafeStrcpy(descr, sizeof(descr), cvar->GetHelpText()); + while (*dptr != '\0') + { + /* Find the next line */ + char *next_ptr = dptr; + while (*next_ptr != '\0') + { + if (*next_ptr == '\n') + { + *next_ptr = '\0'; + next_ptr++; + break; + } + next_ptr++; + } + fprintf(fp, "// %s\n", dptr); + dptr = next_ptr; + } + + fprintf(fp, "// -\n"); + fprintf(fp, "// Default: \"%s\"\n", cvar->GetDefault()); + if (cvar->GetMin(x)) + { + fprintf(fp, "// Minimum: \"%02f\"\n", x); + } + if (cvar->GetMax(x)) + { + fprintf(fp, "// Maximum: \"%02f\"\n", x); + } + + const char *value = cvar->GetDefault(); + + if (needs_regeneration && existing_cvars) + { + auto p = existing_cvars->find(cvar->GetName()); + if (p.found()) + { + value = p->value.c_str(); + } + } + + fprintf(fp, "%s \"%s\"\n", cvar->GetName(), value); + fprintf(fp, "\n"); + } + fprintf(fp, "\n"); + + fclose(fp); + + if (needs_regeneration) + { +#ifdef PLATFORM_WINDOWS + if (!MoveFileEx(tmp_file, file, MOVEFILE_REPLACE_EXISTING)) +#else + if (rename(tmp_file, file) != 0) +#endif + { + /* Replacing the temp config file with the existing one failed, remove it and notify the user about it */ + logger->LogError("Failed to replace temp config file for %s, make sure the directory has write permission.", pl->GetFilename()); + if (remove(tmp_file) != 0) + { + logger->LogError("Failed to remove temp config file for %s.", pl->GetFilename()); + } + + return false; + } + } + + return true; +} + bool SM_ExecuteConfig(IPlugin *pl, AutoConfig *cfg, bool can_create) { bool will_create = false; @@ -394,90 +672,62 @@ bool SM_ExecuteConfig(IPlugin *pl, AutoConfig *cfg, bool can_create) g_SourceMod.BuildPath(Path_Game, file, sizeof(file), "cfg/%s", local); bool file_exists = ke::file::IsFile(file); - if (!file_exists && will_create) + + if (!will_create) { - List *convars = NULL; - if (pl->GetProperty("ConVarList", (void **)&convars, false) && convars) + if (file_exists) { - /* Attempt to create it */ - FILE *fp = fopen(file, "wt"); - if (fp) - { - fprintf(fp, "// This file was auto-generated by SourceMod (v%s)\n", SOURCEMOD_VERSION); - fprintf(fp, "// ConVars for plugin \"%s\"\n", pl->GetFilename()); - fprintf(fp, "\n\n"); - - List::iterator iter; - float x; - for (iter = convars->begin(); iter != convars->end(); iter++) - { - const ConVar *cvar = (*iter); -#if SOURCE_ENGINE >= SE_ORANGEBOX - if (cvar->IsFlagSet(FCVAR_DONTRECORD)) -#else - if (cvar->IsBitSet(FCVAR_DONTRECORD)) -#endif - { - continue; - } + SM_ExecuteConfigFile(local); + } - char descr[255]; - char *dptr = descr; + return can_create; + } - /* Print comments until there is no more */ - ke::SafeStrcpy(descr, sizeof(descr), cvar->GetHelpText()); - while (*dptr != '\0') - { - /* Find the next line */ - char *next_ptr = dptr; - while (*next_ptr != '\0') - { - if (*next_ptr == '\n') - { - *next_ptr = '\0'; - next_ptr++; - break; - } - next_ptr++; - } - fprintf(fp, "// %s\n", dptr); - dptr = next_ptr; - } + List *convars = NULL; + if (!pl->GetProperty("ConVarList", (void **)&convars, false) || !convars) + { + if (file_exists) + { + SM_ExecuteConfigFile(local); + } - fprintf(fp, "// -\n"); - fprintf(fp, "// Default: \"%s\"\n", cvar->GetDefault()); - if (cvar->GetMin(x)) - { - fprintf(fp, "// Minimum: \"%02f\"\n", x); - } - if (cvar->GetMax(x)) - { - fprintf(fp, "// Maximum: \"%02f\"\n", x); - } - fprintf(fp, "%s \"%s\"\n", cvar->GetName(), cvar->GetDefault()); - fprintf(fp, "\n"); - } - fprintf(fp, "\n"); + return can_create; + } - file_exists = true; - can_create = false; - fclose(fp); - } - else - { - logger->LogError("Failed to auto generate config for %s, make sure the directory has write permission.", pl->GetFilename()); - return can_create; - } + if (!file_exists) + { + /* If file doesn't exist, then automatically generate the config file and execute it. */ + /* If sourcemod somehow failed to generate the config file, then stop. */ + if (!SM_GenerateConfigFile(pl, file, *convars)) + { + return can_create; } + + SM_ExecuteConfigFile(local); + can_create = false; + return can_create; } - if (file_exists) + /* Now if the file exists, read the content of it to see if we need to regenerate it or not. */ + ke::HashMap existing_cvars; + existing_cvars.init(); + if (!SM_ConfigCheckRegeneration(file, existing_cvars, *convars)) { - char cmd[255]; - ke::SafeSprintf(cmd, sizeof(cmd), "exec %s\n", local); - engine->ServerCommand(cmd); + SM_ExecuteConfigFile(local); + return can_create; + } + + /* File exists and misses cvars between the config file and the plugin's convars list + * Create a temporary config file with the new contents and then override it to the existing file. + * If sourcemod somehow failed to override the new contents to the existing config file, then execute the existing config file. + */ + can_create = false; + if (!SM_GenerateConfigFile(pl, file, *convars, true, &existing_cvars)) + { + logger->LogError("An error occurred while regenerating config file for %s, executing the existing file instead.", pl->GetFilename()); } + SM_ExecuteConfigFile(local); return can_create; } diff --git a/core/CoreConfig.h b/core/CoreConfig.h index f722fa4251..9a53ec66a1 100644 --- a/core/CoreConfig.h +++ b/core/CoreConfig.h @@ -71,6 +71,19 @@ class CoreConfig : StringHashMap m_KeyValues; }; +struct StringPolicy +{ + static inline uint32_t hash(const std::string &key) + { + std::string lower = ke::Lowercase(key.c_str()); + return ke::FastHashCharSequence(lower.c_str(), lower.length()); + } + static inline bool matches(const char *find, const std::string &key) + { + return strcasecmp(find, key.c_str()) == 0; + } +}; + extern bool SM_AreConfigsExecuted(); extern void SM_ExecuteAllConfigs(); extern void SM_ExecuteForPlugin(IPluginContext *ctx);