#include "cvar_private.h"
#include "coduo_ctype_compat.h"
#include "coduo_native_x87.h"

#include "../core_info/info_private.h"

#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

coduo_engine_cvar_table_t cvar_vars;
coduo_engine_cvar_hash_table_t cvar_hashTable;
cvar_t *cvar_sortedHead;
int32_t cvar_numIndexes;
int32_t cvar_modifiedFlags;
coduo_cvar_info_string_buffer_t cvar_infoStringBuffer;
coduo_cvar_big_info_string_buffer_t cvar_bigInfoStringBuffer;

cvar_t *sv_cheats;
cvar_t *sv_console_lockout;

uint32_t generateHashValue(const char *name)
{
    uint32_t hash;
    int index;
    int lower;

    if (name == NULL) {
        Com_Error(1, "\x15" "null name in generateHashValue");
    }

    hash = 0;
    for (index = 0; name[index] != '\0'; index++) {
        lower = tolower(coduo_ctype_signed_byte_arg(name[index]));
        hash += (uint32_t)((index + CODUO_CVAR_HASH_BASE_INDEX) * lower);
    }

    return hash & CODUO_CVAR_HASH_MASK;
}

qboolean Cvar_ValidateString(const char *name)
{
    if (name == NULL) {
        return 0;
    }
    if (strchr(name, '\\') != NULL) {
        return 0;
    }
    if (strchr(name, '"') != NULL) {
        return 0;
    }
    if (strchr(name, ';') != NULL) {
        return 0;
    }
    return 1;
}

cvar_t *Cvar_FindVar(const char *name)
{
    cvar_t *var;
    uint32_t hash;

    hash = generateHashValue(name);
    for (var = cvar_hashTable[hash]; var != NULL;
         var = var->hashNext) {
        if (Q_stricmp(name, var->name) == 0) {
            return var;
        }
    }

    return NULL;
}

void Cvar_Shutdown(void)
{
    uint32_t hash;
    cvar_t *var;

    for (hash = 0; hash < CODUO_ENGINE_CVAR_HASH_SIZE; hash++) {
        for (var = cvar_hashTable[hash]; var != NULL;
             var = var->hashNext) {
            if (var->latchedString != NULL) {
                Z_Free(var->latchedString);
                var->latchedString = 0;
            }
            if (var->string != NULL) {
                Z_Free(var->string);
                var->string = 0;
            }
            if (var->resetString != NULL) {
                Z_Free(var->resetString);
                var->resetString = 0;
            }
            Z_Free(var->name);
            var->name = 0;
        }
        cvar_hashTable[hash] = 0;
    }

    cvar_numIndexes = 0;
    cvar_sortedHead = NULL;
    sv_cheats = NULL;
    sv_console_lockout = NULL;
    cvar_modifiedFlags = 0;
}

