Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
358 changes: 283 additions & 75 deletions core/CoreConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,251 @@ inline bool IsPathSepChar(char c)
#endif
}

void SM_ParseConfig(FILE *fp, ke::HashMap<std::string, std::string, StringPolicy> &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<std::string, std::string, StringPolicy> &cvars, const char *cvar_name)
{
return cvars.find(cvar_name).found();
}

bool SM_CvarExistsInPlugin(List<const ConVar *> &convars, const char *cvar_name)
{
for (List<const ConVar *>::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 (strcmp(cvar_name, cvar->GetName()) == 0)
{
return true;
}
}

return false;
}

bool SM_ConfigCheckRegeneration(
const char *file,
ke::HashMap<std::string, std::string, StringPolicy> &existing_cvars,
List<const ConVar *> &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
* If not, then let sourcemod remove the existing config file and generate a new one
*/
for (List<const ConVar *>::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
* If not, then let sourcemod remove the existing config file and generate a new one
*/
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);
}

void SM_GenerateConfigFile(
IPlugin *pl,
const char *file,
List<const ConVar *> &convars,
bool needs_regeneration = false,
ke::HashMap<std::string, std::string, StringPolicy> *existing_cvars = NULL)
{
FILE *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;
}

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<const ConVar *>::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);
}

bool SM_ExecuteConfig(IPlugin *pl, AutoConfig *cfg, bool can_create)
{
bool will_create = false;
Expand Down Expand Up @@ -394,90 +639,53 @@ 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)
{
List<const ConVar *> *convars = NULL;
if (pl->GetProperty("ConVarList", (void **)&convars, false) && convars)
{
/* 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<const ConVar *>::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;
}

char descr[255];
char *dptr = descr;
if (!will_create)
{
SM_ExecuteConfigFile(local);
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<const ConVar *> *convars = NULL;
if (!pl->GetProperty("ConVarList", (void **)&convars, false) || !convars)
{
SM_ExecuteConfigFile(local);
return can_create;
}

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");
if (!file_exists)
{
/* If file doesn't exist, then automatically generate the config file and execute it */
SM_GenerateConfigFile(pl, file, *convars);
SM_ExecuteConfigFile(local);
can_create = false;
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;
}
}
/*
* Now if the file exists, read the content of it to see if we need to regenerate it or not
* We need to remove the file before regenerating it.
*/
ke::HashMap<std::string, std::string, StringPolicy> existing_cvars;
existing_cvars.init();
if (!SM_ConfigCheckRegeneration(file, existing_cvars, *convars))
{
SM_ExecuteConfigFile(local);
return can_create;
}

if (file_exists)
/* File exists and misses cvars between the config file and the plugin's convars list
* Remove the existing config file and generate a new one with the existing values in the old config file
*/
if (remove(file) != 0)
{
char cmd[255];
ke::SafeSprintf(cmd, sizeof(cmd), "exec %s\n", local);
engine->ServerCommand(cmd);
logger->LogError("Failed to remove old config file for %s, make sure the directory has write permission.", pl->GetFilename());
return can_create;
}

SM_GenerateConfigFile(pl, file, *convars, true, &existing_cvars);
SM_ExecuteConfigFile(local);
can_create = false;
return can_create;
}

Expand Down
12 changes: 12 additions & 0 deletions core/CoreConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ class CoreConfig :
StringHashMap<std::string> m_KeyValues;
};

struct StringPolicy
{
static inline uint32_t hash(const std::string &key)
{
return ke::FastHashCharSequence(key.c_str(), key.length());
}
static inline bool matches(const char *find, const std::string &key)
{
return key.compare(find) == 0;
}
};

extern bool SM_AreConfigsExecuted();
extern void SM_ExecuteAllConfigs();
extern void SM_ExecuteForPlugin(IPluginContext *ctx);
Expand Down