/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cvar.c -- dynamic variable tracking #include "quakedef.h" #include "shader.h" cvar_group_t *cvar_groups; //static bucket_t *cvar_buckets[1024]; //static hashtable_t cvar_hash; typedef struct { size_t maxentries; size_t numentries; struct { const char *string; void *data; } entry[1]; } abucket_t; typedef struct { abucket_t **bucket; unsigned int numbuckets; } ahashtable_t; //not thread-safe static abucket_t *cvar_buckets[1024]; static ahashtable_t cvar_hash = {cvar_buckets, countof(cvar_buckets)}; unsigned int Hash_KeyInsensitive(const char *name, unsigned int modulus); void *AHash_GetInsensitive(ahashtable_t *table, const char *name) { abucket_t *b = table->bucket[Hash_KeyInsensitive(name, table->numbuckets)]; size_t i; if (b) for (i = 0; i < b->numentries; i++) { if (!strcasecmp(b->entry[i].string, name)) return b->entry[i].data; } return NULL; } void AHash_RemoveDataInsensitive(ahashtable_t *table, const char *name, void *data) { abucket_t *b = table->bucket[Hash_KeyInsensitive(name, table->numbuckets)]; size_t i; for (i = 0; i < b->numentries; i++) { if (b->entry[i].data == data && !strcasecmp(b->entry[i].string, name)) { //strip it. b->numentries--; //shift everything down. if (b->numentries > i) memmove(&b->entry[i], &b->entry[i+1], sizeof(*b->entry)*(b->numentries-i)); break; } } } void AHash_AddInsensitive(ahashtable_t *table, const char *name, void *data) { unsigned int idx = Hash_KeyInsensitive(name, table->numbuckets); abucket_t *b = table->bucket[idx]; if (!b) { //nothing there!... b = table->bucket[idx] = BZ_Malloc(sizeof(*b)); b->numentries = 0; b->maxentries = countof(b->entry); } else if (b->numentries == b->maxentries) { //can't add anything new size_t n = b->maxentries*2; table->bucket[idx] = BZ_Malloc(sizeof(*b)-sizeof(b->entry) + sizeof(b->entry)*n); memcpy(table->bucket[idx]->entry, b->entry, sizeof(b->entry[0])*b->numentries); table->bucket[idx]->numentries = b->numentries; table->bucket[idx]->maxentries = n; BZ_Free(b); b = table->bucket[idx]; } b->entry[b->numentries].data = data; b->entry[b->numentries].string = name; b->numentries++; } void AHash_Cleanup(ahashtable_t *table) { size_t i; for (i = 0; i < table->numbuckets; i++) { if (table->bucket[i] && !table->bucket[i]->numentries) { BZ_Free(table->bucket[i]); table->bucket[i] = NULL; } } } int cvar_watched; //cvar_t *cvar_vars; static char *cvar_null_string = ""; static char *cvar_zero_string = "0"; static char *cvar_one_string = "1"; static char *Cvar_DefaultAlloc(char *str) { char *c; if (str[0] == '\0') return cvar_null_string; if (str[0] == '0' && str[1] == '\0') return cvar_zero_string; if (str[0] == '1' && str[1] == '\0') return cvar_one_string; c = (char *)Z_Malloc(strlen(str)+1); Q_strcpy(c, str); return c; } void Cvar_DefaultFree(char *str) { if (str == cvar_null_string) return; else if (str == cvar_zero_string) return; else if (str == cvar_one_string) return; else Z_Free(str); } /* ============ Cvar_FindVar ============ */ cvar_t *Cvar_FindVar (const char *var_name) { return AHash_GetInsensitive(&cvar_hash, var_name); /* cvar_group_t *grp; cvar_t *var; for (grp=cvar_groups ; grp ; grp=grp->next) for (var=grp->cvars ; var ; var=var->next) { if (!Q_strcasecmp (var_name, var->name)) return var; if (var->name2 && !Q_strcasecmp (var_name, var->name2)) return var; } */ return NULL; } static cvar_group_t *Cvar_FindGroup (const char *group_name) { cvar_group_t *grp; for (grp=cvar_groups ; grp ; grp=grp->next) if (!Q_strcasecmp (group_name, grp->name)) return grp; return NULL; } static cvar_group_t *Cvar_GetGroup(const char *gname) { cvar_group_t *g; if (!gname) gname = "Miscilaneous vars"; g = Cvar_FindGroup(gname); if (g) return g; g = (cvar_group_t*)Z_Malloc(sizeof(cvar_group_t) + strlen(gname)+1); g->name = (char*)(g+1); strcpy((char*)g->name, gname); g->next = NULL; g->next = cvar_groups; cvar_groups = g; return g; } // converts a given single cvar flag into a human readable string static char *Cvar_FlagToName(int flag) { switch (flag) { case CVAR_ARCHIVE: return "archive"; case CVAR_USERINFO: return "userinfo"; case CVAR_SERVERINFO: return "serverinfo"; case CVAR_NOSET: return "noset"; case CVAR_MAPLATCH: return "latch"; case CVAR_POINTER: return "pointer"; case CVAR_NOTFROMSERVER: return "noserver"; case CVAR_USERCREATED: return "userset"; case CVAR_CHEAT: return "cheat"; case CVAR_SEMICHEAT: return "semicheat"; case CVAR_RENDERERLATCH: return "renderlatch"; case CVAR_VIDEOLATCH: return "videolatch"; case CVAR_SERVEROVERRIDE: return "serverlatch"; case CVAR_RENDERERCALLBACK: return "rendercallback"; case CVAR_NOUNSAFEEXPAND: return "nounsafeexpand"; case CVAR_RULESETLATCH: return "rulesetlatch"; case CVAR_SHADERSYSTEM: return "shadersystem"; case CVAR_NOSAVE: return "nosave"; case CVAR_TELLGAMECODE: return "autocvar"; case CVAR_CONFIGDEFAULT: return ""; case CVAR_NORESET: return "noreset"; case CVAR_RENDEREROVERRIDE: return "rendereroverride"; } return NULL; } //lists commands, also prints restriction level #define CLF_RAW 0x1 #define CLF_LEVEL 0x2 #define CLF_ALTNAME 0x4 #define CLF_VALUES 0x8 #define CLF_DEFAULT 0x10 #define CLF_LATCHES 0x20 #define CLF_FLAGS 0x40 #define CLF_FLAGMASK 0x80 #define CLF_CHANGEDONLY 0x100 void Cvar_List_f (void) { cvar_group_t *grp; cvar_t *cmd; char *var, *search, *gsearch; int gnum, i, num = 0; int listflags = 0, cvarflags = 0; int total = 0; char strtmp[512]; char *col; static char *cvarlist_help = "cvarlist list all cvars matching given parameters\n" "Syntax: cvarlist [-FLdhlrv] [-f flag] [-g group] [cvar]\n" " -c includes only the cvars that have been changed from their defaults\n" " -F shows cvar flags\n" " -L shows latched values\n" " -a shows cvar alternate names\n" " -d shows default cvar values\n" " -f shows only cvars with a matching flag, more than one -f can be used\n" " -g shows only cvar groups using wildcards in group\n" " -h shows this help message\n" " -l shows cvar restriction levels\n" " -r removes group and list headers\n" " -v shows current values\n" " cvar indicates the cvar to show, wildcards (*,?) accepted\n" "Cvar flags are:" ; if (Cmd_Argc() == 1) goto showhelp; gsearch = search = NULL; for (i = 1; i < Cmd_Argc(); i++) { var = Cmd_Argv(i); if (*var == '-') { // short options for (var++; *var; var++) { switch (*var) { case 'g': // fix this so we can search for multiple groups i++; if (i >= Cmd_Argc()) { Con_Printf("Missing parameter for -g\nUse cvarlist -h for help\n"); return; } gsearch = Cmd_Argv(i); break; case 'c': listflags |= CLF_CHANGEDONLY; break; case 'a': listflags |= CLF_ALTNAME; break; case 'l': listflags |= CLF_LEVEL; break; case 'r': listflags |= CLF_RAW; break; case 'v': listflags |= CLF_VALUES; break; case 'd': listflags |= CLF_DEFAULT; break; case 'L': listflags |= CLF_LATCHES; break; case 'f': { char *tmpv; listflags |= CLF_FLAGMASK; i++; if (i >= Cmd_Argc()) { Con_Printf("Missing parameter for -f\nUse cvarlist -h for help\n"); return; } tmpv = Cmd_Argv(i); for (num = 1; num <= CVAR_LASTFLAG; num <<= 1) { char *tmp; tmp = Cvar_FlagToName(num); if (tmp && !stricmp(tmp, tmpv)) { cvarflags |= num; break; } } if (num > CVAR_LASTFLAG) { Con_Printf("Invalid cvar flag name\nUse cvarlist -h for help\n"); return; } } break; case 'F': listflags |= CLF_FLAGS; break; case 'h': showhelp: // list options Con_Printf("%s", cvarlist_help); for (num = 1; num <= CVAR_LASTFLAG; num <<= 1) { // no point caring about the content of var at this point var = Cvar_FlagToName(num); if (var) Con_Printf(" %s", var); } Con_Printf("\n\n"); return; case '-': break; default: Con_Printf("Invalid option for cvarlist\nUse cvarlist -h for help\n"); return; } } } else search = var; } // this is sane.. hopefully if (gsearch) Q_strlwr(gsearch); if (search) Q_strlwr(search); for (grp=cvar_groups ; grp ; grp=grp->next) { // list only cvars with group search substring if (gsearch) { Q_strncpyz(strtmp, grp->name, 512); Q_strlwr(strtmp); if (!wildcmp(gsearch, strtmp)) continue; } gnum = 0; for (cmd=grp->cvars ; cmd ; cmd=cmd->next) { // list only non-restricted cvars if ((cmd->restriction?cmd->restriction:rcon_level.ival) > Cmd_ExecLevel) continue; // list only cvars with search substring if (search) { Q_strncpyz(strtmp, cmd->name, 512); Q_strlwr(strtmp); if (!wildcmp(search, strtmp)) { if (cmd->name2) { Q_strncpyz(strtmp, cmd->name2, 512); Q_strlwr(strtmp); if (!wildcmp(search, strtmp)) continue; } else continue; } } // list only cvars with matching flags if ((listflags & CLF_FLAGMASK) && !(cmd->flags & cvarflags)) continue; if ((listflags & CLF_CHANGEDONLY) && ((cmd->flags & (CVAR_NOSET|CVAR_NOSAVE)) || (cmd->defaultstr && !strcmp(cmd->string, cmd->defaultstr)))) continue; // print cvar list header if (!(listflags & CLF_RAW) && !num) Con_TPrintf("^aCVar list:\n"); // print group header if (!(listflags & CLF_RAW) && !gnum) Con_Printf("^a%s --\n", grp->name); // print restriction level if (listflags & CLF_LEVEL) Con_Printf("(%i) ", cmd->restriction); // print cvar name if (!cmd->defaultstr || !strcmp(cmd->string, cmd->defaultstr)) col = S_COLOR_GREEN; //cvar has default value, woo. else if (cmd->flags & CVAR_ARCHIVE) col = S_COLOR_RED; //cvar will persist. oh noes. else col = S_COLOR_YELLOW; //cvar is changed, but won't be saved to a config so w/e. if (cmd->flags & CVAR_NOUNSAFEEXPAND) Con_Printf("^[%s%s\\type\\%s\\tip\\