#include "coduo_engine_structs.h"
#include "coduo_ctype_compat.h"
#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */
#include "coduo_int32_bits.h"
#include "../abi/vm_private.h"
#include "../animation/xanim_private.h"
#include "../core_command/cmd_private.h"
#include "../core_cvar/cvar_private.h"
#include "../core_info/info_private.h"
#include "../core_math/core_math_private.h"
#include "../core_runtime/core_runtime_private.h"
#include "../filesystem/fs_private.h"
#include "../networking/netchan_private.h"
#include "../scripting/script_memory_private.h"
#include "../scripting/script_runtime_private.h"
#include "../scripting/script_variable_private.h"
#include "server_private.h"
#include "sv_connect_authorize_private.h"
#include "sv_game_lifecycle_private.h"
#include "sv_globals_private.h"
#include "sv_init_shutdown_private.h"
#include "sv_master_private.h"
#include "sv_server_commands_private.h"

#if defined(_WIN32)
#include <winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <ctype.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

enum {
    SV_OP_COM_ERROR_DROP = 1,
    SV_OP_MAPNAME_BUFFER_SIZE = 64,
    SV_OP_GAMETYPE_BUFFER_SIZE = 16,
    SV_OP_PLAYER_NAME_BUFFER_SIZE = 64,
    SV_OP_CHAT_BUFFER_SIZE = 1024,
    SV_OP_CLIENT_INFO_BUFFER_SIZE = 0x68,
    SV_OP_PUNKBUSTER_GUID_OFFSET = 0x21,
    SV_OP_CLIENT_ADDRESS_OFFSET = 0x42,
    SV_OP_AUTHORIZE_TIMEOUT_MSEC = 5000,
    SV_OP_AUTHORIZE_PORT = 20600,
    SV_OP_STATUS_MAX_PING = 9999,
    SV_OP_SNAPSHOT_MSEC_DEFAULT = 50,
    SV_OP_SNAPS_MIN = 1,
    SV_OP_SNAPS_MAX = 30,
    SV_OP_RATE_DEFAULT = 5000,
    SV_OP_RATE_MIN = 1000,
    SV_OP_RATE_MAX = 90000,
    SV_OP_RATE_LOCAL = 99999,
    SV_OP_RESTART_WARMUP_FRAMES = 3,
    SV_OP_RESTART_WARMUP_MSEC = 100,
    SV_OP_SERVER_ID_LOW_MASK = 0x0f,
    SV_OP_SERVER_ID_HIGH_MASK = 0xf0,
    SV_OP_SERVER_ID_SNAP_FLAG = 4,
    SV_OP_DEDICATED_SERVER = 2
};

typedef void (*sv_operator_command_fn_t)(void);

static int32_t svOperatorCommandsInitialized;
static const char svAuthorizeServerName[] = "coduoauthorize.activision.com";

void Scr_FreeValue(uint16_t handle);

/*
 * Mac MP symbols name this lean helper cluster GetLeanFraction,
 * UnGetLeanFraction, AddLeanToPosition, and Q_fabs; the Linux bodies match the
 * lean fraction math and AngleVectors-based origin offset.
 */
static float Q_fabs(float value)
{
    return (float)fabsl((long double)value);
}

long double GetLeanFraction(float value)
{
    long double magnitude = Q_fabs(value);

    return ((long double)2.0 - magnitude) * (long double)value;
}

#if EMULATE_X87
/* NOT_FROM_ORIGINAL_SOURCE:
 * 80-bit variant: (2 - |value|) * value. AddLeanToPosition rounds the result to
 * float, so the 80-bit intermediate matters. This is the native non-x87
 * representation adapter for the original GetLeanFraction
 * expression. */
static x87f coduo_engine_get_lean_fraction_x87(float value)
{
    return x87f_mul(
        x87f_sub(x87f_load_f32(2.0f), x87f_abs(x87f_load_f32(value))),
        x87f_load_f32(value));
}
#endif

/* NOTE: no engine callers; long double return can't carry 80 bits on arm64.
 * Left un-emulated (1.0-value and 1.0-root are single subs). */
long double UnGetLeanFraction(float value)
{
    double radicand =
        (double)((long double)1.0 - (long double)value);
    float root = (float)sqrt(radicand);

    return (long double)1.0 - (long double)root;
}

void AddLeanToPosition(vec3_t origin, float yaw, float value,
                       float scale, float length)
{
    if (value != 0.0f || isnan(value)) {
        /* GetLeanFraction rounded to float here; fraction*scale and
         * fraction*length are single muls (native); origin[k] += right[k]*
         * distance are MAs -> shim. */
#if EMULATE_X87
        float fraction =
            x87f_store_f32(coduo_engine_get_lean_fraction_x87(value));
#else
        float fraction = (float)GetLeanFraction(value);
#endif
        vec3_t angles = {0.0f, yaw, fraction * scale};
        vec3_t right;
        float distance = fraction * length;

        AngleVectors(angles, NULL, right, NULL);
#if EMULATE_X87
        for (int32_t k = 0; k < 3; k++) {
            origin[k] = x87f_store_f32(x87f_add(
                x87f_mul(x87f_load_f32(right[k]), x87f_load_f32(distance)),
                x87f_load_f32(origin[k])));
        }
#else
        origin[0] += right[0] * distance;
        origin[1] += right[1] * distance;
        origin[2] += right[2] * distance;
#endif
    }
}

void OrientationPosToWorldPos(const orientation_t *matrix,
                              const vec3_t in, vec3_t out)
{
    /* Stock 0x8087ac5 sums in[0] term + origin first, then in[1], in[2]; the
     * 4-term sum stays 80-bit, one store per component -> shim. */
#if EMULATE_X87
    for (int32_t k = 0; k < 3; k++) {
        out[k] = x87f_store_f32(x87f_add(x87f_add(x87f_add(
            x87f_mul(x87f_load_f32(in[0]), x87f_load_f32(matrix->axis[0][k])),
            x87f_load_f32(matrix->origin[k])),
            x87f_mul(x87f_load_f32(in[1]), x87f_load_f32(matrix->axis[1][k]))),
            x87f_mul(x87f_load_f32(in[2]), x87f_load_f32(matrix->axis[2][k]))));
    }
#else
    out[0] = in[0] * matrix->axis[0][0] + matrix->origin[0] +
             in[1] * matrix->axis[1][0] + in[2] * matrix->axis[2][0];
    out[1] = in[0] * matrix->axis[0][1] + matrix->origin[1] +
             in[1] * matrix->axis[1][1] + in[2] * matrix->axis[2][1];
    out[2] = in[0] * matrix->axis[0][2] + matrix->origin[2] +
             in[1] * matrix->axis[1][2] + in[2] * matrix->axis[2][2];
#endif
}