cvar_t *Cvar_Get(const char *name, const char *value,
                              uint32_t flags)
{
    cvar_t *var;
    cvar_t *prev;
    cvar_t *scan;
    uint32_t hash;

    if (name == NULL || value == NULL) {
        Com_Error(0, "\x15" "Cvar_Get: NULL parameter");
    }

    if (Cvar_ValidateString(name) == 0) {
        Com_Error(0, va("invalid cvar name string: %s", name));
    }

    var = Cvar_FindVar(name);
    if (var != NULL) {
        if ((var->flags & CODUO_CVAR_PLACEHOLDER_CREATED_MASK) != 0 &&
            (flags & CODUO_CVAR_PLACEHOLDER_CREATED_MASK) == 0 &&
            (value[0] != '\0' || (flags & CODUO_CVAR_CHEAT) != 0)) {
            var->flags &= ~CODUO_CVAR_PLACEHOLDER_CREATED_MASK;
            Z_Free(var->resetString);
            var->resetString = CopyString(value);
            cvar_modifiedFlags |= flags;
        }

        var->flags |= flags;
        if (var->resetString[0] == '\0') {
            Z_Free(var->resetString);
            var->resetString = CopyString(value);
        } else if (value[0] != '\0' &&
                   strcmp(var->resetString, value) != 0) {
            Com_DPrintf("Warning: cvar \"%s\" given initial values: \"%s\" "
                        "and \"%s\"\n",
                        name, var->resetString, value);
        }

        if (var->latchedString != NULL) {
            char *latched;

            latched = var->latchedString;
            var->latchedString = 0;
            Cvar_Set2(name, latched, 1);
            Z_Free(latched);
        }

        if ((var->flags & CODUO_CVAR_CHEAT) != 0 && sv_cheats != NULL &&
            sv_cheats->integer == 0) {
            Cvar_Set2(name, value, 1);
        }

        return var;
    }

    if (cvar_numIndexes > CODUO_ENGINE_CVAR_COUNT - 1) {
        Com_Error(0, "MAX_CVARS");
    }

    var = &cvar_vars[cvar_numIndexes];
    cvar_numIndexes++;
    var->name = CopyString(name);
    var->string = CopyString(value);
    var->modified = 1;
    var->modificationCount = 1;
    var->value = (float)atof(var->string);
    var->integer = (int32_t)atoi(var->string);
    var->resetString = CopyString(value);

    prev = NULL;
    scan = cvar_sortedHead;
    while (scan != NULL &&
           strcasecmp(var->name,
                      scan->name) >= 0) {
        prev = scan;
        scan = scan->next;
    }
    var->next = scan;
    if (prev == NULL) {
        cvar_sortedHead = var;
    } else {
        prev->next = var;
    }

    var->flags = flags;
    hash = generateHashValue(name);
    var->hashNext = cvar_hashTable[hash];
    cvar_hashTable[hash] = var;

    return var;
}

cvar_t *Cvar_Set2(const char *name, const char *value,
                               qboolean force)
{
    cvar_t *var;

    Com_PrintMessage(CODUO_CVAR_PRINT_CHANNEL_DEVELOPER,
                     va("      cvar set %s %s\n", name, value));

    if (Cvar_ValidateString(name) == 0) {
        Com_Error(0, va("invalid cvar name string: %s", name));
    }

    var = Cvar_FindVar(name);
    if (var == NULL) {
        if (value == NULL) {
            return NULL;
        }
        if (force == 0) {
            return Cvar_Get(name, value, CODUO_CVAR_USER_CREATED);
        }
        return Cvar_Get(name, value, 0);
    }

    if (value == NULL) {
        value = var->resetString;
    }

    if (strcmp(value, var->string) == 0) {
        if ((var->flags & CODUO_CVAR_LATCH) != 0 &&
            var->latchedString != NULL) {
            Z_Free(var->latchedString);
            var->latchedString = 0;
        }
        return var;
    }

    cvar_modifiedFlags |= var->flags;

    if (force == 0) {
        if ((var->flags & CODUO_CVAR_ROM) != 0) {
            Com_Printf("%s is read only.\n", name);
            return var;
        }
        if ((var->flags & CODUO_CVAR_INIT) != 0) {
            Com_Printf("%s is write protected.\n", name);
            return var;
        }
        if ((var->flags & CODUO_CVAR_CHEAT) != 0 && sv_cheats->integer == 0) {
            Com_Printf("%s is cheat protected.\n", name);
            return var;
        }
        if (sv_console_lockout->integer != 0 && sv_running->integer != 0) {
            Com_Printf("Consol is locked out, cannot change cvars.\n");
            return var;
        }
        if ((var->flags & CODUO_CVAR_LATCH) != 0) {
            if (var->latchedString == NULL) {
                if (strcmp(value, var->string) == 0) {
                    return var;
                }
            } else {
                if (strcmp(value, var->latchedString) == 0) {
                    return var;
                }
                Z_Free(var->latchedString);
            }

            Com_Printf("%s will be changed upon restarting.\n", name);
            var->latchedString = CopyString(value);
            var->modified = 1;
            return var;
        }
    } else if (var->latchedString != NULL) {
        Z_Free(var->latchedString);
        var->latchedString = 0;
    }

    if (strcmp(value, var->string) != 0) {
        var->modified = 1;
        var->modificationCount++;
        Z_Free(var->string);
        var->string = CopyString(value);
        var->value = (float)atof(var->string);
        var->integer = (int32_t)atoi(var->string);
    }

    return var;
}

