#include "coduo_engine_structs.h"
#include "coduo_ctype_compat.h"
#include "../abi/vm_private.h"
#include "../animation/dobj_private.h"
#include "../animation/xanim_private.h"
#include "../core_cvar/cvar_private.h"
#include "../core_info/info_private.h"
#include "../core_memory/core_memory_private.h"
#include "../core_runtime/core_runtime_private.h"
#include "../filesystem/fs_private.h"
#include "../map_loading/cm_load_map_private.h"
#include "../model_assets/xmodel_asset_private.h"
#include "../networking/netchan_private.h"
#include "../punkbuster/pb_private.h"
#include "../sound_alias/sound_alias_private.h"
#include "sv_globals_private.h"
#include "server_private.h"

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

#define SV_CONFIGSTRING_BROADCAST_RELIABLE qtrue

enum {
    SV_CONFIGSTRING_LONG_CHUNK_SIZE = 1000,
    SV_CONFIGSTRING_LONG_CHUNK_BUFFER_SIZE = 1024,
    SV_MIN_STARTUP_CLIENTS = 1,
    SV_LOCAL_CLIENT_SNAPSHOT_ENTITY_SHIFT = 8,
    SV_DEDICATED_CLIENT_SNAPSHOT_ENTITY_SHIFT = 11,
    SV_LOCAL_CLIENT_SNAPSHOT_CLIENT_MULTIPLIER = 4,
    SV_DEDICATED_CLIENT_SNAPSHOT_CLIENT_MULTIPLIER = 32,
    SV_USERINFO_NAME_SIZE = 32,
    SV_GAMETYPE_BUFFER_SIZE = 64,
    SV_COM_ERROR_DROP = 1,
    SV_COM_ERROR_FATAL = 0,
    SV_XMODEL_CHECK_CVAR_FLAGS = CODUO_CVAR_ARCHIVE | CODUO_CVAR_LATCH,
    SV_GAMETYPE_CVAR_FLAGS = CODUO_CVAR_SERVERINFO | CODUO_CVAR_LATCH,
    SV_SERVER_RESTARTING_CONFIG_TRUE = 1,
    SV_LOADING_RECONNECT_DELAY_MSEC = 250,
    SV_SPAWN_WARMUP_FRAME_COUNT = 3,
    SV_SPAWN_WARMUP_FRAME_MSEC = 100,
    SV_SERVER_ID_STEP = 0x10,
    SV_SERVER_ID_HIGH_NIBBLE_MASK = 0xf0,
    SV_MAP_CHECKSUM_SLOT_COUNT = 1,
    SV_MAP_SYSTEMINFO_BUFFER_SIZE = 8192,
    SV_MAP_SYSTEMINFO_COPY_LIMIT = 0x2000,
    SV_SOUND_ALIAS_LOCALE_LOCALIZED = 2
};

static const char svConfigstringBadIndexError[] =
    "\x15"
    "SV_SetConfigstring: bad index %i\n";
static const char svGetConfigstringBadBufferSizeError[] =
    "\x15"
    "SV_GetConfigstring: bufferSize == %i";
static const char svGetConfigstringBadIndexError[] =
    "\x15"
    "SV_GetConfigstring: bad index %i";
static const char svSetConfigValueOverflowError[] =
    "\x15"
    "SV_SetConfigValueForKey: overflow";
static const char svSetUserinfoBadIndexError[] =
    "\x15"
    "SV_SetUserinfo: bad index %i\n";
static const char svGetUserinfoBadBufferSizeError[] =
    "\x15"
    "SV_GetUserinfo: bufferSize == %i";
static const char svGetUserinfoBadIndexError[] =
    "\x15"
    "SV_GetUserinfo: bad index %i";
static const char svStartupAlreadyInitializedError[] =
    "\x15"
    "SV_Startup: svs.initialized";
static const char svStartupClientsAllocError[] =
    "\x15"
    "SV_Startup: unable to allocate svs.clients";