void OrientationDirToWorldDir(const orientation_t *matrix,
                              const vec3_t in, vec3_t out)
{
    /* Stock 0x8087b6f sums the in[0] term first, then in[1], in[2]; 3-mul/2-add
     * dot stays 80-bit, one store per component -> shim. */
#if EMULATE_X87
    for (int32_t k = 0; k < 3; k++) {
        out[k] = x87f_store_f32(x87f_add(x87f_add(
            x87f_mul(x87f_load_f32(in[0]), x87f_load_f32(matrix->axis[0][k])),
            x87f_mul(x87f_load_f32(in[1]), x87f_load_f32(matrix->axis[1][k]))),
            x87f_mul(x87f_load_f32(in[2]), x87f_load_f32(matrix->axis[2][k]))));
    }
#else
    out[0] = in[0] * matrix->axis[0][0] + in[1] * matrix->axis[1][0] +
             in[2] * matrix->axis[2][0];
    out[1] = in[0] * matrix->axis[0][1] + in[1] * matrix->axis[1][1] +
             in[2] * matrix->axis[2][1];
    out[2] = in[0] * matrix->axis[0][2] + in[1] * matrix->axis[1][2] +
             in[2] * matrix->axis[2][2];
#endif
}

void OrientationPosFromWorldPos(const orientation_t *matrix,
                                const vec3_t in, vec3_t out)
{
    vec3_t delta;

    delta[0] = in[0] - matrix->origin[0];
    delta[1] = in[1] - matrix->origin[1];
    delta[2] = in[2] - matrix->origin[2];
    /* Stock 0x8087c39 sums the delta[0] term first, then delta[1], delta[2]. */
    out[0] = delta[0] * matrix->axis[0][0] + delta[1] * matrix->axis[0][1] +
             delta[2] * matrix->axis[0][2];
    out[1] = delta[0] * matrix->axis[1][0] + delta[1] * matrix->axis[1][1] +
             delta[2] * matrix->axis[1][2];
    out[2] = delta[0] * matrix->axis[2][0] + delta[1] * matrix->axis[2][1] +
             delta[2] * matrix->axis[2][2];
}

void OrientationDirFromWorldDir(const orientation_t *matrix,
                                const vec3_t in, vec3_t out)
{
    /* Stock 0x8087cb3 sums the in[0] term first, then in[1], in[2]. */
    out[0] = in[0] * matrix->axis[0][0] + in[1] * matrix->axis[0][1] +
             in[2] * matrix->axis[0][2];
    out[1] = in[0] * matrix->axis[1][0] + in[1] * matrix->axis[1][1] +
             in[2] * matrix->axis[1][2];
    out[2] = in[0] * matrix->axis[2][0] + in[1] * matrix->axis[2][1] +
             in[2] * matrix->axis[2][2];
}

int Q_GetDecimalDelimiter(int locale)
{
    (void)locale;

    return '.';
}

void Q_LocalizedFloatToString(float value, char *buffer,
                              uint32_t bufferSize, int precision,
                              int locale)
{
    char decimal;

    /* ORIGINAL_BINARY_BUG: 0x08087d68..0x08087d82 turns a zero size into
     * UINT32_MAX for snprintf and then stores the terminator at buffer[-1];
     * see original-bugs/coduomp-localized-float-zero-size-underwrite.md. */
    snprintf(buffer, bufferSize - 1, "%.*f", precision, (double)value);
    buffer[bufferSize - 1] = '\0';
    decimal = (char)Q_GetDecimalDelimiter(locale);
    if (decimal != '.') {
        for (uint32_t index = 0; index < bufferSize; ++index) {
            if (buffer[index] == '.') {
                buffer[index] = decimal;
                return;
            }
        }
    }
}

static int32_t coduomp_sv_client_num_for_client(
    const client_t *client)
{
    /* NOT_FROM_ORIGINAL_SOURCE: readable form of stock client-slot stride math. */
    return (int32_t)(client - svs.clients);
}

client_t *SV_GetPlayerByName(void)
{
    const char *name;

    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        return NULL;
    }

    if (Cmd_Argc() < 2) {
        Com_Printf("No player specified.\n");
        return NULL;
    }

    name = Cmd_Argv(1);
    for (int clientNum = 0;
         clientNum < host_cvar_sv_maxclients_pointer_slot->integer;
         ++clientNum) {
        client_t *client = &svs.clients[clientNum];
        char cleanName[SV_OP_PLAYER_NAME_BUFFER_SIZE];

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

        if (Q_stricmp(client->name, name) == 0) {
            return client;
        }

        Q_strncpyz(cleanName, client->name, sizeof(cleanName));
        Q_CleanStr(cleanName);
        if (Q_stricmp(cleanName, name) == 0) {
            return client;
        }
    }

    Com_Printf("Player %s is not on the server\n", name);
    return NULL;
}

client_t *SV_GetPlayerByNum(void)
{
    const char *slotText;

    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        return NULL;
    }

    if (Cmd_Argc() < 2) {
        Com_Printf("No player specified.\n");
        return NULL;
    }

    slotText = Cmd_Argv(1);
    for (int index = 0; slotText[index] != '\0'; ++index) {
        if (slotText[index] < '0' || slotText[index] > '9') {
            Com_Printf("Bad slot number: %s\n", slotText);
            return NULL;
        }
    }

    int clientNum = atoi(slotText);
    if (clientNum < 0 ||
        clientNum >= host_cvar_sv_maxclients_pointer_slot->integer) {
        Com_Printf("Bad client slot: %i\n", clientNum);
        return NULL;
    }

    if (svs.clients[clientNum].state == CODUO_CS_FREE) {
        Com_Printf("Client %i is not active\n", clientNum);
        return NULL;
    }

    return &svs.clients[clientNum];
}

const char *SV_GetMapBaseName(const char *mapName)
{
    char stripped[SV_OP_MAPNAME_BUFFER_SIZE];
    size_t length;

    if (Q_stricmpn(mapName, "mp", 2) == 0 &&
        (mapName[2] == '/' || mapName[2] == '\\')) {
        mapName += 3;
    }

    length = strlen(mapName);
    /* ORIGINAL_BINARY_BUG: 0x08088055..0x08088071 reads a suffix beginning
     * four bytes before short inputs, and 0x08088090..0x080880ab copies an
     * unrestricted length into this 64-byte array; see
     * original-bugs/coduomp-server-map-command-name-bounds.md. */
    if (strcmp(mapName + length - 4, ".bsp") == 0) {
        length -= 4;
    }

    memcpy(stripped, mapName, length);
    stripped[length] = '\0';
    return va("%s", stripped);
}