cvar_t *Cvar_Set(const char *name, const char *value)
{
    return Cvar_Set2(name, value, 1);
}

void Cvar_VMSet(vmCvar_t *vmCvar, const char *value)
{
    /* ORIGINAL_BINARY_BUG: stock indexes cvar_vars with the unvalidated
     * module handle before Cvar_Update performs its unsigned range check; see
     * original-bugs/coduomp-cvar-vmset-unchecked-handle.md. */
    Cvar_Set(cvar_vars[vmCvar->handle].name, value);
    Cvar_Update(vmCvar);
}

void Cvar_SetLatched(const char *name, const char *value)
{
    Cvar_Set2(name, value, 0);
}

void Cvar_SetValue(const char *name, float value)
{
    char text[CODUO_CVAR_SET_VALUE_FORMAT_SIZE];
#if defined(__x86_64__)
    coduo_x87_truncation_control_t conversionControl;
    int32_t comparisonInteger = CODUO_X87_TRUNCATE_I32_FIRST(
        &conversionControl, (long double)value);
#else
    int32_t comparisonInteger = (int32_t)value;
#endif

    /* 0x807381d..0x8073834 converts once for the comparison, whose bare fild
     * at 0x807383c feeds fucompp without a float narrowing.  The integer
     * formatting arm converts the float a second time at 0x8073851..0x807385b.
     * Do not cache one conversion or add a (float) cast. */
    if (value == comparisonInteger) {
#if defined(__x86_64__)
        int32_t formattingInteger = CODUO_X87_TRUNCATE_I32_NEXT(
            &conversionControl, (long double)value);
#else
        int32_t formattingInteger = (int32_t)value;
#endif
        Com_sprintf(text, sizeof(text), "%i", formattingInteger);
    } else {
        Com_sprintf(text, sizeof(text), "%f", (double)value);
    }
    Cvar_Set(name, text);
}

void Cvar_Reset(const char *name)
{
    Cvar_Set2(name, NULL, 0);
}

void Cvar_Register(vmCvar_t *vmCvar, const char *name,
                   const char *value, uint32_t flags)
{
    cvar_t *var;

    var = Cvar_Get(name, value, flags);
    if (vmCvar != NULL) {
        vmCvar->handle = (int32_t)(var - cvar_vars);
        vmCvar->modificationCount = -1;
        Cvar_Update(vmCvar);
    }
}

void Cvar_Update(vmCvar_t *vmCvar)
{
    cvar_t *var;
    size_t stringLength;

    if ((uint32_t)vmCvar->handle >= (uint32_t)cvar_numIndexes) {
        Com_Error(1, "\x15" "Cvar_Update: handle out of range");
    }

    var = &cvar_vars[vmCvar->handle];
    if (var->modificationCount != vmCvar->modificationCount &&
        var->string != NULL) {
        vmCvar->modificationCount = var->modificationCount;
        stringLength = strlen(var->string);
        if (sizeof(vmCvar->string) < stringLength + 1) {
            Com_Error(1,
                      "\x15" "Cvar_Update: src %s length %d exceeds "
                             "MAX_CVAR_VALUE_STRING",
                      var->string, (int)stringLength,
                      (int)sizeof(vmCvar->string));
        }
        Q_strncpyz(vmCvar->string, var->string,
                   sizeof(vmCvar->string));
        vmCvar->value = var->value;
        vmCvar->integer = var->integer;
    }
}

void Cvar_ResetScriptInfo(void)
{
    sv_cheats = Cvar_Get("sv_cheats", "0",
                         CODUO_CVAR_ROM | CODUO_CVAR_SYSTEMINFO);
    sv_console_lockout = Cvar_Get("sv_console_lockout", "0",
                                  CODUO_CVAR_ROM | CODUO_CVAR_SYSTEMINFO);
    Cvar_AddCommands();
}

void Cvar_ClearScriptSetServerinfoFlags(void)
{
    cvar_t *var;

    for (var = cvar_sortedHead; var != NULL; var = var->next) {
        var->flags &= ~CODUO_CVAR_SCRIPT_SETCVAR_SERVERINFO;
    }
}

float Cvar_VariableValue(const char *name)
{
    cvar_t *var;

    var = Cvar_FindVar(name);
    if (var == NULL) {
        return 0.0f;
    }
    return var->value;
}