static const char svEmptyString[] = "";
static const char svConfigstringLongBeginCommand[] = "x";
static const char svConfigstringLongEndCommand[] = "z";
static const char svConfigstringLongContinueCommand[] = "y";
static const char svConfigstringDirectCommand[] = "d %i %s";
static const char svConfigstringChunkCommand[] = "%s %i %s";
static const char svMaxclientsCvarName[] = "sv_maxclients";
static const char svMaxclientsDefault[] = "20";
static const char svRunningCvarName[] = "sv_running";
static const char svRunningEnabled[] = "1";
static const char svRunningDisabled[] = "0";
static const char svHunkUsagePath[] = "hunkusage.dat";
static const char svExpectedHunkUsageCvarName[] = "com_expectedhunkusage";
static const char svExpectedHunkUsageUnknown[] = "-1";
static const char svGametypeCvarName[] = "g_gametype";
static const char svDeathmatchGametype[] = "dm";
static const char svXModelCheckCvarName[] = "cl_xmodelcheck";
static const char svXModelCheckDefault[] = "0";
static const char svNextmapCvarName[] = "nextmap";
static const char svNextmapRestartValue[] = "map_restart";
static const char svClPausedCvarName[] = "cl_paused";
static const char svMapnameCvarName[] = "mapname";
static const char svServerIdCvarName[] = "sv_serverid";
static const char svServerRestartingCvarName[] = "sv_serverRestarting";
static const char svPunkBusterCvarName[] = "sv_punkbuster";
static const char svPaksCvarName[] = "sv_paks";
static const char svPakNamesCvarName[] = "sv_pakNames";
static const char svReferencedPaksCvarName[] = "sv_referencedPaks";
static const char svReferencedPakNamesCvarName[] = "sv_referencedPakNames";
static const char svUserinfoNameKey[] = "name";

void SV_NormalizeGametypeCvar(void);

/* NOT_FROM_ORIGINAL_SOURCE: common calculation used by two stock paths. */
static void coduomp_set_snapshot_capacity_for_client_count(
    int32_t clientCount)
{
    if (dedicated->integer == 0) {
        svs.numEntityStateSnapshots =
            clientCount << SV_LOCAL_CLIENT_SNAPSHOT_ENTITY_SHIFT;
        svs.numClientSnapshots =
            clientCount * clientCount *
            SV_LOCAL_CLIENT_SNAPSHOT_CLIENT_MULTIPLIER;
    } else {
        svs.numEntityStateSnapshots =
            clientCount << SV_DEDICATED_CLIENT_SNAPSHOT_ENTITY_SHIFT;
        svs.numClientSnapshots =
            clientCount * clientCount *
            SV_DEDICATED_CLIENT_SNAPSHOT_CLIENT_MULTIPLIER;
    }
}

void SV_SetConfigstring(int32_t index, const char *value)
{
    char chunk[SV_CONFIGSTRING_LONG_CHUNK_BUFFER_SIZE];
    const char *command;

    if (index < 0 || index >= CODUO_MAX_CONFIGSTRINGS) {
        Com_Error(SV_COM_ERROR_DROP, svConfigstringBadIndexError, index);
    }

    if (value == NULL) {
        value = svEmptyString;
    }

    if (strcmp(value, host_sv_configstrings[index]) == 0) {
        return;
    }

    Z_Free(host_sv_configstrings[index]);
    host_sv_configstrings[index] = CopyString(value);

    if (sv.state != CODUO_SS_GAME && sv.restarting == qfalse) {
        return;
    }

    for (int32_t clientIndex = 0;
         clientIndex < host_cvar_sv_maxclients_pointer_slot->integer;
         ++clientIndex) {
        client_t *client = &svs.clients[clientIndex];

        if (client->state <= CODUO_CS_PRIMED) {
            continue;
        }

        size_t length = strlen(value);
        if ((int32_t)length < SV_CONFIGSTRING_LONG_CHUNK_SIZE) {
            SV_SendServerCommand(client, SV_CONFIGSTRING_BROADCAST_RELIABLE,
                                 svConfigstringDirectCommand, index, value);
            continue;
        }

        int32_t offset = 0;
        for (size_t remaining = length; (int32_t)remaining > 0;
             remaining = (remaining - SV_CONFIGSTRING_LONG_CHUNK_SIZE) + 1) {
            if (offset == 0) {
                command = svConfigstringLongBeginCommand;
            } else if ((int32_t)remaining < SV_CONFIGSTRING_LONG_CHUNK_SIZE) {
                command = svConfigstringLongEndCommand;
            } else {
                command = svConfigstringLongContinueCommand;
            }

            Q_strncpyz(chunk, value + offset, SV_CONFIGSTRING_LONG_CHUNK_SIZE);
            SV_SendServerCommand(client, SV_CONFIGSTRING_BROADCAST_RELIABLE,
                                 svConfigstringChunkCommand, command, index,
                                 chunk);
            offset += SV_CONFIGSTRING_LONG_CHUNK_SIZE - 1;
        }
    }
}