void SV_MapRestart_f(void);

void SV_Map_f(void)
{
    const char *arg = Cmd_Argv(1);
    char mapName[SV_OP_MAPNAME_BUFFER_SIZE];
    const char *cleanMapName;
    const char *bspPath;
    const char *command;
    qboolean devmap;

    if (arg[0] == '\0') {
        return;
    }

    /* ORIGINAL_BINARY_BUG: 0x08088115..0x08088126 copies the unrestricted
     * map argument into this 64-byte array; see
     * original-bugs/coduomp-server-map-command-name-bounds.md. */
    strcpy(mapName, arg);
    Q_strlwr(mapName);
    cleanMapName = SV_GetMapBaseName(mapName);
    bspPath = va("maps/mp/%s.bsp", cleanMapName);
    if (FS_ReadFile(bspPath, NULL) == -1) {
        Com_Printf("Can't find map %s\n", bspPath);
        return;
    }

    command = Cmd_Argv(0);
    devmap = Q_stricmp(command, "devmap") == 0;
    if (host_cvar_sv_running_pointer_slot->integer != 0 &&
        host_cvar_timescale_pointer_slot->value <= 0.0f) {
        Cvar_SetValue("timescale", 1.0f);
    }

    if (Cmd_Argc() < 3 ||
        (Cmd_Argv(2) != NULL &&
         Q_stricmp(Cmd_Argv(2), "noautoexec") != 0)) {
        char gametype[SV_OP_GAMETYPE_BUFFER_SIZE];

        Q_strncpyz(gametype, Cvar_VariableString("g_gametype"),
                     SV_OP_GAMETYPE_BUFFER_SIZE);
        for (char *scan = gametype; *scan != '\0'; ++scan) {
            *scan = (char)tolower(coduo_ctype_signed_byte_arg(*scan));
        }
        Cbuf_ExecuteText(CODUO_EXEC_NOW,
                         va("exec %s_%s.cfg\n", cleanMapName, gametype));
    }

    if (host_cvar_sv_running_pointer_slot->integer == 0 ||
        Q_stricmp(cleanMapName, host_cvar_mapname_pointer_slot->string) != 0) {
        char spawnMap[SV_OP_MAPNAME_BUFFER_SIZE];

        Q_strncpyz(spawnMap, cleanMapName, SV_OP_MAPNAME_BUFFER_SIZE);
        SV_SpawnServer(spawnMap);
    } else {
        SV_MapRestart_f();
    }

    Cvar_Set("sv_cheats", devmap ? "1" : "0");
}

void SV_MapRestart_f(void)
{
    qboolean cvarRestartGate;

    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        Com_Printf("Server is not running.\n");
        return;
    }

    if (host_cvar_timescale_pointer_slot->value <= 0.0f) {
        Cvar_SetValue("timescale", 1.0f);
    }

    Cvar_Get("g_gametype", "dm", CODUO_CVAR_SERVERINFO | CODUO_CVAR_LATCH);
    cvarRestartGate =
        VM_Call(sv_gameVM, CODUO_GAME_CONSOLE_COMMAND) != 0;
    if (cvarRestartGate == qfalse) {
        if (Q_stricmp(sv_gametypeNormalizeBuffer,
                      host_cvar_g_gametype_pointer_slot->string) != 0) {
            char mapName[SV_OP_MAPNAME_BUFFER_SIZE];

            Com_Printf("g_gametype variable change -- restarting.\n");
            Q_strncpyz(mapName, Cvar_VariableString("mapname"),
                         SV_OP_MAPNAME_BUFFER_SIZE);
            SV_SpawnServer(mapName);
            return;
        }
        if (host_cvar_sv_maxclients_pointer_slot->modified != 0) {
            char mapName[SV_OP_MAPNAME_BUFFER_SIZE];

            Com_Printf("sv_maxclients variable change -- restarting.\n");
            Q_strncpyz(mapName, Cvar_VariableString("mapname"),
                         SV_OP_MAPNAME_BUFFER_SIZE);
            SV_SpawnServer(mapName);
            return;
        }
    } else {
        Cvar_Set("g_gametype", sv_gametypeNormalizeBuffer);
    }

    if (sv.serverId == sv_serverId_source) {
        return;
    }

    SV_NormalizeGametypeCvar();
    SV_ResetSnapshotArchiveCounters();
    svs.snapFlagServerBit ^= SV_OP_SERVER_ID_SNAP_FLAG;
    sv_serverId =
        ((sv_serverId + 1) & SV_OP_SERVER_ID_LOW_MASK) +
        (sv_serverId & SV_OP_SERVER_ID_HIGH_MASK);
    Cvar_Set("sv_serverid", va("%i", sv_serverId));
    sv.serverId = sv_serverId_source;
    sv.state = CODUO_SS_LOADING;
    sv.restarting = qtrue;
    Cvar_Set("sv_serverRestarting", "1");
    XAnimSetUser(XANIM_SECONDARY_POOL_PAYLOAD_SLOT);
    SV_RestartGameProgs(cvarRestartGate);

    for (int frame = 0; frame < SV_OP_RESTART_WARMUP_FRAMES; ++frame) {
        svs.timeSecondary += SV_OP_RESTART_WARMUP_MSEC;
        svs.time += SV_OP_RESTART_WARMUP_MSEC;
        SV_RunGameFrame();
    }

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

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

        SV_AddServerCommand(client, qtrue, cvarRestartGate ? "n" : "B");
        intptr_t rejectRaw = VM_Call(
            sv_gameVM, CODUO_GAME_CLIENT_CONNECT, clientNum,
            client->scriptId);
        const char *reject = (const char *)rejectRaw;
        if (reject == NULL) {
            if (client->state == CODUO_CS_ACTIVE) {
                SV_ClientEnterWorld(client, &client->usercmd);
            }
        } else {
            SV_DropClient(client, reject);
            Com_Printf("SV_MapRestart_f: dropped client %i - denied!\n",
                       clientNum);
        }
    }

    sv.state = CODUO_SS_GAME;
    sv.restarting = qfalse;
    Cvar_Set("sv_serverRestarting", "0");
}