int32_t Cvar_VariableIntegerValue(const char *name)
{
    cvar_t *var;

    var = Cvar_FindVar(name);
    if (var == NULL) {
        return 0;
    }
    return var->integer;
}

const char *Cvar_VariableString(const char *name)
{
    cvar_t *var;

    var = Cvar_FindVar(name);
    if (var == NULL) {
        return "";
    }
    return var->string;
}

void Cvar_VariableStringBuffer(const char *name, char *buffer,
                               int32_t bufferLength)
{
    cvar_t *var;

    var = Cvar_FindVar(name);
    if (var == NULL) {
        buffer[0] = '\0';
    } else {
        Q_strncpyz(buffer, var->string, bufferLength);
    }
}

void Cvar_CommandCompletion(void (*callback)(const char *name))
{
    cvar_t *var;

    for (var = cvar_sortedHead; var != NULL; var = var->next) {
        callback(var->name);
    }
}

void Cvar_SetCheatState(void)
{
    cvar_t *var;

    for (var = cvar_sortedHead; var != NULL; var = var->next) {
        if ((var->flags & CODUO_CVAR_CHEAT) != 0 &&
            strcmp(var->resetString,
                   var->string) != 0) {
            Cvar_Set(var->name,
                     var->resetString);
        }
    }
}

qboolean Cvar_Command(void)
{
    const char *value;
    cvar_t *var;

    var = Cvar_FindVar(Cmd_Argv(0));
    if (var == NULL) {
        return 0;
    }

    if (Cmd_Argc() == 1) {
        Com_Printf("\"%s\" is:\"%s^7\" default:\"%s^7\"\n",
                   var->name, var->string,
                   var->resetString);
        if (var->latchedString != NULL) {
            Com_Printf("latched: \"%s\"\n", var->latchedString);
        }
    } else {
        value = Cmd_Argv(1);
        Cvar_Set2(var->name, value, 0);
    }

    return 1;
}

void Cvar_Toggle_f(void)
{
    const char *name;
    const char *nextValue;
    const char *current;
    int index;

    if (Cmd_Argc() < 2) {
        Com_Printf("usage: toggle <variable> <optional value sequence>\n");
        return;
    }

    if (Cmd_Argc() == 2) {
#if defined(__x86_64__)
        float currentValue;
        int32_t currentInteger;

        name = Cmd_Argv(1);
        currentValue = Cvar_VariableValue(name);
        currentInteger =
            CODUO_X87_TRUNCATE_I32((long double)currentValue);
        nextValue = va("%i", currentInteger == 0);
#else
        name = Cmd_Argv(1);
        nextValue = va("%i", (int32_t)Cvar_VariableValue(name) == 0);
#endif
        name = Cmd_Argv(1);
        Cvar_Set2(name, nextValue, 0);
        return;
    }

    current = Cvar_VariableString(Cmd_Argv(1));
    for (index = 2; index < Cmd_Argc() - 1; index++) {
        if (strcmp(current, Cmd_Argv((uint32_t)index)) == 0) {
            nextValue = Cmd_Argv((uint32_t)(index + 1));
            name = Cmd_Argv(1);
            Cvar_Set2(name, nextValue, 0);
            return;
        }
    }
    nextValue = Cmd_Argv(2);
    name = Cmd_Argv(1);
    Cvar_Set2(name, nextValue, 0);
}

void Cvar_Set_f(void)
{
    const char *name;
    char value[CODUO_CVAR_SET_VALUE_BUFFER_SIZE];
    int32_t argc;
    int used;
    int index;
    int32_t length;

    argc = Cmd_Argc();
    if (argc < 3) {
        Com_Printf("usage: set <variable> <value>\n");
        return;
    }

    value[0] = '\0';
    used = 0;
    for (index = 2; index < argc; index++) {
        /*
         * ORIGINAL_BINARY_BUG: the source-length check omits the first
         * character and every inserted separator even though strcat copies
         * both into the 4,096-byte destination.
         *
         * Machine code measures strlen(Cmd_Argv(i) + 1), then appends the
         * full token. Preserve that off-by-one bound check.
         */
        length = (int32_t)strlen(Cmd_Argv((uint32_t)index) + 1);
        if (length + used > CODUO_CVAR_SET_VALUE_LIMIT) {
            break;
        }
        strcat(value, Cmd_Argv((uint32_t)index));
        if (index != argc - 1) {
            strcat(value, " ");
        }
        used += length;
    }

    name = Cmd_Argv(1);
    Cvar_Set2(name, value, 0);
}