void SV_GetConfigstring(int32_t index, char *buffer,
                        int32_t bufferSize)
{
    if (bufferSize < 1) {
        Com_Error(SV_COM_ERROR_DROP, svGetConfigstringBadBufferSizeError,
                  bufferSize);
    }
    if (index < 0 || index >= CODUO_MAX_CONFIGSTRINGS) {
        Com_Error(SV_COM_ERROR_DROP, svGetConfigstringBadIndexError, index);
    }

    if (host_sv_configstrings[index] == NULL) {
        buffer[0] = '\0';
    } else {
        Q_strncpyz(buffer, host_sv_configstrings[index], bufferSize);
    }
}

const char *SV_GetConfigstringConst(int32_t index)
{
    if (host_sv_configstrings[index] == NULL) {
        return svEmptyString;
    }

    return host_sv_configstrings[index];
}

const char *SV_GetConfigValueForKey(int32_t base,
                                    int32_t count,
                                    const char *key)
{
    for (int32_t slot = 0; slot < count; ++slot) {
        const char *storedKey = host_sv_configstrings[base + slot];

        if (storedKey[0] == '\0') {
            break;
        }
        if (strcasecmp(key, storedKey) == 0) {
            return host_sv_configstrings[base + count + slot];
        }
    }

    return svEmptyString;
}

void SV_SetConfigValueForKey(int32_t base,
                             int32_t count,
                             const char *key, const char *value)
{
    int32_t slot = 0;

    while (slot < count) {
        const char *storedKey = host_sv_configstrings[base + slot];

        if (storedKey[0] == '\0') {
            SV_SetConfigstring(base + slot, key);
            break;
        }
        if (Q_strcasecmp(key, storedKey) == 0) {
            break;
        }
        ++slot;
    }

    if (slot == count) {
        Com_Error(SV_COM_ERROR_DROP, svSetConfigValueOverflowError);
    }

    SV_SetConfigstring(base + count + slot, value);
}

void SV_SetUserinfo(int32_t clientNum, const char *userinfo)
{
    if (clientNum < 0 ||
        clientNum >= host_cvar_sv_maxclients_pointer_slot->integer) {
        Com_Error(SV_COM_ERROR_DROP, svSetUserinfoBadIndexError, clientNum);
    }

    if (userinfo == NULL) {
        userinfo = svEmptyString;
    }

    client_t *client = &svs.clients[clientNum];

    Q_strncpyz(client->userinfo, userinfo, sizeof(client->userinfo));
    Q_strncpyz(client->name, Info_ValueForKey(userinfo, svUserinfoNameKey),
               SV_USERINFO_NAME_SIZE);
}

void SV_GetUserinfo(int32_t clientNum, char *buffer,
                    int32_t bufferSize)
{
    if (bufferSize < 1) {
        Com_Error(SV_COM_ERROR_DROP, svGetUserinfoBadBufferSizeError,
                  bufferSize);
    }
    if (clientNum < 0 ||
        clientNum >= host_cvar_sv_maxclients_pointer_slot->integer) {
        Com_Error(SV_COM_ERROR_DROP, svGetUserinfoBadIndexError, clientNum);
    }

    Q_strncpyz(buffer, svs.clients[clientNum].userinfo, bufferSize);
}

void SV_CreateBaseline(void)
{
    for (int32_t entityNum = 1; entityNum < sv_gameData.numGEntities;
         ++entityNum) {
        sharedEntity_t *gentity = SV_GentityNum(entityNum);

        if (gentity->linked == qfalse) {
            continue;
        }

        gentity->entityState.s_number = entityNum;
        sv_entityLinks[entityNum].baseline.entityState = gentity->entityState;
        sv_entityLinks[entityNum].baseline.flags = gentity->svFlags;
        sv_entityLinks[entityNum].baseline.ownerNum = gentity->ownerNum;
        for (int32_t axis = 0; axis < 3; ++axis) {
            sv_entityLinks[entityNum].baseline.clipMins[axis] =
                gentity->mins[axis];
            sv_entityLinks[entityNum].baseline.clipMaxs[axis] =
                gentity->maxs[axis];
        }
    }
}