/* NOT_FROM_ORIGINAL_SOURCE: factored map-rotation token parser. */
static const char *coduomp_sv_map_rotation_next_token(void)
{
    char *parseData =
        (char *)host_cvar_sv_mapRotationCurrent_pointer_slot->string;
    char *token = Com_Parse(&parseData);

    if (parseData == NULL) {
        Cvar_Set("sv_mapRotationCurrent", "");
        return NULL;
    }

    char mapRotationTail[strlen(parseData) + 1];
    strcpy(mapRotationTail, parseData);
    Cvar_Set("sv_mapRotationCurrent", mapRotationTail);
    return token;
}

const char *SV_GetMapRotationToken(void)
{
    return coduomp_sv_map_rotation_next_token();
}

void SV_MapRotate_f(void)
{
    const char *token;
    qboolean execSeen = qfalse;

    Com_Printf("map_rotate...\n\n");
    Com_Printf("\"sv_mapRotation\" is:\"%s\"\n\n",
               host_cvar_sv_mapRotation_pointer_slot->string);
    Com_Printf("\"sv_mapRotationCurrent\" is:\"%s\"\n\n",
               host_cvar_sv_mapRotationCurrent_pointer_slot->string);

    if (host_cvar_sv_mapRotationCurrent_pointer_slot->string[0] == '\0') {
        Cvar_Set("sv_mapRotationCurrent",
                 host_cvar_sv_mapRotation_pointer_slot->string);
    }

    token = SV_GetMapRotationToken();
    if (token == NULL) {
        Cvar_Set("sv_mapRotationCurrent",
                 host_cvar_sv_mapRotation_pointer_slot->string);
        token = SV_GetMapRotationToken();
    }

    while (1) {
        if (token == NULL) {
            Com_Printf("No map specified in sv_mapRotation - forcing map_restart.\n");
            SV_MapRestart_f();
            return;
        }

        if (Q_stricmp(token, "exec") == 0) {
            token = SV_GetMapRotationToken();
            if (token == NULL) {
                Com_Printf("No value specified after 'exec' keyword in sv_mapRotation - forcing map_restart.\n");
                SV_MapRestart_f();
                return;
            }
            Com_Printf("Execing: %s.\n", token);
            Cbuf_ExecuteText(CODUO_EXEC_NOW, va("exec %s\n", token));
            execSeen = qtrue;
        } else if (Q_stricmp(token, "allow_jeeps") == 0) {
            token = SV_GetMapRotationToken();
            if (token == NULL) {
                Com_Printf("No value specified after 'allow_jeeps' keyword in sv_mapRotation - forcing map_restart.\n");
                SV_MapRestart_f();
                return;
            }
            Com_Printf("Setting scr_allow_jeeps: %s.\n", token);
            if (host_cvar_sv_running_pointer_slot->integer != 0 &&
                Q_stricmp(host_cvar_scr_allow_jeeps_pointer_slot->string,
                          token) != 0) {
                VM_Call(sv_gameVM, CODUO_GAME_SET_MATCH_STATE, 0);
            }
            Cvar_Set("scr_allow_jeeps", token);
        } else if (Q_stricmp(token, "allow_tanks") == 0) {
            token = SV_GetMapRotationToken();
            if (token == NULL) {
                Com_Printf("No value specified after 'allow_tanks' keyword in sv_mapRotation - forcing map_restart.\n");
                SV_MapRestart_f();
                return;
            }
            Com_Printf("Setting scr_allow_tanks: %s.\n", token);
            if (host_cvar_sv_running_pointer_slot->integer != 0 &&
                Q_stricmp(host_cvar_scr_allow_tanks_pointer_slot->string,
                          token) != 0) {
                VM_Call(sv_gameVM, CODUO_GAME_SET_MATCH_STATE, 0);
            }
            Cvar_Set("scr_allow_tanks", token);
        } else if (Q_stricmp(token, "gametype") == 0) {
            token = SV_GetMapRotationToken();
            if (token == NULL) {
                Com_Printf("No gametype specified after 'gametype' keyword in sv_mapRotation - forcing map_restart.\n");
                SV_MapRestart_f();
                return;
            }
            Com_Printf("Setting g_gametype: %s.\n", token);
            if (host_cvar_sv_running_pointer_slot->integer != 0 &&
                Q_stricmp(host_cvar_g_gametype_pointer_slot->string, token) !=
                    0) {
                VM_Call(sv_gameVM, CODUO_GAME_SET_MATCH_STATE, 0);
            }
            Cvar_Set("g_gametype", token);
        } else if (Q_stricmp(token, "map") == 0) {
            token = SV_GetMapRotationToken();
            if (token == NULL) {
                Com_Printf("No map specified after 'map' keyword in sv_mapRotation - forcing map_restart.\n");
                SV_MapRestart_f();
                return;
            }
            Com_Printf("Setting map: %s.\n", token);
            Cbuf_ExecuteText(CODUO_EXEC_NOW,
                             execSeen ? va("map %s noautoexec\n", token)
                                      : va("map %s\n", token));
            return;
        } else {
            Com_Printf("Unknown keyword '%s' in sv_mapRotation.\n", token);
        }

        token = SV_GetMapRotationToken();
    }
}

int32_t
SV_KickClient(client_t *client, char *nameOut, int nameOutSize)
{
    if (client->netchan.remoteAddress.type == CODUO_NA_LOOPBACK) {
        SV_SendServerCommand(NULL, qfalse, "e \"EXE_CANNOTKICKHOSTPLAYER\"");
        return 0;
    }

    if (nameOut != NULL) {
        Q_strncpyz(nameOut, client->name, nameOutSize);
        Q_CleanStr(nameOut);
    }

    int32_t guid = client->guid;
    SV_DropClient(client, "EXE_PLAYERKICKED");
    client->lastPacketTime = svs.timeSecondary;
    return guid;
}

int32_t SV_KickUser_f(char *nameOut, int nameOutSize)
{
    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        Com_Printf("Server is not running.\n");
        return 0;
    }

    if (Cmd_Argc() != 2) {
        const char *command = Cmd_Argv(0);

        Com_Printf("Usage: %s <player name>\n%s all = kick everyone\n",
                   command, command);
        return 0;
    }

    client_t *client = SV_GetPlayerByName();
    if (client == NULL) {
        if (Q_stricmp(Cmd_Argv(1), "all") == 0) {
            for (int clientNum = 0;
                 clientNum < host_cvar_sv_maxclients_pointer_slot->integer;
                 ++clientNum) {
                if (svs.clients[clientNum].state != CODUO_CS_FREE) {
                    SV_KickClient(&svs.clients[clientNum], NULL, 0);
                }
            }
        }
        return 0;
    }

    return SV_KickClient(client, nameOut, nameOutSize);
}