void Cvar_SetU_f(void)
{
    cvar_t *var;

    if (Cmd_Argc() == 3) {
        Cvar_Set_f();
        var = Cvar_FindVar(Cmd_Argv(1));
        if (var != NULL) {
            var->flags |= CODUO_CVAR_USERINFO;
        }
    } else {
        Com_Printf("usage: setu <variable> <value>\n");
    }
}

void Cvar_SetS_f(void)
{
    cvar_t *var;

    if (Cmd_Argc() == 3) {
        Cvar_Set_f();
        var = Cvar_FindVar(Cmd_Argv(1));
        if (var != NULL) {
            var->flags |= CODUO_CVAR_SERVERINFO;
        }
    } else {
        Com_Printf("usage: sets <variable> <value>\n");
    }
}

void Cvar_SetA_f(void)
{
    cvar_t *var;

    if (Cmd_Argc() == 3) {
        Cvar_Set_f();
        var = Cvar_FindVar(Cmd_Argv(1));
        if (var != NULL) {
            var->flags |= CODUO_CVAR_ARCHIVE;
        }
    } else {
        Com_Printf("usage: seta <variable> <value>\n");
    }
}

void Cvar_SetFromCvar_f(void)
{
    cvar_t *var;
    const char *value;

    if (Cmd_Argc() == 3) {
        var = Cvar_FindVar(Cmd_Argv(2));
        if (var == NULL) {
            value = "";
        } else {
            value = var->string;
        }
        Cvar_Set2(Cmd_Argv(1), value, 0);
    } else {
        Com_Printf("usage: setfromcvar <variable> <variablein>\n");
    }
}

void Cvar_Reset_f(void)
{
    if (Cmd_Argc() == 2) {
        Cvar_Reset(Cmd_Argv(1));
    } else {
        Com_Printf("usage: reset <variable>\n");
    }
}

void Cvar_WriteVariables(int32_t handle)
{
    cvar_t *var;
    char line[CODUO_CVAR_WRITE_BUFFER_SIZE];

    for (var = cvar_sortedHead; var != NULL; var = var->next) {
        if (Q_stricmp(var->name, "cl_cdkey") != 0 &&
            (var->flags & CODUO_CVAR_ARCHIVE) != 0) {
            if (var->latchedString == NULL) {
                Com_sprintf(line, CODUO_CVAR_WRITE_BUFFER_SIZE,
                             "seta %s \"%s\"\n", var->name,
                             var->string);
            } else {
                Com_sprintf(line, CODUO_CVAR_WRITE_BUFFER_SIZE,
                             "seta %s \"%s\"\n", var->name,
                             var->latchedString);
            }
            FS_Printf(handle, "%s", line);
        }
    }
}

void Cvar_WriteDefaults(int32_t handle)
{
    cvar_t *var;
    char line[CODUO_CVAR_WRITE_BUFFER_SIZE];

    for (var = cvar_sortedHead; var != NULL; var = var->next) {
        if (Q_stricmp(var->name, "cl_cdkey") != 0 &&
            (var->flags & (CODUO_CVAR_ROM | CODUO_CVAR_USER_CREATED |
                           CODUO_CVAR_CHEAT |
                           CODUO_CVAR_SCRIPT_SETCVAR)) == 0) {
            Com_sprintf(line, CODUO_CVAR_WRITE_BUFFER_SIZE,
                         "set %s \"%s\"\n", var->name,
                         var->resetString);
            FS_Printf(handle, "%s", line);
        }
    }
}