void SV_BoundMaxClients(int32_t minimumClientCount)
{
    Cvar_Get(svMaxclientsCvarName, svMaxclientsDefault, 0);
    host_cvar_sv_maxclients_pointer_slot->modified = qfalse;

    if (host_cvar_sv_maxclients_pointer_slot->integer < minimumClientCount) {
        Cvar_Set(svMaxclientsCvarName, va("%i", minimumClientCount));
    } else if (host_cvar_sv_maxclients_pointer_slot->integer >
               CODUO_MAX_CLIENTS) {
        Cvar_Set(svMaxclientsCvarName, va("%i", CODUO_MAX_CLIENTS));
    }
}

void SV_Startup(void)
{
    if (svs.initialized != qfalse) {
        Com_Error(SV_COM_ERROR_FATAL, svStartupAlreadyInitializedError);
    }

    SV_BoundMaxClients(SV_MIN_STARTUP_CLIENTS);
    svs.clients =
        calloc((size_t)host_cvar_sv_maxclients_pointer_slot->integer,
               sizeof(*svs.clients));
    if (svs.clients == NULL) {
        Com_Error(SV_COM_ERROR_FATAL, svStartupClientsAllocError);
    }

    coduomp_set_snapshot_capacity_for_client_count(
        host_cvar_sv_maxclients_pointer_slot->integer);
    svs.initialized = qtrue;
    Cvar_Set(svRunningCvarName, svRunningEnabled);
}

void SV_ChangeMaxClients(void)
{
    int32_t highestClientCount = 0;

    for (int32_t clientIndex = 0;
         clientIndex < host_cvar_sv_maxclients_pointer_slot->integer;
         ++clientIndex) {
        if (svs.clients[clientIndex].state > CODUO_CS_ZOMBIE &&
            highestClientCount < clientIndex) {
            highestClientCount = clientIndex;
        }
    }
    ++highestClientCount;

    int32_t oldMaxClients = host_cvar_sv_maxclients_pointer_slot->integer;
    SV_BoundMaxClients(highestClientCount);
    if (host_cvar_sv_maxclients_pointer_slot->integer == oldMaxClients) {
        return;
    }

    client_t *oldClients =
        Hunk_AllocateTempMemory(highestClientCount * sizeof(*oldClients));
    for (int32_t clientIndex = 0; clientIndex < highestClientCount;
         ++clientIndex) {
        if (svs.clients[clientIndex].state < CODUO_CS_CONNECTED) {
            Com_Memset(&oldClients[clientIndex], 0, sizeof(oldClients[0]));
        } else {
            oldClients[clientIndex] = svs.clients[clientIndex];
        }
    }

    free(svs.clients);
    svs.clients =
        calloc((size_t)host_cvar_sv_maxclients_pointer_slot->integer,
               sizeof(*svs.clients));
    if (svs.clients == NULL) {
        Com_Error(SV_COM_ERROR_FATAL, svStartupClientsAllocError);
    }

    Com_Memset(svs.clients, 0,
               (size_t)host_cvar_sv_maxclients_pointer_slot->integer *
                   sizeof(*svs.clients));
    for (int32_t clientIndex = 0; clientIndex < highestClientCount;
         ++clientIndex) {
        if (oldClients[clientIndex].state >= CODUO_CS_CONNECTED) {
            svs.clients[clientIndex] = oldClients[clientIndex];
        }
    }

    Hunk_FreeTempMemory(oldClients);
    coduomp_set_snapshot_capacity_for_client_count(
        host_cvar_sv_maxclients_pointer_slot->integer);
}

void SV_SetExpectedHunkUsage(const char *mapBspPath)
{
    int32_t handle;
    int32_t fileLength;

    fileLength = FS_FOpenFileByMode(svHunkUsagePath, &handle, 0);
    if (fileLength < 0) {
        Cvar_Set(svExpectedHunkUsageCvarName, svExpectedHunkUsageUnknown);
        return;
    }

    char *fileBuffer = Z_Malloc((size_t)fileLength + 1);
    memset(fileBuffer, 0, (size_t)fileLength + 1);
    FS_Read(fileBuffer, fileLength, handle);
    FS_FCloseFile(handle);

    char *parseData = fileBuffer;
    for (;;) {
        const char *token = Com_Parse(&parseData);
        if (token == NULL || token[0] == '\0') {
            Z_Free(fileBuffer);
            Cvar_Set(svExpectedHunkUsageCvarName, svExpectedHunkUsageUnknown);
            return;
        }

        if (Q_strcasecmp(token, mapBspPath) == 0) {
            token = Com_Parse(&parseData);
            if (token != NULL && token[0] != '\0') {
                Cvar_Set(svExpectedHunkUsageCvarName, token);
                Z_Free(fileBuffer);
                return;
            }
        }
    }
}