int32_t SV_KickClient_f(char *nameOut, int nameOutSize)
{
    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        Com_Printf("Server is not running.\n");
        return 0;
    }

    if (Cmd_Argc() != 2) {
        Com_Printf("Usage: %s <client number>\n", Cmd_Argv(0));
        return 0;
    }

    client_t *client = SV_GetPlayerByNum();
    return client == NULL ? 0 : SV_KickClient(client, nameOut, nameOutSize);
}

void SV_BanClient(client_t *client)
{
    if (client->netchan.remoteAddress.type == CODUO_NA_LOOPBACK) {
        SV_SendServerCommand(NULL, qfalse, "e \"EXE_CANNOTKICKHOSTPLAYER\"");
        return;
    }

    if (client->guid == 0 || SV_AuthorizeGuidOnBanList(client->guid)) {
        return;
    }

    int32_t handle;
    if (FS_FOpenFileByMode("ban.txt", &handle, CODUO_FS_APPEND) < 0) {
        return;
    }

    char cleanName[SV_OP_PLAYER_NAME_BUFFER_SIZE];
    Q_strncpyz(cleanName, client->name, SV_OP_PLAYER_NAME_BUFFER_SIZE);
    Q_CleanStr(cleanName);
    FS_Printf(handle, "%i %s\r\n", client->guid, cleanName);
    FS_FCloseFile(handle);
    SV_DropClient(client, "EXE_PLAYERKICKED");
    client->lastPacketTime = svs.timeSecondary;
}

void SV_UnbanClient(const char *name)
{
    void *fileBuffer;
    int fileLength = FS_ReadFile("ban.txt", &fileBuffer);
    char *fileText = fileBuffer;
    int removed = 0;
    char cleanName[SV_OP_PLAYER_NAME_BUFFER_SIZE];
    size_t cleanLength;
    char *scan;

    if (fileLength < 0) {
        return;
    }

    Q_strncpyz(cleanName, name, SV_OP_PLAYER_NAME_BUFFER_SIZE);
    Q_CleanStr(cleanName);
    cleanLength = strlen(cleanName);
    scan = fileText;

    while (1) {
        char *lineStart = scan;
        char *token = Com_Parse(&scan);
        qboolean removeLine = qfalse;

        if (token[0] == '\0') {
            break;
        }

        while (*scan != '\0' && *scan < '!') {
            ++scan;
        }
        if (strncasecmp(scan, cleanName, cleanLength) == 0 &&
            (scan[cleanLength] == '\r' || scan[cleanLength] == '\n')) {
            removeLine = qtrue;
        }

        Com_SkipRestOfLine(&scan);
        if (removeLine) {
            ++removed;
            memmove(lineStart, scan,
                    (size_t)(fileLength - (int)(scan - fileText)) + 1);
            fileLength -= (int)(scan - lineStart);
            scan = lineStart;
        }
    }

    FS_WriteFile("ban.txt", fileText,
                 (int32_t)fileLength);
    FS_FreeFile(fileText);

    if (removed == 0) {
        Com_Printf("no banned user has name %s\n", cleanName);
    } else {
        Com_Printf("unbanned %i user(s) named %s\n", removed, cleanName);
    }
}

void SV_TempBan_f(void)
{
    char name[SV_OP_PLAYER_NAME_BUFFER_SIZE];
    int32_t guid =
        SV_KickUser_f(name, SV_OP_PLAYER_NAME_BUFFER_SIZE);

    if (guid != 0) {
        Com_Printf("%s (guid %i) was kicked for cheating\n", name, guid);
        SV_AuthorizeGuidCacheStore(guid);
    }
}

void SV_Ban_f(void)
{
    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        Com_Printf("Server is not running.\n");
        return;
    }

    if (Cmd_Argc() != 2) {
        Com_Printf("Usage: banUser <player name>\n");
        return;
    }

    client_t *client = SV_GetPlayerByName();
    if (client != NULL) {
        SV_BanClient(client);
    }
}

void SV_BanNum_f(void)
{
    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        Com_Printf("Server is not running.\n");
        return;
    }

    if (Cmd_Argc() != 2) {
        Com_Printf("Usage: banClient <client number>\n");
        return;
    }

    client_t *client = SV_GetPlayerByNum();
    if (client != NULL) {
        SV_BanClient(client);
    }
}

void SV_Unban_f(void)
{
    if (Cmd_Argc() == 2) {
        SV_UnbanClient(Cmd_Argv(1));
    } else {
        Com_Printf("Usage: unban <client name>\n");
    }
}

void SV_Drop_f(void)
{
    (void)SV_KickUser_f(NULL, 0);
}

void SV_DropNum_f(void)
{
    (void)SV_KickClient_f(NULL, 0);
}

void SV_TempBanNum_f(void)
{
    char name[SV_OP_PLAYER_NAME_BUFFER_SIZE];
    int32_t guid =
        SV_KickClient_f(name, SV_OP_PLAYER_NAME_BUFFER_SIZE);

    if (guid != 0) {
        Com_Printf("%s (guid %i) was kicked for cheating\n", name, guid);
        SV_AuthorizeGuidCacheStore(guid);
    }
}

void SV_Status_f(void)
{
    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        Com_Printf("Server is not running.\n");
        return;
    }

    Com_Printf("map: %s\n", host_cvar_mapname_pointer_slot->string);
    Com_Printf("num score ping guid   name            lastmsg address               qport rate\n");
    Com_Printf("--- ----- ---- ------ --------------- ------- --------------------- ----- -----\n");

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

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

        Com_Printf("%3i ", clientNum);
        Com_Printf("%5i ", SV_GetClientScore(client));
        if (client->state == CODUO_CS_CONNECTED) {
            Com_Printf("CNCT ");
        } else if (client->state == CODUO_CS_ZOMBIE) {
            Com_Printf("ZMBI ");
        } else {
            int ping = client->ping;
            if (ping > SV_OP_STATUS_MAX_PING) {
                ping = SV_OP_STATUS_MAX_PING;
            }
            Com_Printf("%4i ", ping);
        }
        Com_Printf("%6i ", client->guid);
        Com_Printf("%s^7", client->name);
        for (int pad = Q_DrawStrlen(client->name); pad < 16; ++pad) {
            Com_Printf(" ");
        }
        Com_Printf("%7i ", svs.timeSecondary - client->lastPacketTime);

        const char *address = NET_AdrToString(client->netchan.remoteAddress);
        Com_Printf("%s", address);
        for (int pad = (int)strlen(address); pad < 22; ++pad) {
            Com_Printf(" ");
        }
        Com_Printf("%5i", client->netchan.qport);
        Com_Printf(" %5i", client->rate);
        Com_Printf("\n");
    }
    Com_Printf("\n");
}