void Cvar_List_f(void)
{
    const char *match;
    cvar_t *var;
    int count;

    if (Cmd_Argc() < 2) {
        match = NULL;
    } else {
        match = Cmd_Argv(1);
    }

    count = 0;
    for (var = cvar_sortedHead; var != NULL; var = var->next) {
        if (match == NULL ||
            Com_Filter(match, var->name, 0) != 0) {
            Com_Printf((var->flags & (CODUO_CVAR_SERVERINFO |
                                      CODUO_CVAR_SCRIPT_SETCVAR_SERVERINFO)) == 0
                           ? " "
                           : "S");
            Com_Printf((var->flags & CODUO_CVAR_USERINFO) == 0 ? " " : "U");
            Com_Printf((var->flags & CODUO_CVAR_ROM) == 0 ? " " : "R");
            Com_Printf((var->flags & CODUO_CVAR_INIT) == 0 ? " " : "I");
            Com_Printf((var->flags & CODUO_CVAR_ARCHIVE) == 0 ? " " : "A");
            Com_Printf((var->flags & CODUO_CVAR_LATCH) == 0 ? " " : "L");
            Com_Printf((var->flags & CODUO_CVAR_CHEAT) == 0 ? " " : "C");
            Com_Printf(" %s \"%s\"\n", var->name,
                       var->string);
        }
        count++;
    }
    Com_Printf("\n%i total cvars\n", count);
    Com_Printf("%i cvar indexes\n", cvar_numIndexes);
}

void Cvar_Dump_f(void)
{
    Cvar_DumpToChannel(0);
}

void Cvar_DumpToChannel(int32_t channel)
{
    const char *match;
    cvar_t *var;
    char line[CODUO_CVAR_DUMP_BUFFER_SIZE];
    int count;

    if (Cmd_Argc() < 2) {
        match = NULL;
    } else {
        match = Cmd_Argv(1);
    }

    if (channel == 0 && (logfile == NULL || logfile->integer == 0)) {
        return;
    }

    count = 0;
    Com_PrintMessage(channel,
                     "=============================== CVAR DUMP "
                     "========================================\n");
    for (var = cvar_sortedHead; var != NULL; var = var->next) {
        if (match == NULL ||
            Com_Filter(match, var->name, 0) != 0) {
            if (var->latchedString != NULL) {
                Com_sprintf(line, CODUO_CVAR_DUMP_BUFFER_SIZE,
                             "      %s \"%s\" -- latched \"%s\"\n",
                             var->name,
                             var->string,
                             var->latchedString);
            } else {
                Com_sprintf(line, CODUO_CVAR_DUMP_BUFFER_SIZE,
                             "      %s \"%s\"\n", var->name,
                             var->string);
            }
            Com_PrintMessage(channel, line);
        }
        count++;
    }
    Com_sprintf(line, CODUO_CVAR_DUMP_BUFFER_SIZE,
                 "\n%i total cvars\n%i cvar indexes\n", count,
                 cvar_numIndexes);
    Com_PrintMessage(channel, line);
    Com_PrintMessage(channel,
                     "=============================== END CVAR DUMP "
                     "====================================\n");
}

void Cvar_SetExisting(const char *name, const char *value)
{
    cvar_t *var;

    var = Cvar_FindVar(name);
    if (var != NULL) {
        if ((var->flags & CODUO_CVAR_LATCH) == 0) {
            Cvar_Set(name, value);
        } else {
            Cvar_SetLatched(name, value);
        }
    }
}

qboolean Cvar_NextExport(const char **name, const char **string,
                         uint32_t *flags,
                         const char **resetString)
{
    static cvar_t *cursor;

    if (cursor == NULL) {
        cursor = cvar_sortedHead;
    } else {
        cursor = cursor->next;
    }

    if (cursor == NULL) {
        return 0;
    }

    *name = cursor->name;
    *string = cursor->string;
    *flags = cursor->flags;
    *resetString = cursor->resetString;
    return 1;
}

char *PbCvarValidate(char *buffer)
{
    cvar_t *var;
    float parsedValue;
    int parsedInteger;

    var = cvar_sortedHead;
    buffer[0] = '\0';
    while (var != NULL) {
        parsedValue = (float)atof(var->string);
        parsedInteger = atoi(var->string);
        if (parsedValue != var->value || parsedInteger != var->integer) {
            /* ORIGINAL_BINARY_BUG: stock copies an unrestricted cvar name
             * into the caller's fixed PunkBuster response buffer without
             * receiving a capacity; see
             * original-bugs/coduomp-pb-cvar-validate-name-overflow.md. */
            strcpy(buffer, var->name);
            return buffer;
        }
        var = var->next;
    }
    return buffer;
}