void SV_ClearServer(void)
{
    for (int32_t index = 0; index < CODUO_MAX_CONFIGSTRINGS;
         ++index) {
        if (host_sv_configstrings[index] != NULL) {
            Z_Free(host_sv_configstrings[index]);
            host_sv_configstrings[index] = NULL;
        }
    }

    Com_Memset(&sv, 0, sizeof(sv));
    host_sv_timeResidual = 0;
    Com_Memset(host_sv_configstrings, 0, sizeof(host_sv_configstrings));
    Com_Memset(sv_entityLinks, 0, sizeof(sv_entityLinks));
    Com_Memset(&sv_gameData, 0, sizeof(sv_gameData));
    sv_entityParsePoint = NULL;
    Com_Memset(sv_compressedBpsWindow, 0, sizeof(sv_compressedBpsWindow));
    Com_Memset(sv_uncompressedBpsWindow, 0, sizeof(sv_uncompressedBpsWindow));
    sv_totalBytesSentThisFrame = 0;
    sv_totalUncompressedBytesThisFrame = 0;
    sv_compressedBpsMax = 0;
    sv_uncompressedBpsMax = 0;
    sv_averageBpsFrameCount = 0;
    sv_averageCompressionRatioSum = 0.0f;
    sv_averageCompressionRatioCount = 0;
    Com_Memset(sv_gametypeNormalizeBuffer, 0,
               sizeof(sv_gametypeNormalizeBuffer));
}

void SV_SetSnapshotArchiveEnabled(qboolean enabled)
{
    svs.archiveEnabled = enabled;
    if (enabled == qfalse || svs.archivedSnapshotFrames != NULL) {
        return;
    }

    svs.cachedSnapshotEntities =
        Z_Malloc(sizeof(coduo_cached_snapshot_entity_ring_t));
    svs.cachedSnapshotClients =
        Z_Malloc(sizeof(serverCachedSnapshotClientRing_t));
    svs.archivedSnapshotFrames =
        Z_Malloc(sizeof(archivedSnapshotFrameIndexTable_t));
    svs.archivedSnapshotBuffer =
        Z_Malloc(CODUO_HOST_ARCHIVED_SNAPSHOT_BUFFER_SIZE);
    svs.cachedSnapshotFrames =
        Z_Malloc(sizeof(serverCachedSnapshotFrameRing_t));
}

void SV_ResetSnapshotArchiveCounters(void)
{
    svs.archiveEnabled = qfalse;
    svs.nextArchivedSnapshotFrames = 0;
    svs.nextArchivedSnapshotBuffer = 0;
    svs.nextCachedSnapshotEntities = 0;
    svs.nextCachedSnapshotClients = 0;
    svs.nextCachedSnapshotFrames = 0;
}