void SV_ConSay_f(void)
{
    char text[SV_OP_CHAT_BUFFER_SIZE];
    const char *message;

    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        Com_Printf("Server is not running.\n");
        return;
    }

    if (Cmd_Argc() <= 1) {
        return;
    }

    strcpy(text, "console: ");
    message = Cmd_Args(1);
    if (message[0] == '"') {
        char *mutableMessage = (char *)message + 1;

        mutableMessage[strlen(mutableMessage) - 1] = '\0';
        message = mutableMessage;
    }
    /* ORIGINAL_BINARY_BUG: 0x080891e1..0x080891ee appends the complete
     * command text after the prefix without checking this 1,024-byte array;
     * see original-bugs/coduomp-server-console-chat-prefix-overflow.md. */
    strcat(text, message);
    SV_SendServerCommand(NULL, qfalse, "h \"\x15%s\"", text);
}

void SV_ConTell_f(void)
{
    char text[SV_OP_CHAT_BUFFER_SIZE];
    const char *message;
    int clientNum;

    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        Com_Printf("Server is not running.\n");
        return;
    }

    if (Cmd_Argc() <= 2) {
        return;
    }

    clientNum = atoi(Cmd_Argv(1));
    if (clientNum < 0 ||
        clientNum >= host_cvar_sv_maxclients_pointer_slot->integer ||
        svs.clients[clientNum].state != CODUO_CS_ACTIVE) {
        return;
    }

    strcpy(text, "console: ");
    message = Cmd_Args(2);
    if (message[0] == '"') {
        char *mutableMessage = (char *)message + 1;

        mutableMessage[strlen(mutableMessage) - 1] = '\0';
        message = mutableMessage;
    }
    /* ORIGINAL_BINARY_BUG: 0x080892fa..0x08089307 repeats the unbounded
     * prefixed append; see
     * original-bugs/coduomp-server-console-chat-prefix-overflow.md. */
    strcat(text, message);
    SV_SendServerCommand(&svs.clients[clientNum], qfalse,
                         "h \"\x15%s\"", text);
}

void SV_Heartbeat_f(void)
{
    svs.nextHeartbeatTime = INT32_MIN;
}

void SV_Serverinfo_f(void)
{
    Com_Printf("Server info settings:\n");
    Info_Print(Cvar_InfoString(CODUO_CVAR_SERVERINFO |
                                 CODUO_CVAR_SCRIPT_SETCVAR_SERVERINFO));
}

void SV_Systeminfo_f(void)
{
    Com_Printf("System info settings:\n");
    Info_Print(Cvar_InfoString(CODUO_CVAR_SYSTEMINFO));
}

void SV_DumpUser_f(void)
{
    if (host_cvar_sv_running_pointer_slot->integer == 0) {
        Com_Printf("Server is not running.\n");
        return;
    }

    if (Cmd_Argc() != 2) {
        Com_Printf("Usage: info <userid>\n");
        return;
    }

    client_t *client = SV_GetPlayerByName();
    if (client != NULL) {
        Com_Printf("userinfo\n");
        Com_Printf("--------\n");
        Info_Print(client->userinfo);
    }
}

void SV_KillServer_f(void)
{
    Com_Shutdown("EXE_SERVERKILLED");
}

void SV_GameCompleteStatus_f(void)
{
    SV_MasterGameCompleteStatus();
}

void SV_ScriptUsage_f(void)
{
    Scr_DumpScriptThreads();
}

void SV_StringUsage_f(void)
{
    MT_DumpTree();
}

void SV_SetDrawFriend_f(void)
{
    if (Cmd_Argc() == 2) {
        Cvar_Set("scr_drawfriend", Cmd_Argv(1));
        Cvar_Set("ui_drawfriend", Cmd_Argv(1));
    } else {
        Com_Printf("Usage: %s <integer>\n", Cmd_Argv(0));
    }
}

void SV_SetFriendlyFire_f(void)
{
    if (Cmd_Argc() == 1) {
        Com_Printf("Usage: %s <integer>\n", Cmd_Argv(0));
        return;
    }

    Cvar_Set("scr_friendlyfire", Cmd_Argv(1));
    Cvar_Set("ui_friendlyfire", Cmd_Argv(1));
}

void SV_SetKillcam_f(void)
{
    const char *value;

    if (Cmd_Argc() == 1) {
        Com_Printf("Usage: %s <integer>\n", Cmd_Argv(0));
        return;
    }

    value = Cmd_Argv(1);
    Cvar_Set("scr_killcam", value);
    Cvar_Set("ui_killcam", value);
}

void SV_AddDedicatedCommands(void);

void SV_AddOperatorCommands(void)
{
    if (svOperatorCommandsInitialized != qfalse) {
        return;
    }

    svOperatorCommandsInitialized = qtrue;
    Cmd_AddCommand("heartbeat", SV_Heartbeat_f);
    Cmd_AddCommand("kick", SV_Drop_f);
    Cmd_AddCommand("banUser", SV_Ban_f);
    Cmd_AddCommand("banClient", SV_BanNum_f);
    Cmd_AddCommand("tempBanUser", SV_TempBan_f);
    Cmd_AddCommand("tempBanClient", SV_TempBanNum_f);
    Cmd_AddCommand("unbanUser", SV_Unban_f);
    Cmd_AddCommand("clientkick", SV_DropNum_f);
    Cmd_AddCommand("status", SV_Status_f);
    Cmd_AddCommand("serverinfo", SV_Serverinfo_f);
    Cmd_AddCommand("systeminfo", SV_Systeminfo_f);
    Cmd_AddCommand("dumpuser", SV_DumpUser_f);
    Cmd_AddCommand("map_restart", SV_MapRestart_f);
    Cmd_AddCommand("map", SV_Map_f);
    Cmd_AddCommand("map_rotate", SV_MapRotate_f);
    Cmd_AddCommand("gameCompleteStatus", SV_GameCompleteStatus_f);
    Cmd_AddCommand("devmap", SV_Map_f);
    Cmd_AddCommand("killserver", SV_KillServer_f);
    if (dedicated->integer != 0) {
        SV_AddDedicatedCommands();
    }
    Cmd_AddCommand("scriptUsage", SV_ScriptUsage_f);
    Cmd_AddCommand("stringUsage", SV_StringUsage_f);
    Cmd_AddCommand("setdrawfriend", SV_SetDrawFriend_f);
    Cmd_AddCommand("setfriendlyfire", SV_SetFriendlyFire_f);
    Cmd_AddCommand("setkillcam", SV_SetKillcam_f);
}