void Cvar_Restart_f(void)
{
    cvar_t *prev;
    cvar_t *var;
    cvar_t *next;

    prev = NULL;
    var = cvar_sortedHead;
    while (var != NULL) {
        next = var->next;
        if ((var->flags & CODUO_CVAR_RESTART_PRESERVE_MASK) != 0) {
            prev = var;
            var = next;
        } else if ((var->flags & CODUO_CVAR_USER_CREATED) != 0) {
            if (prev == NULL) {
                cvar_sortedHead = next;
            } else {
                prev->next = next;
            }
            if (var->name != NULL) {
                Z_Free(var->name);
            }
            if (var->string != NULL) {
                Z_Free(var->string);
            }
            if (var->latchedString != NULL) {
                Z_Free(var->latchedString);
            }
            if (var->resetString != NULL) {
                Z_Free(var->resetString);
            }
            /* ORIGINAL_BINARY_BUG: preserved from machine code.
             * 0x80747c5 passes memset size 4 — the classic Q3
             * memset(var, 0, sizeof(var)) pointer-size typo. On original
             * i386 that clears exactly the name pointer and leaves the other
             * fields stale. sizeof(var->name) deliberately widens only that
             * pointer clear on 64-bit hosts, preserving the field-level
             * behavior without leaving half a freed native pointer live.
             * The record is still never unlinked from cvar_hashTable. See
             * original-bugs/engine-cvar-restart-memset-4.md and
             * original-bugs/coduomp-cvar-restart-stale-hash-row.md. */
            memset(var, 0, sizeof(var->name));
            var = next;
        } else {
            Cvar_Set(var->name,
                     var->resetString);
            prev = var;
            var = next;
        }
    }
}

void Cvar_SetConfigstringValues(int32_t base,
                                int32_t count,
                                uint32_t flags)
{
    cvar_t *var;

    for (var = cvar_sortedHead; var != NULL; var = var->next) {
        if ((var->flags & flags) != 0) {
            SV_SetConfigValueForKey(base, count, var->name,
                                    var->string);
        }
    }
}

const char *Cvar_InfoString(uint32_t flags)
{
    cvar_t *var;

    cvar_infoStringBuffer[0] = '\0';
    for (var = cvar_sortedHead; var != NULL; var = var->next) {
        if ((var->flags & flags) != 0) {
            Info_SetValueForKey(cvar_infoStringBuffer,
                                var->name,
                                var->string);
        }
    }
    return cvar_infoStringBuffer;
}

const char *Cvar_InfoString_Big(uint32_t flags)
{
    cvar_t *var;

    cvar_bigInfoStringBuffer[0] = '\0';
    for (var = cvar_sortedHead; var != NULL; var = var->next) {
        if ((var->flags & flags) != 0) {
            Info_SetValueForKey_Big(cvar_bigInfoStringBuffer,
                                    var->name,
                                    var->string);
        }
    }
    return cvar_bigInfoStringBuffer;
}

void Cvar_InfoStringBuffer(uint32_t flags, char *buffer,
                           int32_t bufferLength)
{
    Q_strncpyz(buffer, Cvar_InfoString(flags), bufferLength);
}

void Cvar_AddCommands(void)
{
    Cmd_AddCommand("toggle", Cvar_Toggle_f);
    Cmd_AddCommand("set", Cvar_Set_f);
    Cmd_AddCommand("sets", Cvar_SetS_f);
    Cmd_AddCommand("setu", Cvar_SetU_f);
    Cmd_AddCommand("seta", Cvar_SetA_f);
    Cmd_AddCommand("setfromcvar", Cvar_SetFromCvar_f);
    Cmd_AddCommand("reset", Cvar_Reset_f);
    Cmd_AddCommand("cvarlist", Cvar_List_f);
    Cmd_AddCommand("cvardump", Cvar_Dump_f);
    Cmd_AddCommand("cvar_restart", Cvar_Restart_f);
}