void SV_SpawnServer(const char *server)
{
    int32_t mapChecksum[SV_MAP_CHECKSUM_SLOT_COUNT];
    char systemInfo[SV_MAP_SYSTEMINFO_BUFFER_SIZE];
    qboolean restarting;
    uint32_t checksumHigh;
    uint32_t checksumLow;
    uint32_t checksumTime;

    XModelEnforceExist(
        Cvar_Get(svXModelCheckCvarName, svXModelCheckDefault,
                 SV_XMODEL_CHECK_CVAR_FLAGS)
            ->integer);
    Cvar_Get(svGametypeCvarName, svDeathmatchGametype,
             SV_GAMETYPE_CVAR_FLAGS);

    if (Cvar_VariableValue(svRunningCvarName) != 0.0f) {
        restarting = VM_Call(sv_gameVM, CODUO_GAME_GET_MATCH_STATE);
        if (restarting != qfalse) {
            Cvar_Set(svGametypeCvarName, sv_gametypeNormalizeBuffer);
        }

        for (int32_t clientIndex = 0;
             clientIndex < host_cvar_sv_maxclients_pointer_slot->integer;
             ++clientIndex) {
            client_t *client = &svs.clients[clientIndex];

            if (client->state > CODUO_CS_CONNECTED) {
                NET_OutOfBandPrint(NS_SERVER, client->netchan.remoteAddress,
                                   "loadingnewmap\n%s\n%s", server,
                                   host_cvar_g_gametype_pointer_slot->string);
            }
        }

        NET_Sleep(SV_LOADING_RECONNECT_DELAY_MSEC);
    } else {
        restarting = qfalse;
    }

    CL_MapLoading();
    CL_ShutdownAll();
    SV_ShutdownGameProgs();
    Com_Printf("------ Server Initialization ------\n");
    Com_Printf("Server: %s\n", server);
    SV_ShutdownGameProgs();
    Hunk_Clear();
    VM_Clear();
    SV_ClearServer();
    FS_Shutdown(qtrue);
    FS_ClearPakReferences(qfalse);

    srand((uint32_t)Sys_Milliseconds());
    /* These stateful calls occur in this exact order in the stock body. */
    checksumHigh = (uint32_t)rand() << 16;
    checksumLow = (uint32_t)rand();
    checksumTime = (uint32_t)Sys_Milliseconds();
    sv.gamestateChecksumFeed = checksumTime ^ checksumHigh ^ checksumLow;
    FS_Restart(sv.gamestateChecksumFeed);
    FS_RefreshLookupCache();

    for (int32_t index = 0; index < CODUO_MAX_CONFIGSTRINGS;
         ++index) {
        host_sv_configstrings[index] = CopyString(svEmptyString);
    }

    if (Cvar_VariableValue(svRunningCvarName) == 0.0f) {
        Com_InitServerRuntimePools();
        SV_Startup();
    } else {
        DObjInitPool();
        if (host_cvar_sv_maxclients_pointer_slot->modified != qfalse) {
            SV_ChangeMaxClients();
        }
    }

    SV_NormalizeGametypeCvar();
    svs.entityStateSnapshots =
        Hunk_Alloc(svs.numEntityStateSnapshots *
                   (size_t)
                       sizeof(coduo_host_snapshot_entity_record_t));
    svs.nextEntityStateSnapshot = 0;
    svs.clientSnapshots =
        Hunk_Alloc(svs.numClientSnapshots *
                   (size_t)
                       sizeof(coduo_host_client_snapshot_t));
    svs.nextClientSnapshot = 0;
    SV_ResetSnapshotArchiveCounters();
    svs.snapFlagServerBit ^= 4;

    Cvar_Set(svNextmapCvarName, svNextmapRestartValue);
    SV_SetExpectedHunkUsage(va("maps/mp/%s.bsp", server));
    Cvar_Set(svClPausedCvarName, svRunningDisabled);
    CM_LoadMap(va("maps/mp/%s.bsp", server), qfalse, mapChecksum);
    Cvar_Set(svMapnameCvarName, server);

    sv_serverId = (uint8_t)sv_serverId + SV_SERVER_ID_STEP;
    if ((sv_serverId & SV_SERVER_ID_HIGH_NIBBLE_MASK) == 0) {
        sv_serverId += SV_SERVER_ID_STEP;
    }
    Cvar_Set(svServerIdCvarName, va("%i", sv_serverId));
    sv.serverId = sv_serverId_source;
    sv.state = CODUO_SS_LOADING;
    Cvar_Set(svServerRestartingCvarName, va("%i",
                                            SV_SERVER_RESTARTING_CONFIG_TRUE));

    Com_LoadSoundAliasSet(va("maps/mp/%s.bsp", server),
                          SV_SOUND_ALIAS_LOCALE_LOCALIZED);
    XAnimSetUser(XANIM_SECONDARY_POOL_PAYLOAD_SLOT);
    SV_InitGameProgs(restarting);

    for (int32_t frame = 0; frame < SV_SPAWN_WARMUP_FRAME_COUNT; ++frame) {
        svs.timeSecondary += SV_SPAWN_WARMUP_FRAME_MSEC;
        svs.time += SV_SPAWN_WARMUP_FRAME_MSEC;
        SV_RunGameFrame();
    }

    SV_CreateBaseline();

    for (int32_t clientIndex = 0;
         clientIndex < host_cvar_sv_maxclients_pointer_slot->integer;
         ++clientIndex) {
        client_t *client = &svs.clients[clientIndex];

        if (client->state > CODUO_CS_ZOMBIE) {
            intptr_t reject =
                VM_Call(sv_gameVM, CODUO_GAME_CLIENT_CONNECT, clientIndex,
                        client->scriptId);

            if (reject == 0) {
                client->state = CODUO_CS_CONNECTED;
            } else {
                SV_DropClient(client, (const char *)reject);
            }
        }
    }

    if (host_cvar_sv_pure_pointer_slot->integer == 0) {
        Cvar_Set(svPaksCvarName, svEmptyString);
        Cvar_Set(svPakNamesCvarName, svEmptyString);
    } else {
        const char *loadedPakChecksums = FS_LoadedPakChecksums();

        Cvar_Set(svPaksCvarName, loadedPakChecksums);
        if (loadedPakChecksums[0] == '\0') {
            Com_Printf("WARNING: sv_pure set but no PK3 files loaded\n");
        }
        Cvar_Set(svPakNamesCvarName, FS_LoadedPakNames());
    }

    Cvar_Set(svReferencedPaksCvarName, FS_ReferencedPakChecksums());
    Cvar_Set(svReferencedPakNamesCvarName, FS_ReferencedPakNames());

    Q_strncpyz(systemInfo, Cvar_InfoString_Big(CODUO_CVAR_SYSTEMINFO_SYNC_MASK),
               SV_MAP_SYSTEMINFO_COPY_LIMIT);
    cvar_modifiedFlags &= ~CODUO_CVAR_SYSTEMINFO_SYNC_MASK;
    SV_SetConfigstring(CODUO_HOST_CONFIGSTRING_SYSTEMINFO_INDEX, systemInfo);
    SV_SetConfigstring(CODUO_HOST_CONFIGSTRING_SERVERINFO_INDEX,
                       Cvar_InfoString(CODUO_CVAR_SERVERINFO_SYNC_MASK));
    cvar_modifiedFlags &= ~CODUO_CVAR_SERVERINFO_SYNC_MASK;
    Cvar_SetConfigstringValues(CODUO_HOST_CONFIGSTRING_SYSTEMINFO_KV_BASE,
                               CODUO_HOST_CONFIGSTRING_SYSTEMINFO_KV_COUNT,
                               CODUO_CVAR_SYSTEMINFO_KEY_VALUE_SYNC_MASK);
    cvar_modifiedFlags &= ~CODUO_CVAR_SYSTEMINFO_KEY_VALUE_SYNC_MASK;

    sv.state = CODUO_SS_GAME;
    SV_Heartbeat_f();
    Cvar_Set(svServerRestartingCvarName, svRunningDisabled);
    Com_Printf("-----------------------------------\n");

    if (Cvar_VariableString(svPunkBusterCvarName)[0] == '1') {
        PB_NotifyServerEnabled();
    } else {
        PB_NotifyServerDisabled();
    }
}