void SV_RemoveOperatorCommands(void)
{
}

void SV_AddDedicatedCommands(void)
{
    Cmd_AddCommand("say", SV_ConSay_f);
    Cmd_AddCommand("tell", SV_ConTell_f);
}

void SV_RemoveDedicatedCommands(void)
{
    Cmd_RemoveCommand("say");
    Cmd_RemoveCommand("tell");
}

void SV_AuthorizeRequest(netadr_t from,
                             int32_t challenge)
{
#if CODUO_DISABLE_SERVER_AUTH
    (void)from;
    (void)challenge;
    return;
#else
    char fsGame[SV_OP_CHAT_BUFFER_SIZE];
    cvar_t *fsGameCvar;
    cvar_t *allowAnonymous;

    if (host_authorizeServerAddress.type == CODUO_NA_BOT) {
        return;
    }

    if (SV_AuthorizeIsCrackedMode()) {
        return;
    }

    fsGame[0] = '\0';
    fsGameCvar = Cvar_Get("fs_game", "",
                          CODUO_CVAR_SYSTEMINFO | CODUO_CVAR_INIT);
    if (fsGameCvar != NULL && fsGameCvar->string[0] != '\0') {
        /* ORIGINAL_BINARY_BUG: 0x08089c8d..0x08089ca0 copies the
         * unrestricted cvar string into this 1,024-byte array; see
         * original-bugs/coduomp-authorize-fs-game-stack-overflow.md. */
        strcpy(fsGame, fsGameCvar->string);
    }

    Com_DPrintf("sending getIpAuthorize for %s\n", NET_AdrToString(from));
    allowAnonymous = Cvar_Get("sv_allowAnonymous", "0",
                              CODUO_CVAR_SERVERINFO);
    NET_OutOfBandPrint(NS_SERVER, host_authorizeServerAddress,
                       "getIpAuthorize %i %i.%i.%i.%i %s %i", challenge,
                       from.ip[0], from.ip[1], from.ip[2], from.ip[3],
                       fsGame, allowAnonymous->integer);
#endif
}

void SV_GetChallenge(netadr_t from)
{
    challenge_t *challenge;
    int oldestSlot = 0;
    int oldestTime = INT32_MAX;
    int slot;

    for (slot = 0; slot < CODUO_HOST_CHALLENGE_COUNT; ++slot) {
        challenge = &host_svs_challenges[slot];
        if (challenge->connected == qfalse &&
            NET_CompareAdr(from, challenge->adr) != qfalse) {
            break;
        }
        if (challenge->time < oldestTime) {
            oldestTime = challenge->time;
            oldestSlot = slot;
        }
    }

    if (slot == CODUO_HOST_CHALLENGE_COUNT) {
        challenge = &host_svs_challenges[oldestSlot];
        uint32_t challengeHigh = (uint32_t)rand() << 16U;
        uint32_t challengeLow = (uint32_t)rand();

        challenge->challenge = coduo_int32_from_bits(
            challengeHigh ^ challengeLow ^
            (uint32_t)svs.time);
        challenge->adr = from;
        challenge->firstTime = svs.timeSecondary;
        challenge->firstPing = 0;
        challenge->time = svs.timeSecondary;
        challenge->connected = qfalse;
        slot = oldestSlot;
    } else {
        challenge = &host_svs_challenges[slot];
    }

#if CODUO_DISABLE_SERVER_AUTH
    challenge->pingTime = svs.timeSecondary;
    if (host_cvar_sv_onlyVisibleClients_pointer_slot->integer == 0) {
        NET_OutOfBandPrint(NS_SERVER, from, "challengeResponse %i",
                           challenge->challenge);
    } else {
        NET_OutOfBandPrint(NS_SERVER, from, "challengeResponse %i %i",
                           challenge->challenge,
                           host_cvar_sv_onlyVisibleClients_pointer_slot
                               ->integer);
    }
    return;
#endif

    if (net_lanauthorize->integer == 0 &&
        Sys_IsLANAddress(from) != qfalse) {
        challenge->pingTime = svs.timeSecondary;
        if (host_cvar_sv_onlyVisibleClients_pointer_slot->integer == 0) {
            NET_OutOfBandPrint(NS_SERVER, from, "challengeResponse %i",
                               challenge->challenge);
        } else {
            NET_OutOfBandPrint(NS_SERVER, from,
                               "challengeResponse %i %i",
                               challenge->challenge,
                               host_cvar_sv_onlyVisibleClients_pointer_slot
                                   ->integer);
        }
        return;
    }

    if (SV_AuthorizeIsCrackedMode()) {
        challenge->pingTime = svs.timeSecondary;
        if (host_cvar_sv_onlyVisibleClients_pointer_slot->integer == 0) {
            NET_OutOfBandPrint(NS_SERVER, from, "challengeResponse %i",
                               challenge->challenge);
        } else {
            NET_OutOfBandPrint(NS_SERVER, from,
                               "challengeResponse %i %i",
                               challenge->challenge,
                               host_cvar_sv_onlyVisibleClients_pointer_slot
                                   ->integer);
        }
        return;
    }

    if (host_authorizeServerAddress.ip[0] == 0 &&
        host_authorizeServerAddress.type != CODUO_NA_BOT) {
        Com_Printf("Resolving %s\n", svAuthorizeServerName);
        if (NET_StringToAdr(svAuthorizeServerName,
                            &host_authorizeServerAddress) == qfalse) {
            Com_Printf("Couldn't resolve address\n");
            return;
        }
        host_authorizeServerAddress.port = htons(SV_OP_AUTHORIZE_PORT);
        Com_Printf("%s resolved to %i.%i.%i.%i:%i\n", svAuthorizeServerName,
                   host_authorizeServerAddress.ip[0],
                   host_authorizeServerAddress.ip[1],
                   host_authorizeServerAddress.ip[2],
                   host_authorizeServerAddress.ip[3],
                   htons(host_authorizeServerAddress.port));
    }

    if (svs.timeSecondary - challenge->firstTime >
        SV_OP_AUTHORIZE_TIMEOUT_MSEC) {
        challenge->pingTime = svs.timeSecondary;
        if (NET_CompareAdr(from, *SV_ResolveMasterAddress()) == qfalse) {
            Com_Printf("authorize server timed out\n");
            if (host_cvar_sv_onlyVisibleClients_pointer_slot->integer != 0) {
                NET_OutOfBandPrint(NS_SERVER, challenge->adr,
                                   "challengeResponse %i %i",
                                   challenge->challenge,
                                   host_cvar_sv_onlyVisibleClients_pointer_slot
                                       ->integer);
                return;
            }
            NET_OutOfBandPrint(NS_SERVER, challenge->adr,
                               "challengeResponse %i", challenge->challenge);
            return;
        }
    }

    SV_AuthorizeRequest(from, challenge->challenge);
}