void SV_FreeSnapshotArchive(void)
{
    if (svs.cachedSnapshotEntities != NULL) {
        Z_Free(svs.cachedSnapshotEntities);
        svs.cachedSnapshotEntities = NULL;
    }
    if (svs.cachedSnapshotClients != NULL) {
        Z_Free(svs.cachedSnapshotClients);
        svs.cachedSnapshotClients = NULL;
    }
    if (svs.archivedSnapshotFrames != NULL) {
        Z_Free(svs.archivedSnapshotFrames);
        svs.archivedSnapshotFrames = NULL;
    }
    if (svs.archivedSnapshotBuffer != NULL) {
        Z_Free(svs.archivedSnapshotBuffer);
        svs.archivedSnapshotBuffer = NULL;
    }
    if (svs.cachedSnapshotFrames != NULL) {
        Z_Free(svs.cachedSnapshotFrames);
        svs.cachedSnapshotFrames = NULL;
    }
}

void SV_NormalizeGametypeCvar(void)
{
    Q_strncpyz(sv_gametypeNormalizeBuffer,
               Cvar_VariableString(svGametypeCvarName),
               SV_GAMETYPE_BUFFER_SIZE);

    for (char *cursor = sv_gametypeNormalizeBuffer; cursor[0] != '\0';
         ++cursor) {
        cursor[0] =
            (char)tolower(coduo_ctype_signed_byte_arg(cursor[0]));
    }

    Cvar_Set(svGametypeCvarName, sv_gametypeNormalizeBuffer);
    Cvar_ClearScriptSetServerinfoFlags();
}