void SV_UserinfoChanged(client_t *client)
{
    const char *value;

    value = Info_ValueForKey(client->userinfo, "name");
    Q_strncpyz(client->name, value, sizeof(client->name));

    if (NET_IsLocalAddress(client->netchan.remoteAddress) == qfalse ||
        dedicated->integer == SV_OP_DEDICATED_SERVER) {
        value = Info_ValueForKey(client->userinfo, "rate");
        if (value[0] == '\0') {
            client->rate = SV_OP_RATE_DEFAULT;
        } else {
            client->rate = atoi(value);
            if (client->rate < SV_OP_RATE_MIN) {
                client->rate = SV_OP_RATE_MIN;
            } else if (client->rate > SV_OP_RATE_MAX) {
                client->rate = SV_OP_RATE_MAX;
            }
        }
    } else {
        client->rate = SV_OP_RATE_LOCAL;
    }

    value = Info_ValueForKey(client->userinfo, "handicap");
    if (value[0] != '\0' &&
        (atoi(value) < 1 || atoi(value) > 100 || strlen(value) > 4)) {
        Info_SetValueForKey(client->userinfo, "handicap", "100");
    }

    value = Info_ValueForKey(client->userinfo, "snaps");
    if (value[0] == '\0') {
        client->snapshotMsec = SV_OP_SNAPSHOT_MSEC_DEFAULT;
    } else {
        int snaps = atoi(value);
        if (snaps < SV_OP_SNAPS_MIN) {
            snaps = SV_OP_SNAPS_MIN;
        } else if (snaps > SV_OP_SNAPS_MAX) {
            snaps = SV_OP_SNAPS_MAX;
        }
        client->snapshotMsec = 1000 / snaps;
    }

    client->download.redirectAllowedByClient = qfalse;
    value = Info_ValueForKey(client->userinfo, "cl_wwwDownload");
    if (value[0] != '\0' && atoi(value) != 0) {
        client->download.redirectAllowedByClient = qtrue;
    }
}

/* coduo_lnxded RVA 0x042bb3 is the one client-release body called by
 * FUN_0808ac2b, SVC_DirectConnect, and SV_DropClient. The matching Mac symbol
 * names it PB_DropClient; the dedicated ELF contains no separate
 * SV_FreeClient body. */
void PB_DropClient(client_t *client)
{
    SV_ClearClientDownload(client);
    if (sv.state == CODUO_SS_GAME) {
        VM_Call(sv_gameVM, CODUO_GAME_CLIENT_DISCONNECT,
                coduomp_sv_client_num_for_client(client));
    }
    SV_SetUserinfo(coduomp_sv_client_num_for_client(client), "");
    SV_FreeClientScriptId(client);
}

/*
 * Checked 2026-06-29: PB-specific active-client cleanup before freeing
 * svs.clients. Do not rename to SV_FreeClients without stronger evidence.
 */
void FUN_0808ac2b(void)
{
    client_t *client = svs.clients;

    for (int clientNum = 0;
         clientNum < host_cvar_sv_maxclients_pointer_slot->integer;
         ++clientNum, ++client) {
        if (client->state > CODUO_CS_ZOMBIE) {
            PB_DropClient(client);
        }
    }
    free(svs.clients);
}

int32_t Pb_Q_maxclients(void)
{
    return host_cvar_sv_maxclients_pointer_slot->integer;
}

qboolean Pb_Q_client(int32_t clientNum, char *info)
{
    memset(info, 0, SV_OP_CLIENT_INFO_BUFFER_SIZE);
    if (clientNum < 0 ||
        clientNum >= host_cvar_sv_maxclients_pointer_slot->integer ||
        svs.clients == NULL ||
        svs.clients[clientNum].state < CODUO_CS_ACTIVE) {
        return qfalse;
    }

    client_t *client = &svs.clients[clientNum];
    strcpy(info, client->name);
    strcpy(info + SV_OP_PUNKBUSTER_GUID_OFFSET, client->punkbusterGuid);
    strcpy(info + SV_OP_CLIENT_ADDRESS_OFFSET,
           NET_AdrToString(client->netchan.remoteAddress));
    return qtrue;
}

qboolean Pb_Q_stats(int32_t clientNum, char *info)
{
    info[0] = '\0';
    if (clientNum < 0 ||
        clientNum >= host_cvar_sv_maxclients_pointer_slot->integer ||
        svs.clients == NULL ||
        svs.clients[clientNum].state < CODUO_CS_ACTIVE) {
        return qfalse;
    }

    client_t *client = &svs.clients[clientNum];
    sprintf(info, "ping=%d score=%d", client->ping,
            SV_GetClientScore(client));
    return qtrue;
}

void SV_SendPbPacket(int length, const char *data, int32_t clientNum)
{
    if (svs.initialized != qtrue) {
        return;
    }

    for (int slot = 0; slot < host_cvar_sv_maxclients_pointer_slot->integer;
         ++slot) {
        if ((clientNum < 0 || clientNum == slot) &&
            svs.clients[slot].state > CODUO_CS_ZOMBIE) {
            NET_OutOfBandPbPacket(NS_SERVER,
                                  svs.clients[slot].netchan.remoteAddress,
                                  data, length);
        }
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: names the client script-persistent object allocation. */
uint16_t coduomp_sv_alloc_client_script_pers(void)
{
    return (uint16_t)Scr_AllocArray();
}

/* Stock RVA 0x04cde7; exact cross-binary symbol identity. */
void SV_FreeClientScriptId(client_t *client)
{
    Scr_FreeValue(client->scriptId);
    client->scriptId = 0;
}

void SV_PrintPunkBusterMessage(const char *left, const char *right)
{
    Com_Printf("%s: %s\n", left, right);
}
