code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2020 EDuke32 developers and contributors
Copyright (C) 2020 sirlemonhead, Nuke.YKT
This file is part of ETekWar.
ETekWar is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "compat.h"
#include "renderlayer.h"
#include "_control.h"
#include "build.h"
#include "cache1d.h"
#include "keyboard.h"
#include "control.h"
#include "tekwar.h"
#include "scriplib.h"

#include "config.h"

#include <string>
//#include <io.h>
#include <stdio.h>
#include <time.h>

const char gamefunctions[kMaxGameFunctions][kMaxGameFuncLen] =
{
  "Move_Forward",
  "Move_Backward",
  "Turn_Left",
  "Turn_Right",
  "Strafe",
  "Strafe_Left",
  "Strafe_Right",
  "Run",
  "Jump",
  "Crouch",
  "Fire",
  "Open",
  "Look_Up",
  "Look_Down",
  "Look_Straight",
  "Aim_Up",
  "Aim_Down",
  "SendMessage",
  "ConcealWeapon",
  "SwitchPlayer",
  "RearView",
  "MatrixMeter",
  "HealthMeter",
  "ElapsedTime",
  "Score",
  "Inventory",
  "Weapon_1",
  "Weapon_2",
  "Weapon_3",
  "Weapon_4",
  "Weapon_5",
  "Weapon_6",
  "Weapon_7",
  "Weapon_8",
  "Weapon_9",
  "Pause",
  "Map",
  "Zoom_In",
  "Zoom_Out",
  "Gamma_Correction",
  "Escape",
  "Shrink_Screen",
  "Enlarge_Screen",
  "Mouse_Sensitivity_Up",
  "Mouse_Sensitivity_Down",
  "Show_Console",
  "Mouse_Aiming",
  "Toggle_Crosshair"
};

const char keydefaults[kMaxGameFunctions * 2][kMaxGameFuncLen] =
{
   "W", "Kpad8",
   "S", "Kpad2",
   "Left", "Kpad4",
   "Right", "KPad6",
   "LAlt", "RAlt",
   "A", "",
   "D", "",
   "LShift", "RShift",
   "Space", "",
   "LCtrl", "",
   "RCtrl", "",
   "E", "",
   "PgUp", "",
   "PgDn", "",
   "Home", "",
   "", "", // aim up
   "", "", // aim down
   "M", "",
   "/", "", // conceal weapon
   "KpdEnt", "",
   "R", "",
   "E", "",
   "H", "",
   "T", "",
   "",  "", // score
   "I", "", // inventory
   "1", "",
   "2", "",
   "3", "",
   "4", "",
   "5", "",
   "6", "",
   "7", "",
   "8", "",
   "9", "",
   "Pause", "",
   "Tab", "",
   "=", "",
   "-", "",
   "F11", "",
   "Escape", "",
   "Kpad-", "",
   "Kpad+", "",
   "", "", // sensitivity up
   "", "", // sensitivity down
   "`", "",
   "U", "",
   "G", "",
};

const char oldkeydefaults[kMaxGameFunctions * 2][kMaxGameFuncLen] =
{
   "Up", "Kpad8",
   "Down", "Kpad2",
   "Left", "Kpad4",
   "Right", "KPad6",
   "LAlt", "RAlt",
   ",", "",
   ".", "",
   "LShift", "RShift",
   "A", "",
   "Z", "",
   "LCtrl", "RCtrl",
   "Space", "",
   "PgUp", "",
   "PgDn", "",
   "Home", "",
   "Insert", "",
   "Delete", "",
   "T", "",
   "1", "",
   "2", "",
   "3", "",
   "4", "",
   "5", "",
   "6", "",
   "7", "",
   "Pause", "",
   "Tab", "",
   "=", "",
   "-", "",
   "F11", "",
   "Escape", "",
   "Kpad-", "",
   "Kpad+", "",
   "Enter", "",
   "[", "",
   "]", "",
   "F7", "",
   "F8", "",
   "`", "",
   "U", "",
   "I", "",
};

static const char* mousedefaults[MAXMOUSEBUTTONS] =
{
   "Fire",
   "Strafe",
   "Move_Forward"
   "",
   "",
   "",
};

static const char* mouseclickeddefaults[MAXMOUSEBUTTONS] =
{
};

static const char* mouseanalogdefaults[MAXMOUSEAXES] =
{
"analog_strafing",
"analog_moving",
};

static const char* mousedigitaldefaults[MAXMOUSEDIGITAL] =
{
};

ud_setup_t gSetup;

char setupfilename[BMAX_PATH] = {kSetupFilename};

int lMouseSens = 32;

int32_t FXVolume;
int32_t MusicVolume;
int32_t MixRate;
int32_t MidiPort;
int32_t NumVoices;
int32_t NumChannels;
int32_t NumBits;
int32_t ReverseStereo;
int32_t MusicDevice;
int32_t FXDevice;
int32_t ControllerType;

int32_t scripthandle;
int32_t setupread;
// TODO: implement precaching toggle
int32_t useprecache;
int32_t MouseDeadZone, MouseBias;

// JBF 20031211: Store the input settings because
// (currently) mact can't regurgitate them
int32_t MouseFunctions[MAXMOUSEBUTTONS][2];
int32_t MouseDigitalFunctions[MAXMOUSEAXES][2];
int32_t MouseAnalogueAxes[MAXMOUSEAXES];
int32_t MouseAnalogueScale[MAXMOUSEAXES];
int32_t JoystickFunctions[MAXJOYBUTTONSANDHATS][2];
int32_t JoystickDigitalFunctions[MAXJOYAXES][2];
int32_t JoystickAnalogueAxes[MAXJOYAXES];
int32_t JoystickAnalogueScale[MAXJOYAXES];
int32_t JoystickAnalogueInvert[MAXJOYAXES];
int32_t JoystickAnalogueDead[MAXJOYAXES];
int32_t JoystickAnalogueSaturate[MAXJOYAXES];
uint8_t KeyboardKeys[kMaxGameFunctions][2];

int32_t MAXCACHE1DSIZE = (96 * 1024 * 1024);

void SetupGameButtons()
{
    CONTROL_DefineFlag(gamefunc_Move_Forward, FALSE);
    CONTROL_DefineFlag(gamefunc_Move_Backward, FALSE);
    CONTROL_DefineFlag(gamefunc_Turn_Left, FALSE);
    CONTROL_DefineFlag(gamefunc_Turn_Right, FALSE);
    CONTROL_DefineFlag(gamefunc_Strafe, FALSE);
    CONTROL_DefineFlag(gamefunc_Strafe_Left, FALSE);
    CONTROL_DefineFlag(gamefunc_Strafe_Right, FALSE);
    CONTROL_DefineFlag(gamefunc_Run, FALSE);
    CONTROL_DefineFlag(gamefunc_Jump, FALSE);
    CONTROL_DefineFlag(gamefunc_Crouch, FALSE);
    CONTROL_DefineFlag(gamefunc_Fire, FALSE);
    CONTROL_DefineFlag(gamefunc_Open, FALSE);
    CONTROL_DefineFlag(gamefunc_Look_Up, FALSE);
    CONTROL_DefineFlag(gamefunc_Look_Down, FALSE);
    CONTROL_DefineFlag(gamefunc_Look_Straight, FALSE);
    CONTROL_DefineFlag(gamefunc_Aim_Up, FALSE);
    CONTROL_DefineFlag(gamefunc_Aim_Down, FALSE);
    CONTROL_DefineFlag(gamefunc_SendMessage, FALSE);
    CONTROL_DefineFlag(gamefunc_ConcealWeapon, FALSE);
    CONTROL_DefineFlag(gamefunc_SwitchPlayer, FALSE);
    CONTROL_DefineFlag(gamefunc_RearView, FALSE);
    CONTROL_DefineFlag(gamefunc_MatrixMeter, FALSE);
    CONTROL_DefineFlag(gamefunc_HealthMeter, FALSE);
    CONTROL_DefineFlag(gamefunc_ElapsedTime, FALSE);
    CONTROL_DefineFlag(gamefunc_Score, FALSE);
    CONTROL_DefineFlag(gamefunc_Inventory, FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_1, FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_2, FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_3, FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_4, FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_5, FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_6, FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_7, FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_8, FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_9, FALSE);
    CONTROL_DefineFlag(gamefunc_Pause, FALSE);
    CONTROL_DefineFlag(gamefunc_Map, FALSE);
    CONTROL_DefineFlag(gamefunc_Zoom_In, FALSE);
    CONTROL_DefineFlag(gamefunc_Zoom_Out, FALSE);
    CONTROL_DefineFlag(gamefunc_Gamma_Correction, FALSE);
    CONTROL_DefineFlag(gamefunc_Escape, FALSE);
    CONTROL_DefineFlag(gamefunc_Shrink_Screen, FALSE);
    CONTROL_DefineFlag(gamefunc_Enlarge_Screen, FALSE);
    CONTROL_DefineFlag(gamefunc_Mouse_Sensitivity_Up, FALSE);
    CONTROL_DefineFlag(gamefunc_Mouse_Sensitivity_Down, FALSE);
    CONTROL_DefineFlag(gamefunc_Mouse_Aiming, FALSE);
    CONTROL_DefineFlag(gamefunc_Toggle_Crosshair, FALSE);
}

hashtable_t h_gamefuncs = {kMaxGameFunctions << 1, NULL};

int32_t CONFIG_FunctionNameToNum(const char* func)
{
    if (!func)
        return -1;

    return hash_findcase(&h_gamefuncs, func);
}

static char const* CONFIG_FunctionNumToName(int32_t func)
{
    if ((unsigned)func >= (unsigned)kMaxGameFunctions)
        return "";
    return gamefunctions[func];
}

int32_t CONFIG_AnalogNameToNum(const char* func)
{
    if (!func)
        return -1;
    else if (!Bstrcasecmp(func, "analog_turning"))
        return analog_turning;
    else if (!Bstrcasecmp(func, "analog_strafing"))
        return analog_strafing;
    else if (!Bstrcasecmp(func, "analog_moving"))
        return analog_moving;
    else if (!Bstrcasecmp(func, "analog_lookingupanddown"))
        return analog_lookingupanddown;
    else
        return -1;
}

static char const* CONFIG_AnalogNumToName(int32_t func)
{
    switch (func)
    {
        case analog_turning:
        return "analog_turning";
        case analog_strafing:
        return "analog_strafing";
        case analog_moving:
        return "analog_moving";
        case analog_lookingupanddown:
        return "analog_lookingupanddown";
    }

    return "";
}

static void CONFIG_SetJoystickButtonFunction(int i, int j, int function)
{
    JoystickFunctions[i][j] = function;
    CONTROL_MapButton(function, i, j, controldevice_joystick);
}
static void CONFIG_SetJoystickAnalogAxisScale(int i, int scale)
{
    JoystickAnalogueScale[i] = scale;
    CONTROL_SetAnalogAxisScale(i, scale, controldevice_joystick);
}
static void CONFIG_SetJoystickAnalogAxisInvert(int i, int invert)
{
    JoystickAnalogueInvert[i] = invert;
    CONTROL_SetAnalogAxisInvert(i, invert);
}
static void CONFIG_SetJoystickAnalogAxisDeadSaturate(int i, int dead, int saturate)
{
    JoystickAnalogueDead[i] = dead;
    JoystickAnalogueSaturate[i] = saturate;
    JOYSTICK_SetDeadZone(i, dead, saturate);
}
static void CONFIG_SetJoystickDigitalAxisFunction(int i, int j, int function)
{
    JoystickDigitalFunctions[i][j] = function;
    CONTROL_MapDigitalAxis(i, function, j);
}
static void CONFIG_SetJoystickAnalogAxisFunction(int i, int function)
{
    JoystickAnalogueAxes[i] = function;
    CONTROL_MapAnalogAxis(i, function);
}

void CONFIG_SetDefaultKeys(const char(*keyptr)[kMaxGameFuncLen], bool lazy/*=false*/)
{
    static char const s_gamefunc_[] = "gamefunc_";
    int constexpr strlen_gamefunc_ = ARRAY_SIZE(s_gamefunc_) - 1;

    if (!lazy)
    {
        Bmemset(KeyboardKeys, 0xff, sizeof(KeyboardKeys));
        CONTROL_ClearAllBinds();
    }

    for (int i = 0; i < ARRAY_SSIZE(gamefunctions); ++i)
    {
        if (gamefunctions[i][0] == '\0')
            continue;

        auto& key = KeyboardKeys[i];

        int const default0 = KB_StringToScanCode(keyptr[i << 1]);
        int const default1 = KB_StringToScanCode(keyptr[(i << 1) + 1]);

        // skip the function if the default key is already used
        // or the function is assigned to another key
        if (lazy && (key[0] != 0xff || (CONTROL_KeyIsBound(default0) && Bstrlen(CONTROL_KeyBinds[default0].cmdstr) > strlen_gamefunc_
            && CONFIG_FunctionNameToNum(CONTROL_KeyBinds[default0].cmdstr + strlen_gamefunc_) >= 0)))
        {
            #if 0 // defined(DEBUGGINGAIDS)
            if (key[0] != 0xff)
                initprintf("Skipping %s bound to %s\n", keyptr[i << 1], CONTROL_KeyBinds[default0].cmdstr);
            #endif
            continue;
        }

        key[0] = default0;
        key[1] = default1;

        if (key[0])
            CONTROL_FreeKeyBind(key[0]);

        if (key[1])
            CONTROL_FreeKeyBind(key[1]);

        if (i == gamefunc_Show_Console)
            OSD_CaptureKey(key[0]);
        else
            CONFIG_MapKey(i, key[0], 0, key[1], 0);
    }
}

void CONFIG_SetDefaults()
{
    scripthandle = -1;
    # if defined RENDERTYPESDL && SDL_MAJOR_VERSION > 1
    uint32_t inited = SDL_WasInit(SDL_INIT_VIDEO);
    if (inited == 0)
        SDL_Init(SDL_INIT_VIDEO);
    else if (!(inited & SDL_INIT_VIDEO))
        SDL_InitSubSystem(SDL_INIT_VIDEO);

    SDL_DisplayMode dm;
    if (SDL_GetDesktopDisplayMode(0, &dm) == 0)
    {
        gSetup.xdim = dm.w;
        gSetup.ydim = dm.h;
    }
    else
        # endif
    {
        gSetup.xdim = 1024;
        gSetup.ydim = 768;
    }

    #ifdef USE_OPENGL
    gSetup.bpp = 32;
    #else
    gSetup.bpp = 8;
    #endif

    // currently settings.cfg is only read after the startup window launches the game,
    // and rereading binds might be fickle so just enable this
    gSetup.usejoystick = 1;

    gSetup.forcesetup = 1;
    gSetup.noautoload = 1;
    gSetup.fullscreen = 1;
    gSetup.usemouse = 1;

    MixRate = 44100;
    FXVolume = 255;
    MusicVolume = 255;
    NumChannels = 2;
    NumBits = 16;
    NumVoices = 32;
    /* // TODO
        mouseaiming = 0;
        aimmode = 1;
        mouseflip = 1;
        runkey_mode = 0;
        auto_run = 1;

        gFov = 90;
    */
    CONFIG_SetDefaultKeys(keydefaults);

    memset(MouseFunctions, -1, sizeof(MouseFunctions));
    memset(MouseDigitalFunctions, -1, sizeof(MouseDigitalFunctions));
    memset(JoystickFunctions, -1, sizeof(JoystickFunctions));
    memset(JoystickDigitalFunctions, -1, sizeof(JoystickDigitalFunctions));

    CONTROL_MouseSensitivity = DEFAULTMOUSESENSITIVITY;

    for (int i = 0; i < MAXMOUSEBUTTONS; i++)
    {
        MouseFunctions[i][0] = CONFIG_FunctionNameToNum(mousedefaults[i]);
        CONTROL_MapButton(MouseFunctions[i][0], i, 0, controldevice_mouse);
        if (i >= 4) continue;
        MouseFunctions[i][1] = CONFIG_FunctionNameToNum(mouseclickeddefaults[i]);
        CONTROL_MapButton(MouseFunctions[i][1], i, 1, controldevice_mouse);
    }

    for (int i = 0; i < MAXMOUSEAXES; i++)
    {
        MouseAnalogueScale[i] = DEFAULTMOUSEANALOGUESCALE;
        CONTROL_SetAnalogAxisScale(i, MouseAnalogueScale[i], controldevice_mouse);

        MouseDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(mousedigitaldefaults[i * 2]);
        MouseDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(mousedigitaldefaults[i * 2 + 1]);
        CONTROL_MapDigitalAxis(i, MouseDigitalFunctions[i][0], 0);
        CONTROL_MapDigitalAxis(i, MouseDigitalFunctions[i][1], 1);

        MouseAnalogueAxes[i] = CONFIG_AnalogNameToNum(mouseanalogdefaults[i]);
        CONTROL_MapAnalogAxis(i, MouseAnalogueAxes[i]);
    }

    // TODO:
    //CONFIG_SetGameControllerDefaultsStandard();

    #if 0
    FXVolume = 128;
    MusicVolume = 128;
    ReverseStereo = 0;
    ControllerType = controltype_keyboardandmouse;
    lMouseSens = 8;
    #endif
}

int CONFIG_ReadSetup()
{
    //char tempbuf[1024];

    CONTROL_ClearAssignments();
    CONFIG_SetDefaults();

    setupread = 1;
    pathsearchmode = 1;

    if (scripthandle < 0)
    {
        if (buildvfs_exists(setupfilename))  // JBF 20031211
            scripthandle = SCRIPT_Load(setupfilename);
        else if (buildvfs_exists(kSetupFilename))
        {
            int const i = wm_ynbox("Import Configuration Settings",
                "The configuration file \"%s\" was not found. "
                "Import configuration data from \"%s\"?",
                setupfilename, kSetupFilename);
            if (i)
                scripthandle = SCRIPT_Load(kSetupFilename);
        }
    }

    pathsearchmode = 0;

    if (scripthandle < 0)
        return -1;

    SCRIPT_GetNumber(scripthandle, "Options", "Difficulty", &difficulty);
// TODO    SCRIPT_GetNumber(scripthandle, "Options", "Brightness", &gbrightness);

    SCRIPT_GetNumber(scripthandle, "Setup", "ForceSetup", &gSetup.forcesetup);
    SCRIPT_GetNumber(scripthandle, "Setup", "NoAutoLoad", &gSetup.noautoload);

    int32_t cachesize;
    SCRIPT_GetNumber(scripthandle, "Setup", "CacheSize", &cachesize);

    if (cachesize > MAXCACHE1DSIZE)
        MAXCACHE1DSIZE = cachesize;

    if (g_noSetup == 0 && g_modDir[0] == '/')
    {
        SCRIPT_GetString(scripthandle, "Setup", "ModDir", &g_modDir[0]);

        if (!buildvfs_isdir(g_modDir))
        {
            initprintf("Invalid mod dir in cfg!\n");
            Bsprintf(g_modDir, "/");
        }
    }

    SCRIPT_GetNumber(scripthandle, "Screen Setup", "MaxRefreshFreq", (int32_t*)&maxrefreshfreq);
    SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenBPP", &gSetup.bpp);
    SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenHeight", &gSetup.ydim);
    SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenMode", &gSetup.fullscreen);
    SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenWidth", &gSetup.xdim);

    vec2_t windowPos;
    if (!SCRIPT_GetNumber(scripthandle, "Screen Setup", "WindowPosX", &windowPos.x)
        && !SCRIPT_GetNumber(scripthandle, "Screen Setup", "WindowPosY", &windowPos.y))
    {
        g_windowPos = windowPos;
        g_windowPosValid = true;
    }

    if (gSetup.bpp < 8) gSetup.bpp = 32;

    setupread = 1;
    return 0;
}

// wrapper for CONTROL_MapKey(), generates key bindings to reflect changes to keyboard setup
void CONFIG_MapKey(int which, kb_scancode key1, kb_scancode oldkey1, kb_scancode key2, kb_scancode oldkey2)
{
    char tempbuf[256];
    int const keys[] = {key1, key2, oldkey1, oldkey2};
    char buf[2 * kMaxGameFuncLen];

    if (which == gamefunc_Show_Console)
        OSD_CaptureKey(key1);

    for (int k = 0; (unsigned)k < ARRAY_SIZE(keys); k++)
    {
        if (keys[k] == 0xff || !keys[k])
            continue;

        int match = 0;

        for (; match < ARRAY_SSIZE(sctokeylut); ++match)
        {
            if (keys[k] == sctokeylut[match].sc)
                break;
        }

        tempbuf[0] = 0;

        for (int i = kMaxGameFunctions - 1; i >= 0; i--)
        {
            if (KeyboardKeys[i][0] == keys[k] || KeyboardKeys[i][1] == keys[k])
            {
                Bsprintf(buf, "gamefunc_%s; ", CONFIG_FunctionNumToName(i));
                Bstrcat(tempbuf, buf);
            }
        }

        int const len = Bstrlen(tempbuf);

        if (len >= 2)
        {
            tempbuf[len - 2] = 0;  // cut off the trailing "; "
            CONTROL_BindKey(keys[k], tempbuf, 1, sctokeylut[match].key?sctokeylut[match].key:"<?>");
        }
        else
        {
            CONTROL_FreeKeyBind(keys[k]);
        }
    }
}

void CONFIG_SetupMouse(void)
{
    if (scripthandle < 0)
        return;

    char str[80];
    char temp[80];

    for (int i = 0; i < MAXMOUSEBUTTONS; i++)
    {
        Bsprintf(str, "MouseButton%d", i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str, temp))
            MouseFunctions[i][0] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str, "MouseButtonClicked%d", i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str, temp))
            MouseFunctions[i][1] = CONFIG_FunctionNameToNum(temp);
    }

    // map over the axes
    for (int i = 0; i < MAXMOUSEAXES; i++)
    {
        Bsprintf(str, "MouseAnalogAxes%d", i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str, temp))
            MouseAnalogueAxes[i] = CONFIG_AnalogNameToNum(temp);

        Bsprintf(str, "MouseDigitalAxes%d_0", i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str, temp))
            MouseDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str, "MouseDigitalAxes%d_1", i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str, temp))
            MouseDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str, "MouseAnalogScale%d", i);
        int32_t scale = MouseAnalogueScale[i];
        SCRIPT_GetNumber(scripthandle, "Controls", str, &scale);
        MouseAnalogueScale[i] = scale;
    }

    for (int i = 0; i < MAXMOUSEBUTTONS; i++)
    {
        CONTROL_MapButton(MouseFunctions[i][0], i, 0, controldevice_mouse);
        CONTROL_MapButton(MouseFunctions[i][1], i, 1, controldevice_mouse);
    }
    for (int i = 0; i < MAXMOUSEAXES; i++)
    {
        CONTROL_MapAnalogAxis(i, MouseAnalogueAxes[i]);
        CONTROL_MapDigitalAxis(i, MouseDigitalFunctions[i][0], 0);
        CONTROL_MapDigitalAxis(i, MouseDigitalFunctions[i][1], 1);
        CONTROL_SetAnalogAxisScale(i, MouseAnalogueScale[i], controldevice_mouse);
    }
}

void CONFIG_SetupJoystick(void)
{
    int32_t i;
    char str[80];
    char temp[80];
    int32_t scale;

    if (scripthandle < 0) return;

    for (i = 0; i < MAXJOYBUTTONSANDHATS; i++)
    {
        Bsprintf(str, "ControllerButton%d", i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str, temp))
            JoystickFunctions[i][0] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str, "ControllerButtonClicked%d", i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str, temp))
            JoystickFunctions[i][1] = CONFIG_FunctionNameToNum(temp);
    }

    // map over the axes
    for (i = 0; i < MAXJOYAXES; i++)
    {
        Bsprintf(str, "ControllerAnalogAxes%d", i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str, temp))
            JoystickAnalogueAxes[i] = CONFIG_AnalogNameToNum(temp);

        Bsprintf(str, "ControllerDigitalAxes%d_0", i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str, temp))
            JoystickDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str, "ControllerDigitalAxes%d_1", i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str, temp))
            JoystickDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str, "ControllerAnalogScale%d", i);
        scale = JoystickAnalogueScale[i];
        SCRIPT_GetNumber(scripthandle, "Controls", str, &scale);
        JoystickAnalogueScale[i] = scale;

        Bsprintf(str, "ControllerAnalogInvert%d", i);
        scale = JoystickAnalogueInvert[i];
        SCRIPT_GetNumber(scripthandle, "Controls", str, &scale);
        JoystickAnalogueInvert[i] = scale;

        Bsprintf(str, "ControllerAnalogDead%d", i);
        scale = JoystickAnalogueDead[i];
        SCRIPT_GetNumber(scripthandle, "Controls", str, &scale);
        JoystickAnalogueDead[i] = scale;

        Bsprintf(str, "ControllerAnalogSaturate%d", i);
        scale = JoystickAnalogueSaturate[i];
        SCRIPT_GetNumber(scripthandle, "Controls", str, &scale);
        JoystickAnalogueSaturate[i] = scale;
    }

    for (i = 0; i < MAXJOYBUTTONSANDHATS; i++)
    {
        CONTROL_MapButton(JoystickFunctions[i][0], i, 0, controldevice_joystick);
        CONTROL_MapButton(JoystickFunctions[i][1], i, 1, controldevice_joystick);
    }
    for (i = 0; i < MAXJOYAXES; i++)
    {
        CONTROL_MapAnalogAxis(i, JoystickAnalogueAxes[i]);
        CONTROL_MapDigitalAxis(i, JoystickDigitalFunctions[i][0], 0);
        CONTROL_MapDigitalAxis(i, JoystickDigitalFunctions[i][1], 1);
        CONTROL_SetAnalogAxisScale(i, JoystickAnalogueScale[i], controldevice_joystick);
        CONTROL_SetAnalogAxisInvert(i, JoystickAnalogueInvert[i]);
    }
}

void SetupInput()
{
    if (CONTROL_Startup(controltype_keyboardandmouse, &BGetTime, CLKIPS))
    {
        ERRprintf("There was an error initializing the CONTROL system.\n");
        engineUnInit();
        Bexit(5);
    }
    SetupGameButtons();
    CONFIG_SetupMouse();
    CONFIG_SetupJoystick();

    CONTROL_JoystickEnabled = (gSetup.usejoystick && CONTROL_JoyPresent);
    CONTROL_MouseEnabled = (gSetup.usemouse && CONTROL_MousePresent);

    // JBF 20040215: evil and nasty place to do this, but joysticks are evil and nasty too
    for (int i = 0; i < joystick.numAxes; i++)
        JOYSTICK_SetDeadZone(i, JoystickAnalogueDead[i], JoystickAnalogueSaturate[i]);
}

void CONFIG_WriteSettings(void) // save binds and aliases to <cfgname>_settings.cfg
{
    char filename[BMAX_PATH];

/*
    if (!Bstrcmp(setupfilename, kSetupFilename))
        Bsprintf(filename, "settings.cfg");
    else
        Bsprintf(filename, "%s_settings.cfg", strtok(setupfilename, "."));
*/

    Bsprintf(filename, "etekwar_cvars.cfg");
    buildvfs_FILE fp = buildvfs_fopen_write(filename);

    if (fp)
    {
        buildvfs_fputstr(fp, "// this file is automatically generated by ");
        buildvfs_fputstrptr(fp, AppProperName);
        buildvfs_fputstr(fp, "\nunbindall\n");

        for (int i = 0; i < MAXBOUNDKEYS + MAXMOUSEBUTTONS; i++)
        {
            if (CONTROL_KeyIsBound(i))
            {
                buildvfs_fputstr(fp, "bind \"");
                buildvfs_fputstrptr(fp, CONTROL_KeyBinds[i].key);
                if (CONTROL_KeyBinds[i].repeat)
                    buildvfs_fputstr(fp, "\" \"");
                else
                    buildvfs_fputstr(fp, "\" norepeat \"");
                buildvfs_fputstrptr(fp, CONTROL_KeyBinds[i].cmdstr);
                buildvfs_fputstr(fp, "\"\n");
            }
        }

        for (int i = 0; i < kMaxGameFunctions; ++i)
        {
            char const* name = CONFIG_FunctionNumToName(i);
            if (name && name[0] != '\0' && (KeyboardKeys[i][0] == 0xff || !KeyboardKeys[i][0]))
            {
                buildvfs_fputstr(fp, "unbound ");
                buildvfs_fputstrptr(fp, name);
                buildvfs_fputstr(fp, "\n");
            }
        }

        OSD_WriteAliases(fp);

        //if (g_crosshairSum != -1 && g_crosshairSum != DefaultCrosshairColors.r+(DefaultCrosshairColors.g<<8)+(DefaultCrosshairColors.b<<16))
        //{
        //    buildvfs_fputstr(fp, "crosshaircolor ");
        //    char buf[64];
        //    snprintf(buf, sizeof(buf), "%d %d %d\n", CrosshairColors.r, CrosshairColors.g, CrosshairColors.b);
        //    buildvfs_fputstrptr(fp, buf);
        //}

        OSD_WriteCvars(fp);

        buildvfs_fclose(fp);

        OSD_Printf("Wrote %s\n", filename);

        return;
    }

    OSD_Printf("Error writing %s: %s\n", filename, strerror(errno));
}

void CONFIG_WriteSetup(uint32_t flags)
{
    char buf[256];
    if (!setupread) return;

    if (scripthandle < 0)
        scripthandle = SCRIPT_Init(setupfilename);

    SCRIPT_PutNumber(scripthandle, "Options", "Difficulty", difficulty, FALSE, 2);
// TODO    SCRIPT_PutNumber(scripthandle, "Options", "Brightness", gbrightness, FALSE, 0);

    SCRIPT_PutNumber(scripthandle, "Setup", "CacheSize", MAXCACHE1DSIZE, FALSE, FALSE);
    //SCRIPT_PutNumber(scripthandle, "Setup", "ConfigVersion", BYTEVERSION_EDUKE32, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Setup", "ForceSetup", gSetup.forcesetup, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Setup", "NoAutoLoad", gSetup.noautoload, FALSE, FALSE);

    //#ifdef POLYMER
    //    SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "Polymer", glrendmode == REND_POLYMER, FALSE, FALSE);
    //#endif

    SCRIPT_PutNumber(scripthandle, "Screen Setup", "ScreenBPP", gSetup.bpp, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Screen Setup", "ScreenHeight", gSetup.ydim, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Screen Setup", "ScreenMode", gSetup.fullscreen, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Screen Setup", "ScreenWidth", gSetup.xdim, FALSE, FALSE);

    //if (g_grpNamePtr && !g_addonNum)
    //    SCRIPT_PutString(ud.config.scripthandle, "Setup", "SelectedGRP", g_grpNamePtr);

    #ifdef STARTUP_SETUP_WINDOW
    if (g_noSetup == 0)
        SCRIPT_PutString(scripthandle, "Setup", "ModDir", &g_modDir[0]);
    #endif
    // exit early after only updating the values that can be changed from the startup window
    if (flags & 1)
    {
        SCRIPT_Save(scripthandle, setupfilename);
        SCRIPT_Free(scripthandle);
        return;
    }

    SCRIPT_PutNumber(scripthandle, "Screen Setup", "MaxRefreshFreq", maxrefreshfreq, FALSE, FALSE);
    if (g_windowPosValid)
    {
        SCRIPT_PutNumber(scripthandle, "Screen Setup", "WindowPosX", g_windowPos.x, FALSE, FALSE);
        SCRIPT_PutNumber(scripthandle, "Screen Setup", "WindowPosY", g_windowPos.y, FALSE, FALSE);
    }

    //if (!NAM_WW2GI)
    //{
    //    SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "Out",ud.lockout,FALSE,FALSE);
    //    SCRIPT_PutString(ud.config.scripthandle, "Screen Setup", "Password",ud.pwlockout);
    //}

//#ifdef _WIN32
//    SCRIPT_PutNumber(ud.config.scripthandle, "Updates", "CheckForUpdates", ud.config.CheckForUpdates, FALSE, FALSE);
//    SCRIPT_PutNumber(ud.config.scripthandle, "Updates", "LastUpdateCheck", ud.config.LastUpdateCheck, FALSE, FALSE);
//#endif

    if (gSetup.usemouse)
    {
        for (int i = 0; i < MAXMOUSEBUTTONS; i++)
        {
            if (CONFIG_FunctionNumToName(MouseFunctions[i][0]))
            {
                Bsprintf(buf, "MouseButton%d", i);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(MouseFunctions[i][0]));
            }

            if (i >= (MAXMOUSEBUTTONS - 2)) continue;

            if (CONFIG_FunctionNumToName(MouseFunctions[i][1]))
            {
                Bsprintf(buf, "MouseButtonClicked%d", i);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(MouseFunctions[i][1]));
            }
        }

        for (int i = 0; i < MAXMOUSEAXES; i++)
        {
            if (CONFIG_AnalogNumToName(MouseAnalogueAxes[i]))
            {
                Bsprintf(buf, "MouseAnalogAxes%d", i);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_AnalogNumToName(MouseAnalogueAxes[i]));
            }

            if (CONFIG_FunctionNumToName(MouseDigitalFunctions[i][0]))
            {
                Bsprintf(buf, "MouseDigitalAxes%d_0", i);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(MouseDigitalFunctions[i][0]));
            }

            if (CONFIG_FunctionNumToName(MouseDigitalFunctions[i][1]))
            {
                Bsprintf(buf, "MouseDigitalAxes%d_1", i);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(MouseDigitalFunctions[i][1]));
            }

            Bsprintf(buf, "MouseAnalogScale%d", i);
            SCRIPT_PutNumber(scripthandle, "Controls", buf, MouseAnalogueScale[i], FALSE, FALSE);
        }
    }

    if (gSetup.usejoystick)
    {
        for (int dummy = 0; dummy < MAXJOYBUTTONSANDHATS; dummy++)
        {
            if (CONFIG_FunctionNumToName(JoystickFunctions[dummy][0]))
            {
                Bsprintf(buf, "ControllerButton%d", dummy);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(JoystickFunctions[dummy][0]));
            }

            if (CONFIG_FunctionNumToName(JoystickFunctions[dummy][1]))
            {
                Bsprintf(buf, "ControllerButtonClicked%d", dummy);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(JoystickFunctions[dummy][1]));
            }
        }
        for (int dummy = 0; dummy < MAXJOYAXES; dummy++)
        {
            if (CONFIG_AnalogNumToName(JoystickAnalogueAxes[dummy]))
            {
                Bsprintf(buf, "ControllerAnalogAxes%d", dummy);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_AnalogNumToName(JoystickAnalogueAxes[dummy]));
            }

            if (CONFIG_FunctionNumToName(JoystickDigitalFunctions[dummy][0]))
            {
                Bsprintf(buf, "ControllerDigitalAxes%d_0", dummy);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(JoystickDigitalFunctions[dummy][0]));
            }

            if (CONFIG_FunctionNumToName(JoystickDigitalFunctions[dummy][1]))
            {
                Bsprintf(buf, "ControllerDigitalAxes%d_1", dummy);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(JoystickDigitalFunctions[dummy][1]));
            }

            Bsprintf(buf, "ControllerAnalogScale%d", dummy);
            SCRIPT_PutNumber(scripthandle, "Controls", buf, JoystickAnalogueScale[dummy], FALSE, FALSE);

            Bsprintf(buf, "ControllerAnalogInvert%d", dummy);
            SCRIPT_PutNumber(scripthandle, "Controls", buf, JoystickAnalogueInvert[dummy], FALSE, FALSE);

            Bsprintf(buf, "ControllerAnalogDead%d", dummy);
            SCRIPT_PutNumber(scripthandle, "Controls", buf, JoystickAnalogueDead[dummy], FALSE, FALSE);

            Bsprintf(buf, "ControllerAnalogSaturate%d", dummy);
            SCRIPT_PutNumber(scripthandle, "Controls", buf, JoystickAnalogueSaturate[dummy], FALSE, FALSE);
        }
    }

    //SCRIPT_PutString(ud.config.scripthandle, "Comm Setup","PlayerName",&szPlayerName[0]);

    //SCRIPT_PutString(ud.config.scripthandle, "Comm Setup","RTSName",&ud.rtsname[0]);

 //   char commmacro[] = "CommbatMacro# ";

    //for (int dummy = 0; dummy < MAXRIDECULE; dummy++)
    //{
    //    commmacro[13] = dummy+'0';
    //    SCRIPT_PutString(ud.config.scripthandle, "Comm Setup",commmacro,&ud.ridecule[dummy][0]);
    //}

    SCRIPT_Save(scripthandle, setupfilename);

    if ((flags & 2) == 0)
        SCRIPT_Free(scripthandle);

    OSD_Printf("Wrote %s\n", setupfilename);
    CONFIG_WriteSettings();
    Bfflush(NULL);
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2020 EDuke32 developers and contributors
Copyright (C) 2020 sirlemonhead, Nuke.YKT
This file is part of ETekWar.
ETekWar is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef __config_h__
#define __config_h__

#include "compat.h"

#define kMaxGameFuncLen     64

// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp
#define MAXJOYBUTTONS 32
#define MAXJOYBUTTONSANDHATS (MAXJOYBUTTONS+4)

// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp
#define MAXMOUSEAXES 2
#define MAXMOUSEDIGITAL (MAXMOUSEAXES*2)

// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp
#define MAXJOYAXES 9
#define MAXJOYDIGITAL (MAXJOYAXES*2)

// default mouse scale
#define DEFAULTMOUSEANALOGUESCALE           65536

// default joystick settings
#define DEFAULTJOYSTICKANALOGUESCALE        65536
#define DEFAULTJOYSTICKANALOGUEDEAD         2000
#define DEFAULTJOYSTICKANALOGUESATURATE     9500

enum
{
    gamefunc_Move_Forward,
    gamefunc_Move_Backward,
    gamefunc_Turn_Left,
    gamefunc_Turn_Right,
    gamefunc_Strafe,
    gamefunc_Strafe_Left,
    gamefunc_Strafe_Right,
    gamefunc_Run,
    gamefunc_Jump,
    gamefunc_Crouch,
    gamefunc_Fire,
    gamefunc_Open,
    gamefunc_Look_Up,
    gamefunc_Look_Down,
    gamefunc_Look_Straight,
    gamefunc_Aim_Up,
    gamefunc_Aim_Down,
    gamefunc_SendMessage,
    gamefunc_ConcealWeapon,
    gamefunc_SwitchPlayer,
    gamefunc_RearView,
    gamefunc_MatrixMeter,
    gamefunc_HealthMeter,
    gamefunc_ElapsedTime,
    gamefunc_Score,
    gamefunc_Inventory,
    gamefunc_Weapon_1,
    gamefunc_Weapon_2,
    gamefunc_Weapon_3,
    gamefunc_Weapon_4,
    gamefunc_Weapon_5,
    gamefunc_Weapon_6,
    gamefunc_Weapon_7,
    gamefunc_Weapon_8,
    gamefunc_Weapon_9,
    gamefunc_Pause,
    gamefunc_Map,
    gamefunc_Zoom_In,
    gamefunc_Zoom_Out,
    gamefunc_Gamma_Correction,
    gamefunc_Escape,
    gamefunc_Shrink_Screen,
    gamefunc_Enlarge_Screen,
    gamefunc_Mouse_Sensitivity_Up,
    gamefunc_Mouse_Sensitivity_Down,
    gamefunc_Show_Console,
    gamefunc_Mouse_Aiming,
    gamefunc_Toggle_Crosshair,
    kMaxGameFunctions
};

extern const char gamefunctions[kMaxGameFunctions][kMaxGameFuncLen];
extern const char keydefaults[kMaxGameFunctions * 2][kMaxGameFuncLen];
extern const char oldkeydefaults[kMaxGameFunctions * 2][kMaxGameFuncLen];

typedef struct {
    int32_t usejoystick;
    int32_t usemouse;
    int32_t fullscreen;
    int32_t xdim;
    int32_t ydim;
    int32_t bpp;
    int32_t forcesetup;
    int32_t noautoload;
} ud_setup_t;

#define kSetupFilename  "etekwar.cfg"
extern char setupfilename[BMAX_PATH];

extern hashtable_t h_gamefuncs;

void SetupInput();

int CONFIG_ReadSetup();
void CONFIG_WriteSetup(uint32_t flags);

extern int lMouseSens;

extern ud_setup_t gSetup;
extern int32_t scripthandle;
extern int32_t setupread;
extern int32_t useprecache;
extern int32_t MouseDeadZone, MouseBias;

extern int32_t FXVolume;
extern int32_t MusicVolume;
extern int32_t MixRate;
extern int32_t MidiPort;
extern int32_t NumVoices;
extern int32_t NumChannels;
extern int32_t NumBits;
extern int32_t ReverseStereo;
extern int32_t MusicDevice;
extern int32_t FXDevice;

// JBF 20031211: Store the input settings because
// (currently) mact can't regurgitate them
extern int32_t MouseFunctions[MAXMOUSEBUTTONS][2];
extern int32_t MouseDigitalFunctions[MAXMOUSEAXES][2];
extern int32_t MouseAnalogueAxes[MAXMOUSEAXES];
extern int32_t MouseAnalogueScale[MAXMOUSEAXES];
extern int32_t JoystickFunctions[MAXJOYBUTTONSANDHATS][2];
extern int32_t JoystickDigitalFunctions[MAXJOYAXES][2];
extern int32_t JoystickAnalogueAxes[MAXJOYAXES];
extern int32_t JoystickAnalogueScale[MAXJOYAXES];
extern int32_t JoystickAnalogueInvert[MAXJOYAXES];
extern int32_t JoystickAnalogueDead[MAXJOYAXES];
extern int32_t JoystickAnalogueSaturate[MAXJOYAXES];
extern uint8_t KeyboardKeys[kMaxGameFunctions][2];

extern int32_t MAXCACHE1DSIZE;

extern int32_t difficulty;

void CONFIG_SetDefaultKeys(const char(*keyptr)[kMaxGameFuncLen], bool lazy=false);
void CONFIG_MapKey(int which, kb_scancode key1, kb_scancode oldkey1, kb_scancode key2, kb_scancode oldkey2);
int32_t CONFIG_FunctionNameToNum(const char* func);

#endif

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "baselayer.h"
#include "cache1d.h"
#include "common_game.h"
#include "common.h"
#include "compat.h"
#include "crc32.h"
#include "grpscan.h"
#include "scriptfile.h"

#include "vfs.h"

#ifndef EDUKE32_STANDALONE
// custom GRP support for the startup window, file format reflects the structure below
#define GAMELISTFILE "games.list"
//    name          crc             size      flags                 dependency
static internalgrpinfo_t const internalgrpfiles[] =
{
    { "TekWar",   TEKWAR_CRC,  11904, GAMEFLAG_TEKWAR, 0 },
};
#endif

struct grpfile_t *foundgrps = NULL;
struct grpinfo_t *listgrps = NULL;

static void LoadList(const char * filename)
{
    scriptfile *script = scriptfile_fromfile(filename);

    if (!script)
        return;

#ifndef EDUKE32_STANDALONE
    scriptfile_addsymbolvalue("GAMEFLAG_TEKWAR", GAMEFLAG_TEKWAR);
#endif

    while (!scriptfile_eof(script))
    {
        enum
        {
            T_GRPINFO,
            T_GAMENAME,
            T_CRC,
            T_SIZE,
            T_DEPCRC,
            T_DEFNAME,
            T_FLAGS,
        };

        static const tokenlist profiletokens[] =
        {
            { "grpinfo",            T_GRPINFO },
        };

        int32_t token = getatoken(script,profiletokens,ARRAY_SIZE(profiletokens));
        switch (token)
        {
        case T_GRPINFO:
        {
            int32_t gsize = 0, gcrcval = 0, gflags = GAMEFLAG_TEKWAR, gdepcrc = TEKWAR_CRC;
            char *gname = NULL, *gdef = NULL;
            char *grpend = NULL;

            static const tokenlist grpinfotokens[] =
            {
                { "name",           T_GAMENAME },
                { "defname",        T_DEFNAME },
                { "crc",            T_CRC },
                { "dependency",     T_DEPCRC },
                { "size",           T_SIZE },
                { "flags",          T_FLAGS },

            };

            if (scriptfile_getbraces(script,&grpend)) break;

            while (script->textptr < grpend)
            {
                int32_t token = getatoken(script,grpinfotokens,ARRAY_SIZE(grpinfotokens));

                switch (token)
                {
                case T_GAMENAME:
                    scriptfile_getstring(script,&gname); break;
                case T_DEFNAME:
                    scriptfile_getstring(script,&gdef); break;

                case T_FLAGS:
                    scriptfile_getsymbol(script,&gflags); gflags &= GAMEFLAGMASK; break;
                case T_DEPCRC:
                    scriptfile_getsymbol(script,&gdepcrc); break;
                case T_CRC:
                    scriptfile_getsymbol(script,&gcrcval); break;
                case T_SIZE:
                    scriptfile_getnumber(script,&gsize); break;
                default:
                    break;
                }

                grpinfo_t * const fg = (grpinfo_t *)Xcalloc(1, sizeof(grpinfo_t));
                fg->next = listgrps;
                listgrps = fg;

                if (gname)
                    fg->name = Xstrdup(gname);

                fg->size = gsize;
                fg->crcval = gcrcval;
                fg->dependency = gdepcrc;
                fg->game = gflags;

                if (gdef)
                    fg->defname = dup_filename(gdef);
            }
            break;
        }

        default:
            break;
        }
    }

    scriptfile_close(script);
    scriptfile_clearsymbols();
}

static void LoadGameList(void)
{
#ifndef EDUKE32_STANDALONE
    for (size_t i = 0; i < ARRAY_SIZE(internalgrpfiles); i++)
    {
        grpinfo_t * const fg = (grpinfo_t *)Xcalloc(1, sizeof(grpinfo_t));

        fg->name = Xstrdup(internalgrpfiles[i].name);
        fg->crcval = internalgrpfiles[i].crcval;
        fg->size = internalgrpfiles[i].size;
        fg->game = internalgrpfiles[i].game;
        fg->dependency = internalgrpfiles[i].dependency;

        fg->next = listgrps;
        listgrps = fg;
    }
#endif

#ifdef USE_PHYSFS
    auto const base = PHYSFS_getBaseDir();
#else
    static char const base[] = "/";
#endif
    BUILDVFS_FIND_REC * const srch = klistpath(base, "*.grpinfo", BUILDVFS_FIND_FILE);

    for (BUILDVFS_FIND_REC *sidx = srch; sidx; sidx = sidx->next)
        LoadList(sidx->name);

    klistfree(srch);
}

static void FreeGameList(void)
{
    while (listgrps)
    {
        Xfree(listgrps->name);
        Xfree(listgrps->defname);

        grpinfo_t * const fg = listgrps->next;
        Xfree(listgrps);
        listgrps = fg;
    }
}

#define GRPCACHEFILE "grpfiles.cache"
static struct grpcache
{
    struct grpcache *next;
    int32_t size;
    int32_t mtime;
    int32_t crcval;
    char name[BMAX_PATH];
}
*grpcache = NULL, *usedgrpcache = NULL;

static int32_t LoadGroupsCache(void)
{
    struct grpcache *fg;

    int32_t fsize, fmtime, fcrcval;
    char *fname;

    scriptfile *script;

    script = scriptfile_fromfile(GRPCACHEFILE);
    if (!script) return -1;

    while (!scriptfile_eof(script))
    {
        if (scriptfile_getstring(script, &fname)) break;    // filename
        if (scriptfile_getnumber(script, &fsize)) break;    // filesize
        if (scriptfile_getnumber(script, &fmtime)) break;   // modification time
        if (scriptfile_getnumber(script, &fcrcval)) break;  // crc checksum

        fg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
        fg->next = grpcache;
        grpcache = fg;

        Bstrncpy(fg->name, fname, BMAX_PATH);
        fg->size = fsize;
        fg->mtime = fmtime;
        fg->crcval = fcrcval;
    }

    scriptfile_close(script);
    return 0;
}

static void FreeGroupsCache(void)
{
    while (grpcache)
    {
        struct grpcache * const fg = grpcache->next;
        Xfree(grpcache);
        grpcache = fg;
    }
}

static void RemoveGroup(grpfile_t *igrp)
{
    for (grpfile_t *prev = NULL, *grp = foundgrps; grp; grp=grp->next)
    {
        if (grp == igrp)
        {
            if (grp == foundgrps)
                foundgrps = grp->next;
            else
                prev->next = grp->next;

            Xfree((char *)grp->filename);
            Xfree(grp);

            return;
        }

        prev = grp;
    }
}

grpfile_t * FindGroup(int32_t crcval)
{
    grpfile_t *grp;

    for (grp = foundgrps; grp; grp=grp->next)
    {
        if (grp->type->crcval == crcval)
            return grp;
    }

    return NULL;
}

#ifndef USE_PHYSFS
static grpinfo_t const * FindGrpInfo(int32_t crcval, int32_t size)
{
    grpinfo_t *grpinfo;

    for (grpinfo = listgrps; grpinfo; grpinfo=grpinfo->next)
    {
        if (grpinfo->crcval == crcval && grpinfo->size == size)
            return grpinfo;
    }

    return NULL;
}

static void ProcessGroups(BUILDVFS_FIND_REC *srch)
{
    BUILDVFS_FIND_REC *sidx;
    struct grpcache *fg, *fgg;
    char *fn;
    struct Bstat st;

    static constexpr int ReadSize = 65536;

    auto buf = (uint8_t *)Xmalloc(ReadSize);

    for (sidx = srch; sidx; sidx = sidx->next)
    {
        for (fg = grpcache; fg; fg = fg->next)
        {
            if (!Bstrcmp(fg->name, sidx->name)) break;
        }

        if (fg)
        {
            if (findfrompath(sidx->name, &fn)) continue; // failed to resolve the filename
            if (Bstat(fn, &st))
            {
                Xfree(fn);
                continue;
            } // failed to stat the file
            Xfree(fn);
            if (fg->size == (int32_t)st.st_size && fg->mtime == (int32_t)st.st_mtime)
            {
                grpinfo_t const * const grptype = FindGrpInfo(fg->crcval, fg->size);
                if (grptype)
                {
                    grpfile_t * const grp = (grpfile_t *)Xcalloc(1, sizeof(grpfile_t));
                    grp->filename = Xstrdup(sidx->name);
                    grp->type = grptype;
                    grp->next = foundgrps;
                    foundgrps = grp;
                }

                fgg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
                strcpy(fgg->name, fg->name);
                fgg->size = fg->size;
                fgg->mtime = fg->mtime;
                fgg->crcval = fg->crcval;
                fgg->next = usedgrpcache;
                usedgrpcache = fgg;
                continue;
            }
        }

        {
            int32_t b, fh;
            int32_t crcval = 0;

            fh = openfrompath(sidx->name, BO_RDONLY|BO_BINARY, BS_IREAD);
            if (fh < 0) continue;
            if (Bfstat(fh, &st)) continue;

            initprintf(" Checksumming %s...", sidx->name);
            do
            {
                b = read(fh, buf, ReadSize);
                if (b > 0) crcval = Bcrc32((uint8_t *)buf, b, crcval);
            }
            while (b == ReadSize);
            close(fh);
            initprintf(" Done\n");

            grpinfo_t const * const grptype = FindGrpInfo(crcval, st.st_size);
            if (grptype)
            {
                grpfile_t * const grp = (grpfile_t *)Xcalloc(1, sizeof(grpfile_t));
                grp->filename = Xstrdup(sidx->name);
                grp->type = grptype;
                grp->next = foundgrps;
                foundgrps = grp;
            }

            fgg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
            Bstrncpy(fgg->name, sidx->name, BMAX_PATH);
            fgg->size = st.st_size;
            fgg->mtime = st.st_mtime;
            fgg->crcval = crcval;
            fgg->next = usedgrpcache;
            usedgrpcache = fgg;
        }
    }

    Xfree(buf);
}
#endif

int32_t ScanGroups(void)
{
#ifndef USE_PHYSFS
    struct grpcache *fg, *fgg;

    initprintf("Searching for game data...\n");

    LoadGameList();
    LoadGroupsCache();

    static char const *extensions[] =
    {
        "*.grp",
        "*.ssi",
        "*.dat",
    };

    for (char const *extension : extensions)
    {
        BUILDVFS_FIND_REC *srch = klistpath("/", extension, BUILDVFS_FIND_FILE);
        ProcessGroups(srch);
        klistfree(srch);
    }

    FreeGroupsCache();

    for (grpfile_t *grp = foundgrps; grp; grp=grp->next)
    {
        if (grp->type->dependency)
        {
            if (FindGroup(grp->type->dependency) == NULL) // couldn't find dependency
            {
                //initprintf("removing %s\n", grp->name);
                RemoveGroup(grp);
                grp = foundgrps;
                // start from the beginning so we can remove anything that depended on this grp
                continue;
            }
        }
    }

    if (usedgrpcache)
    {
        int32_t i = 0;
        buildvfs_FILE fp = buildvfs_fopen_write(GRPCACHEFILE);
        if (fp)
        {
            for (fg = usedgrpcache; fg; fg=fgg)
            {
                fgg = fg->next;
                fprintf(fp, "\"%s\" %d %d %d\n", fg->name, fg->size, fg->mtime, fg->crcval);
                Xfree(fg);
                i++;
            }
            buildvfs_fclose(fp);
        }
//        initprintf("Found %d recognized GRP %s.\n",i,i>1?"files":"file");

        return 0;
    }

    initprintf("Found no recognized game data!\n");
#endif

    return 0;
}

void FreeGroups(void)
{
    while (foundgrps)
    {
        Xfree((char *)foundgrps->filename);
        grpfile_t * const fg = foundgrps->next;
        Xfree(foundgrps);
        foundgrps = fg;
    }

    FreeGameList();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef grpscan_h_
#define grpscan_h_

#ifdef __cplusplus
extern "C" {
#endif

#define MAXLISTNAMELEN 32

// List of internally-known GRP files
#define TEKWAR_CRC      (int32_t)0x0

typedef struct internalgrpinfo_t {
    char const *name;
    int32_t const crcval;
    int32_t const size;
    int32_t const game;
    int32_t const dependency;
    // char const *scriptname;
    // void(*postprocessing)(int32_t);
} internalgrpinfo_t;

typedef struct grpinfo_t {
	char *name;
	int32_t crcval;
	int32_t size;
	int32_t game;
    int32_t dependency;
    // char *scriptname;
    char *defname;
    // char *rtsname;
    // void (*postprocessing)(int32_t);
	struct grpinfo_t *next;
} grpinfo_t;

typedef struct grpfile_t {
	char *filename;
	struct grpinfo_t const *type;
	struct grpfile_t *next;
} grpfile_t;

extern grpfile_t *foundgrps;
extern grpinfo_t *listgrps;

extern grpfile_t * FindGroup(int32_t crcval);

int32_t ScanGroups(void);
void FreeGroups(void);

#ifdef __cplusplus
}
#endif

#endif


code:
//Be careful when changing this file - it is parsed by Editart and Build.
#define SECTOREFFECT 104
#define JEFFSTART 252
#define STATUSBARPIC 253
#define CLOCKRSTPIC 254
#define JOYCENTPIC 255
#define TIMERESTORE 256
#define SCORERESTORE 257
#define BAKGRPIC 258
#define DOOMGUYPIC 259
#define RATPIC 260
#define RATATTACKPIC 280
#define RATDEATHPIC 287
#define BLUESPLASH 292
#define RATTHROWPIC 299
#define BATPIC 303
#define MESSIAHPIC 309
#define GRAVEPIC 310
#define GARBAGECANPIC 311
#define PULSARPIC 312
#define BULLETHOLE 313
#define BOMBPIC 314
#define GRENADEPIC 315
#define PAUSEDPIC 316
#define ES1A_SVGA 317
#define ES1B_SVGA 318
#define ES2A_SVGA 319
#define ES2B_SVGA 320
#define ES1_VGA 321
#define ES2_VGA 322
#define DEMOWON 323
#define MENUSTATION 325
#define MENUGLASS 326
#define BLOODSPLAT 327
#define STARTPOS 331
#define FORCEBALPIC 332
#define AIMPIC 333
#define BIGAIMPIC 334
#define DB_2A_SVGA 338
#define ACCUTRAKPIC 339
#define WALLBLOOD 340
#define RED_KEYCARD 346
#define BLUE_KEYCARD 347
#define ALPHABET 348
#define ALPHABET2 349
#define RUBSTANDPIC 350
#define RUBWALKPIC 355
#define RUBATAKPIC 375
#define RUBPAINPIC 385
#define RUBDEATHPIC 390
#define RUBSQUATPIC 397
#define SWITCH1ON 400
#define SLIME 401
#define KENPICTURE 402
#define BUILDDISK 403
#define SWITCH2ON 404
#define SWITCH2OFF 405
#define BACKGROUND 406
#define NO 407
#define DEMOSIGN 408
#define COIN 409
#define COINSTACK 410
#define GIFTBOX 411
#define DIAMONDS 412
#define EVILGRAVE 413
#define STATUSBAR 414
#define WATERFOUNTAIN 416
#define USEWATERFOUNTAIN 417
#define SLOTMACHINE 418
#define SLOTWHEEL1 419
#define SLOTWHEEL2 420
#define SLOTWHEEL3 421
#define BULLET 422
#define BOMB 423
#define CANON 424
#define GUNONBOTTOM 425
#define BOMBEMITTER 426
#define EXPLOSION 427
#define SPLASH 428
#define BROWNMONSTER 429
#define SKELETON 430
#define AL 431
#define EVILAL 432
#define SWITCH3OFF 433
#define SWITCH3ON 434
#define SWITCH4OFF 435
#define SWITCH4ON 436
#define COMPASS 437
#define FOOTPRINT 438
#define STATUSBARFILL8 439
#define EVILALGRAVE 440
#define STATUSBARFILL4 441
#define BOUNCYMAT 442
#define FIREPIC 450
#define FLMEXPLODEPIC 457
#define BOMBEXP1PIC 460
#define TUBEBOMB 466
#define DARTBOMB 469
#define GUN08READY 472
#define GUN08FIRESTART 474
#define GUN08FIREEND 480
#define SNDFX_AMBIENT 481
#define SNDFX_SECTOR 482
#define SNDFX_TURN_ON 486
#define SNDFX_TURN_OFF 487
#define SNDFX_TOGGLED 488
#define SYMBOL1PIC 491
#define SYMBOL2PIC 492
#define SYMBOL3PIC 493
#define SYMBOL4PIC 494
#define SYMBOL5PIC 495
#define SYMBOL6PIC 496
#define SYMBOL7PIC 497
#define LESSTART 505
#define REDKEYACCESS 513
#define BLUEKEYACCESS 516
#define NOKEYACCESS 534
#define ELEVATORSWITCH 558
#define AUTOGUN 562
#define MEDICPACK 577
#define MEDICPACKOPEN 580
#define VISORBATTERY 585
#define OOGIEPIC 592
#define AMMODISPENSE 603
#define BLUEKEYDOOR 705
#define REDKEYDOOR 706
#define STOPPIC 720
#define SPEEDPIC 722
#define DIRECTION 727
#define SKULLPIC 728
#define GREENHIGH 729
#define GLASSDEATHPIC 794
#define SPRITESWITCHOFF 803
#define SPRITESWITCHON 804
#define GUN6ONGROUND 829
#define GUN4ONGROUND 830
#define GUN3ONGROUND 856
#define GUN2ONGROUND 857
#define GUN5ONGROUND 859
#define GUN7ONGROUND 860
#define BARRELL 899
#define BARRELLEXPSTART 900
#define BARRELLEXPEND 908
#define MEDICKITPIC 922
#define MEDICKIT2PIC 923
#define KLIPPIC 924
#define FUELBOTLPIC 925
#define FORCECHRGPIC 926
#define FLMRFIREPIC 930
#define JAKGUNCARRYPIC 942
#define JAKGUNFIRPIC 945
#define GENEXP1PIC 956
#define GENEXP2PIC 965
#define HELPSCREENPIC 981
#define HELPSCREEN4801 982
#define HELPSCREEN4802 983
#define JAKESTANDPIC 994
#define JAKEWALKPIC 999
#define JAKEDEATHPIC 1019
#define JAKETWITCHPIC 1027
#define JAKEPAINPIC 1029
#define JAKESQUATPIC 1034
#define JAKEDRAWPIC 1037
#define GUN05READY 1084
#define GUN05FIRESTART 1088
#define GUN05FIREEND 1090
#define GUN03READY 1093
#define GUN03FIRESTART 1095
#define GUN03FIREEND 1097
#define GUN01READY 1099
#define GUN01FIRESTART 1102
#define GUN01FIREEND 1103
#define COP1STNDPIC 1110
#define COP1WALKPIC 1115
#define COP1DEATHPIC 1135
#define COP1PAINPIC 1145
#define COP1DODGEPIC 1150
#define COP1DRAWPIC 1153
#define COPATTACKPIC 1168
#define COP1SQUATPIC 1183
#define JAKEATTACKPIC 1198
#define GUN04READY 1281
#define GUN04FIRESTART 1283
#define GUN04FIREEND 1284
#define GUN07READY 1285
#define GUN07FIRESTART 1288
#define GUN07FIREEND 1289
#define GUN06READY 1290
#define GUN06FIRESTART 1293
#define GUN06FIREEND 1296
#define GADGET01READY 1299
#define GADGET01USE 1303
#define GADGET01DONE 1307
#define GUN02READY 1308
#define GUN02FIRESTART 1310
#define GUN02FIREEND 1312
#define GOREARM 1320
#define GOREHEAD 1324
#define GORELEG 1328
#define FORCEBALLPIC 1332
#define GOREBLOOD 1344
#define CHARGEPAKPIC 1433
#define DONUTSPIC 1453
#define MFONT_A 1493
#define MFONT_0 1519
#define MENUPANEL4801 1529
#define MENUPANEL4802 1530
#define MFONT_SPECIAL1 1531
#define WPDEVICEGUN2 1546
#define WPDEVICEGUN4 1547
#define WPDEVICEGUN6 1548
#define RVDEVICE 1554
#define RVDEVICEON 1558
#define WPDEVICE 1559
#define WPDEVICEON 1563
#define HCDEVICE 1564
#define HCDEVICEON 1568
#define SLIDERBARPIC 1569
#define GREENLIGHTPIC 1570
#define REDLIGHTPIC 1571
#define YELLOWLIGHTPIC 1572
#define BLUELIGHTPIC 1573
#define SLIDERKNOBPIC 1574
#define RVDEVRES 1575
#define WPDEVRES 1576
#define HCDEVRES 1577
#define KILLGRILL 1578
#define ANTSTANDPIC 1746
#define ANTWALKPIC 1751
#define ANTATTACKPIC 1771
#define ANTPAINPIC 1786
#define ANTDEATHPIC 1791
#define ANTSQUATPIC 1798
#define SARAHWALKPIC 1801
#define SARAHDEATHPIC 1821
#define MASTANDPIC 1829
#define MAWALKPIC 1834
#define MAATTACKPIC 1854
#define MAPAINPIC 1869
#define MADEATHPIC 1874
#define DISTANDPIC 1882
#define DIWALKPIC 1887
#define DIATTACKPIC 1907
#define DIPAINPIC 1922
#define DISQUATPIC 1927
#define DIDODGEPIC 1930
#define DIDEATHPIC 1933
#define ERSTANDPIC 1945
#define ERWALKPIC 1950
#define ERSQUATPIC 1970
#define ERPAINPIC 1975
#define ERDEATHPIC 1980
#define SARAHSQUATPIC 1992
#define MARIADODGEPIC 1995
#define SAMSTANDPIC 1998
#define SAMWALKPIC 2003
#define SAMATTACKPIC 2023
#define SAMSQUATPIC 2038
#define SAMPAINPIC 2041
#define SAMDEATHPIC 2046
#define FRGSTANDPIC 2060
#define FRGWALKPIC 2065
#define FRGDEATHPIC 2085
#define FRGPAINPIC 2094
#define FRGATTACKPIC 2099
#define FRGSQUATPIC 2114
#define SUNGSTANDPIC 2120
#define SUNGWALKPIC 2125
#define SUNGATTACKPIC 2145
#define SUNGPAINPIC 2160
#define SUNGDEATHPIC 2165
#define COWSTANDPIC 2180
#define COWWALKPIC 2185
#define COWATTACKPIC 2206
#define COWPAINPIC 2220
#define COWDODGEPIC 2225
#define COWDEATHPIC 2227
#define COPBSTANDPIC 2254
#define COPBWALKPIC 2259
#define COPBATTACKPIC 2279
#define COPBDEATHPIC 2295
#define NIKASTANDPIC 2310
#define NIKAATTACKPIC 2315
#define NIKAPAINPIC 2332
#define NIKADODGEPIC 2337
#define NIKADEATHPIC 2340
#define NIKAWALKPIC 2349
#define REBRSTANDPIC 2370
#define REBRWALKPIC 2375
#define REBRATTACKPIC 2395
#define REBRPAINPIC 2410
#define REBRDEATHPIC 2415
#define TRENSTANDPIC 2430
#define TRENWALKPIC 2435
#define TRENDEATHPIC 2455
#define TRENPAINPIC 2467
#define TRENDODGEPIC 2472
#define WINGSTANDPIC 2501
#define WINGWALKPIC 2506
#define WINGATTACKPIC 2535
#define WINGDEATHPIC 2550
#define HALTSTANDPIC 2560
#define HALTWALKPIC 2565
#define HALTATTACKPIC 2585
#define HALTPAINPIC 2600
#define HALTDODGEPIC 2605
#define HALTDEATHPIC 2607
#define REDHSTANDPIC 2620
#define REDHWALKPIC 2625
#define REDHATTACKPIC 2645
#define REDHDEATHPIC 2662
#define REDHDODGEPIC 2671
#define ORANSTANDPIC 2680
#define ORANWALKPIC 2685
#define ORANPAINPIC 2705
#define ORANDODGEPIC 2710
#define ORANDEATHPIC 2712
#define ORANATTACKPIC 2718
#define BLKSSTANDPIC 2747
#define BLKSWALKPIC 2752
#define BLKSPAINPIC 2772
#define BLKSATTACKPIC 2777
#define BLKSDEATHPIC 2792
#define BLKSSPECIALPIC 2800
#define BLKSDODGEPIC 2804
#define SFROSTANDPIC 2810
#define SFROWALKPIC 2815
#define SFROATTACKPIC 2835
#define SFRODEATHPIC 2850
#define SFROPAINPIC 2859
#define SSALSTANDPIC 2870
#define SSALWALKPIC 2875
#define SSALATTACKPIC 2895
#define SSALDEATHPIC 2910
#define SSALSPECIALPIC 2919
#define SGOLSTANDPIC 2930
#define SGOLWALKPIC 2935
#define SGOLATTACKPIC 2955
#define SGOLPAINPIC 2971
#define SGOLDODGEPIC 2976
#define SGOLDEATHPIC 2978
#define SGOLSPECIALPIC 2987
#define SWATSTANDPIC 2996
#define SWATWALKPIC 3001
#define SWATATTACKPIC 3021
#define SWATPAINPIC 3036
#define SWATDEATHPIC 3041
#define SWATSQUATPIC 3049
#define RS232 3800
#define RS232ATTACK 3805
#define PROBE1 3810


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "compat.h"
#include "build.h"
#include "common.h"
#include "keyboard.h"
#include "control.h"
#include "fx_man.h"
#include "tekwar.h"
#include "config.h"
#include "osdcmds.h"
#include "vfs.h"

#if 0
static int osdcmd_god(osdcmdptr_t UNUSED(parm))
{
    UNREFERENCED_CONST_PARAMETER(parm);

    if (!nNetPlayerCount && !bInDemo)
    {
        DoPassword(3);
    }
    else
        OSD_Printf("god: Not in a single-player game.\n");

    return OSDCMD_OK;
}

static int osdcmd_noclip(osdcmdptr_t UNUSED(parm))
{
    UNREFERENCED_CONST_PARAMETER(parm);

    if (!nNetPlayerCount && !bInDemo)
    {
        DoPassword(6);
    }
    else
    {
        OSD_Printf("noclip: Not in a single-player game.\n");
    }

    return OSDCMD_OK;
}

static int osdcmd_changelevel(osdcmdptr_t parm)
{
    char* p;

    if (parm->numparms != 1) return OSDCMD_SHOWHELP;

    int nLevel = strtol(parm->parms[0], &p, 10);
    if (p[0]) return OSDCMD_SHOWHELP;

    if (nLevel < 0) return OSDCMD_SHOWHELP;

    int nMaxLevels;

    if (!ISDEMOVER) {
        nMaxLevels = 32;
    }
    else {
        nMaxLevels = 4;
    }

    if (nLevel > nMaxLevels)
    {
        OSD_Printf("changelevel: invalid level number\n");
        return OSDCMD_SHOWHELP;
    }

    levelnew = nLevel;
    levelnum = nLevel;

    return OSDCMD_OK;
}
#endif

static inline int osdcmd_quit(osdcmdptr_t UNUSED(parm))
{
    UNREFERENCED_CONST_PARAMETER(parm);
    OSD_ShowDisplay(0);
// TODO    shutdown();
    return OSDCMD_OK;
}

int osdcmd_restartvid(osdcmdptr_t UNUSED(parm))
{
    UNREFERENCED_CONST_PARAMETER(parm);
    videoResetMode();
    if (videoSetGameMode(gSetup.fullscreen,gSetup.xdim,gSetup.ydim,gSetup.bpp,0))
        crash("restartvid: Reset failed...\n");
    onvideomodechange(gSetup.bpp>8);
// TODO   UpdateScreenSize();

    return OSDCMD_OK;
}

static int osdcmd_vidmode(osdcmdptr_t parm)
{
    int32_t newbpp = gSetup.bpp, newwidth = gSetup.xdim,
            newheight = gSetup.ydim, newfs = gSetup.fullscreen;
    int32_t tmp;

    if (parm->numparms < 1 || parm->numparms > 4) return OSDCMD_SHOWHELP;

    switch (parm->numparms)
    {
    case 1: // bpp switch
        tmp = Batol(parm->parms[0]);
        if (!(tmp==8 || tmp==16 || tmp==32))
            return OSDCMD_SHOWHELP;
        newbpp = tmp;
        break;
    case 2: // res switch
        newwidth = Batol(parm->parms[0]);
        newheight = Batol(parm->parms[1]);
        break;
    case 3: // res & bpp switch
    case 4:
        newwidth = Batol(parm->parms[0]);
        newheight = Batol(parm->parms[1]);
        tmp = Batol(parm->parms[2]);
        if (!(tmp==8 || tmp==16 || tmp==32))
            return OSDCMD_SHOWHELP;
        newbpp = tmp;
        if (parm->numparms == 4)
            newfs = (Batol(parm->parms[3]) != 0);
        break;
    }

    if (videoSetGameMode(newfs,newwidth,newheight,newbpp,upscalefactor))
    {
        initprintf("vidmode: Mode change failed!\n");
        if (videoSetGameMode(gSetup.fullscreen, gSetup.xdim, gSetup.ydim, gSetup.bpp, upscalefactor))
            crash("vidmode: Reset failed!\n");
    }
    gSetup.bpp = newbpp;
    gSetup.xdim = newwidth;
    gSetup.ydim = newheight;
    gSetup.fullscreen = newfs;
    onvideomodechange(gSetup.bpp>8);
// TODO    UpdateScreenSize();
    return OSDCMD_OK;
}

static int osdcmd_addpath(osdcmdptr_t parm)
{
    if (parm->numparms != 1)
        return OSDCMD_SHOWHELP;

    addsearchpath(parm->parms[0]);

    return OSDCMD_OK;
}

static int osdcmd_initgroupfile(osdcmdptr_t parm)
{
    if (parm->numparms != 1)
        return OSDCMD_SHOWHELP;

    initgroupfile(parm->parms[0]);

    return OSDCMD_OK;
}

void onvideomodechange(int32_t newmode)
{
#if 0 // TODO
    uint8_t palid = BASEPAL;

#if 0
#ifdef POLYMER
    if (videoGetRenderMode() == REND_POLYMER)
    {
        int32_t i = 0;

        while (i < MAXSPRITES)
        {
            if (actor[i].lightptr)
            {
                polymer_deletelight(actor[i].lightId);
                actor[i].lightptr = NULL;
                actor[i].lightId = -1;
            }
            i++;
        }
    }
#endif
#endif

    videoSetPalette(0, palid, 0);

#endif
}

static int osdcmd_button(osdcmdptr_t parm)
{
    static char const s_gamefunc_[] = "gamefunc_";
    int constexpr strlen_gamefunc_  = ARRAY_SIZE(s_gamefunc_) - 1;

    char const *p = parm->name + strlen_gamefunc_;

//    if (g_player[myconnectindex].ps->gm == MODE_GAME) // only trigger these if in game
    CONTROL_ButtonFlags[CONFIG_FunctionNameToNum(p)] = 1; // FIXME

    return OSDCMD_OK;
}

const char *const ConsoleButtons[] =
{
    "mouse1", "mouse2", "mouse3", "mouse4", "mwheelup",
    "mwheeldn", "mouse5", "mouse6", "mouse7", "mouse8"
};

static int osdcmd_bind(osdcmdptr_t parm)
{
    char tempbuf[256];
    if (parm->numparms==1 && !Bstrcasecmp(parm->parms[0],"showkeys"))
    {
        for (auto & s : sctokeylut)
            OSD_Printf("%s\n", s.key);
        for (auto ConsoleButton : ConsoleButtons)
            OSD_Printf("%s\n",ConsoleButton);
        return OSDCMD_OK;
    }

    if (parm->numparms==0)
    {
        int j=0;

        OSD_Printf("Current key bindings:\n");

        for (int i=0; i<MAXBOUNDKEYS+MAXMOUSEBUTTONS; i++)
            if (CONTROL_KeyIsBound(i))
            {
                j++;
                OSD_Printf("%-9s %s\"%s\"\n", CONTROL_KeyBinds[i].key, CONTROL_KeyBinds[i].repeat?"":"norepeat ",
                           CONTROL_KeyBinds[i].cmdstr);
            }

        if (j == 0)
            OSD_Printf("No binds found.\n");

        return OSDCMD_OK;
    }

    int i, j, repeat;

    for (i=0; i < ARRAY_SSIZE(sctokeylut); i++)
    {
        if (!Bstrcasecmp(parm->parms[0], sctokeylut[i].key))
            break;
    }

    // didn't find the key
    if (i == ARRAY_SSIZE(sctokeylut))
    {
        for (i=0; i<MAXMOUSEBUTTONS; i++)
            if (!Bstrcasecmp(parm->parms[0],ConsoleButtons[i]))
                break;

        if (i >= MAXMOUSEBUTTONS)
            return OSDCMD_SHOWHELP;

        if (parm->numparms < 2)
        {
            if (CONTROL_KeyBinds[MAXBOUNDKEYS + i].cmdstr && CONTROL_KeyBinds[MAXBOUNDKEYS + i ].key)
                OSD_Printf("%-9s %s\"%s\"\n", ConsoleButtons[i], CONTROL_KeyBinds[MAXBOUNDKEYS + i].repeat?"":"norepeat ",
                CONTROL_KeyBinds[MAXBOUNDKEYS + i].cmdstr);
            else OSD_Printf("%s is unbound\n", ConsoleButtons[i]);
            return OSDCMD_OK;
        }

        j = 1;

        repeat = 1;
        if (!Bstrcasecmp(parm->parms[j],"norepeat"))
        {
            repeat = 0;
            j++;
        }

        Bstrcpy(tempbuf,parm->parms[j++]);
        for (; j<parm->numparms; j++)
        {
            Bstrcat(tempbuf," ");
            Bstrcat(tempbuf,parm->parms[j++]);
        }

        CONTROL_BindMouse(i, tempbuf, repeat, ConsoleButtons[i]);

        if (!OSD_ParsingScript())
            OSD_Printf("%s\n",parm->raw);
        return OSDCMD_OK;
    }

    if (parm->numparms < 2)
    {
        if (CONTROL_KeyIsBound(sctokeylut[i].sc))
            OSD_Printf("%-9s %s\"%s\"\n", sctokeylut[i].key, CONTROL_KeyBinds[sctokeylut[i].sc].repeat?"":"norepeat ",
                       CONTROL_KeyBinds[sctokeylut[i].sc].cmdstr);
        else OSD_Printf("%s is unbound\n", sctokeylut[i].key);

        return OSDCMD_OK;
    }

    j = 1;

    repeat = 1;
    if (!Bstrcasecmp(parm->parms[j],"norepeat"))
    {
        repeat = 0;
        j++;
    }

    Bstrcpy(tempbuf,parm->parms[j++]);
    for (; j<parm->numparms; j++)
    {
        Bstrcat(tempbuf," ");
        Bstrcat(tempbuf,parm->parms[j++]);
    }

    CONTROL_BindKey(sctokeylut[i].sc, tempbuf, repeat, sctokeylut[i].key);

    char *cp = tempbuf;

    // Populate the keyboard config menu based on the bind.
    // Take care of processing one-to-many bindings properly, too.
    static char const s_gamefunc_[] = "gamefunc_";
    int constexpr strlen_gamefunc_  = ARRAY_SIZE(s_gamefunc_) - 1;

    while ((cp = Bstrstr(cp, s_gamefunc_)))
    {
        cp += strlen_gamefunc_;

        char *semi = Bstrchr(cp, ';');

        if (semi)
            *semi = 0;

        j = CONFIG_FunctionNameToNum(cp);

        if (semi)
            cp = semi+1;

        if (j != -1)
        {
            KeyboardKeys[j][1] = KeyboardKeys[j][0];
            KeyboardKeys[j][0] = sctokeylut[i].sc;
//            CONTROL_MapKey(j, sctokeylut[i].sc, ud.config.KeyboardKeys[j][0]);

            if (j == gamefunc_Show_Console)
                OSD_CaptureKey(sctokeylut[i].sc);
        }
    }

    if (!OSD_ParsingScript())
        OSD_Printf("%s\n",parm->raw);

    return OSDCMD_OK;
}

static int osdcmd_unbindall(osdcmdptr_t UNUSED(parm))
{
    UNREFERENCED_CONST_PARAMETER(parm);

    for (int i = 0; i < MAXBOUNDKEYS; ++i)
        CONTROL_FreeKeyBind(i);

    for (int i = 0; i < MAXMOUSEBUTTONS; ++i)
        CONTROL_FreeMouseBind(i);

    for (auto &KeyboardKey : KeyboardKeys)
        KeyboardKey[0] = KeyboardKey[1] = 0xff;

    if (!OSD_ParsingScript())
        OSD_Printf("unbound all controls\n");

    return OSDCMD_OK;
}

static int osdcmd_unbind(osdcmdptr_t parm)
{
    if (parm->numparms != 1)
        return OSDCMD_SHOWHELP;

    for (auto & ConsoleKey : sctokeylut)
    {
        if (ConsoleKey.key && !Bstrcasecmp(parm->parms[0], ConsoleKey.key))
        {
            CONTROL_FreeKeyBind(ConsoleKey.sc);
            OSD_Printf("unbound key %s\n", ConsoleKey.key);
            return OSDCMD_OK;
        }
    }

    for (int i = 0; i < MAXMOUSEBUTTONS; i++)
    {
        if (!Bstrcasecmp(parm->parms[0], ConsoleButtons[i]))
        {
            CONTROL_FreeMouseBind(i);
            OSD_Printf("unbound %s\n", ConsoleButtons[i]);
            return OSDCMD_OK;
        }
    }

    return OSDCMD_SHOWHELP;
}

static int osdcmd_unbound(osdcmdptr_t parm)
{
    if (parm->numparms != 1)
        return OSDCMD_OK;

    int const gameFunc = CONFIG_FunctionNameToNum(parm->parms[0]);

    if (gameFunc != -1)
        KeyboardKeys[gameFunc][0] = 0;

    return OSDCMD_OK;
}

static int osdcmd_screenshot(osdcmdptr_t parm)
{
//    KB_ClearKeysDown();
    static const char *fn = "capt0000.png";

    if (parm->numparms == 1 && !Bstrcasecmp(parm->parms[0], "tga"))
        videoCaptureScreenTGA(fn, 0);
    else videoCaptureScreen(fn, 0);

    return OSDCMD_OK;
}

static int osdcmd_cvar_set_game(osdcmdptr_t parm)
{
    int const r = osdcmd_cvar_set(parm);

    if (r != OSDCMD_OK) return r;

    // TODO:
    /*if (!Bstrcasecmp(parm->name, "r_upscalefactor"))
    {
        if (in3dmode())
        {
            videoSetGameMode(fullscreen, xres, yres, bpp, ud.detail);
        }
    }
    else if (!Bstrcasecmp(parm->name, "r_size"))
    {
        ud.statusbarmode = (ud.screen_size < 8);
        G_UpdateScreenArea();
    }*/
    else if (!Bstrcasecmp(parm->name, "r_maxfps") || !Bstrcasecmp(parm->name, "r_maxfpsoffset"))
    {
/* TODO
        if (r_maxfps != 0) r_maxfps = clamp(r_maxfps, 30, 1000);
        g_frameDelay = calcFrameDelay(r_maxfps + r_maxfpsoffset);
*/
    }/*
    else if (!Bstrcasecmp(parm->name, "r_ambientlight"))
    {
        if (r_ambientlight == 0)
            r_ambientlightrecip = 256.f;
        else r_ambientlightrecip = 1.f/r_ambientlight;
    }
    else */if (!Bstrcasecmp(parm->name, "in_mouse"))
    {
        CONTROL_MouseEnabled = (gSetup.usemouse && CONTROL_MousePresent);
    }
    else if (!Bstrcasecmp(parm->name, "in_joystick"))
    {
        CONTROL_JoystickEnabled = (gSetup.usejoystick && CONTROL_JoyPresent);
    }
    else if (!Bstrcasecmp(parm->name, "vid_gamma"))
    {
        // TODO
        //ud.brightness = GAMMA_CALC;
        //ud.brightness <<= 2;
        //videoSetPalette(ud.brightness>>2,g_player[myconnectindex].ps->palette,0);
    }
    else if (!Bstrcasecmp(parm->name, "vid_brightness") || !Bstrcasecmp(parm->name, "vid_contrast"))
    {
        // TODO
        //videoSetPalette(ud.brightness>>2,g_player[myconnectindex].ps->palette,0);
    }
    //else if (!Bstrcasecmp(parm->name, "hud_scale")
    //         || !Bstrcasecmp(parm->name, "hud_statusbarmode")
    //         || !Bstrcasecmp(parm->name, "r_rotatespritenowidescreen"))
    //{
    //    G_UpdateScreenArea();
    //}
    //else if (!Bstrcasecmp(parm->name, "skill"))
    //{
    //    if (numplayers > 1)
    //        return r;
    //
    //    ud.player_skill = ud.m_player_skill;
    //}
    //else if (!Bstrcasecmp(parm->name, "color"))
    //{
    //    ud.color = G_CheckPlayerColor(ud.color);
    //    g_player[0].ps->palookup = g_player[0].pcolor = ud.color;
    //}
    //else if (!Bstrcasecmp(parm->name, "osdscale"))
    //{
    //    osdrscale = 1.f/osdscale;
    //
    //    if (xdim && ydim)
    //        OSD_ResizeDisplay(xdim, ydim);
    //}
    //else if (!Bstrcasecmp(parm->name, "wchoice"))
    //{
    //    if (parm->numparms == 1)
    //    {
    //        if (g_forceWeaponChoice) // rewrite ud.wchoice because osdcmd_cvar_set already changed it
    //        {
    //            int j = 0;
    //
    //            while (j < 10)
    //            {
    //                ud.wchoice[j] = g_player[myconnectindex].wchoice[j] + '0';
    //                j++;
    //            }
    //
    //            ud.wchoice[j] = 0;
    //        }
    //        else
    //        {
    //            char const *c = parm->parms[0];
    //
    //            if (*c)
    //            {
    //                int j = 0;
    //
    //                while (*c && j < 10)
    //                {
    //                    g_player[myconnectindex].wchoice[j] = *c - '0';
    //                    c++;
    //                    j++;
    //                }
    //
    //                while (j < 10)
    //                {
    //                    if (j == 9)
    //                        g_player[myconnectindex].wchoice[9] = 1;
    //                    else
    //                        g_player[myconnectindex].wchoice[j] = 2;
    //
    //                    j++;
    //                }
    //            }
    //        }
    //
    //        g_forceWeaponChoice = 0;
    //    }
    //
    //    /*    Net_SendClientInfo();*/
    //}

    return r;
}

int32_t registerosdcommands(void)
{
    FX_InitCvars();
    char tempbuf[256];
    static osdcvardata_t cvars_game[] =
    {
        // TODO:
        //{ "benchmarkmode", "Set the benchmark mode (0: off, 1: performance test, 2: generate reference screenshots for correctness testing)", (void *) &g_BenchmarkMode, CVAR_INT|CVAR_NOSAVE, 0, 2 },

        //{ "crosshair", "enable/disable crosshair", (void *)&ud.crosshair, CVAR_BOOL, 0, 1 },

        //{ "cl_autoaim", "enable/disable weapon autoaim", (void *)&ud.config.AutoAim, CVAR_INT|CVAR_MULTI, 0, 3 },
        //{ "cl_automsg", "enable/disable automatically sending messages to all players", (void *)&ud.automsg, CVAR_BOOL, 0, 1 },
// TODO        { "cl_autorun", "enable/disable autorun", (void *)&auto_run, CVAR_BOOL, 0, 1 },

        //{ "cl_autosave", "enable/disable autosaves", (void *) &ud.autosave, CVAR_BOOL, 0, 1 },
        //{ "cl_autosavedeletion", "enable/disable automatic deletion of autosaves", (void *) &ud.autosavedeletion, CVAR_BOOL, 0, 1 },
        //{ "cl_maxautosaves", "number of autosaves to keep before deleting the oldest", (void *) &ud.maxautosaves, CVAR_INT, 1, 100 },

        //{ "cl_autovote", "enable/disable automatic voting", (void *)&ud.autovote, CVAR_INT, 0, 2 },

        //{ "cl_cheatmask", "configure what cheats show in the cheats menu", (void *)&cl_cheatmask, CVAR_UINT, 0, ~0 },

        //{ "cl_obituaries", "enable/disable multiplayer death messages", (void *)&ud.obituaries, CVAR_BOOL, 0, 1 },
        //{ "cl_democams", "enable/disable demo playback cameras", (void *)&ud.democams, CVAR_BOOL, 0, 1 },

        //{ "cl_idplayers", "enable/disable name display when aiming at opponents", (void *)&ud.idplayers, CVAR_BOOL, 0, 1 },

// TODO        { "cl_runmode", "enable/disable modernized run key operation", (void *)&runkey_mode, CVAR_BOOL, 0, 1 },

//        { "cl_showcoords", "show your position in the game world", (void *)&ud.coords, CVAR_INT, 0,
//#ifdef USE_OPENGL
//          2
//#else
//          1
//#endif
//        },

        //{ "cl_viewbob", "enable/disable player head bobbing", (void *)&ud.viewbob, CVAR_BOOL, 0, 1 },

        //{ "cl_weaponsway", "enable/disable player weapon swaying", (void *)&ud.weaponsway, CVAR_BOOL, 0, 1 },
        //{ "cl_weaponswitch", "enable/disable auto weapon switching", (void *)&ud.weaponswitch, CVAR_INT|CVAR_MULTI, 0, 7 },

        //{ "color", "changes player palette", (void *)&ud.color, CVAR_INT|CVAR_MULTI, 0, MAXPALOOKUPS-1 },

        //{ "crosshairscale","changes the size of the crosshair", (void *)&ud.crosshairscale, CVAR_INT, 10, 100 },

        //{ "demorec_diffs","enable/disable diff recording in demos",(void *)&demorec_diffs_cvar, CVAR_BOOL, 0, 1 },
        //{ "demorec_force","enable/disable forced demo recording",(void *)&demorec_force_cvar, CVAR_BOOL|CVAR_NOSAVE, 0, 1 },
        //{
        //    "demorec_difftics","sets game tic interval after which a diff is recorded",
        //    (void *)&demorec_difftics_cvar, CVAR_INT, 2, 60*REALGAMETICSPERSEC
        //},
        //{ "demorec_diffcompress","Compression method for diffs. (0: none, 1: KSLZW)",(void *)&demorec_diffcompress_cvar, CVAR_BOOL, 0, 1 },
        //{ "demorec_synccompress","Compression method for input. (0: none, 1: KSLZW)",(void *)&demorec_synccompress_cvar, CVAR_BOOL, 0, 1 },
        //{ "demorec_seeds","enable/disable recording of random seed for later sync checking",(void *)&demorec_seeds_cvar, CVAR_BOOL, 0, 1 },
        //{ "demoplay_diffs","enable/disable application of diffs in demo playback",(void *)&demoplay_diffs, CVAR_BOOL, 0, 1 },
        //{ "demoplay_showsync","enable/disable display of sync status",(void *)&demoplay_showsync, CVAR_BOOL, 0, 1 },

// TODO        { "fov", "change the field of view", (void *)&gFov, CVAR_INT|CVAR_FUNCPTR, 60, 140 },

        //{ "hud_althud", "enable/disable alternate mini-hud", (void *)&ud.althud, CVAR_BOOL, 0, 1 },
        //{ "hud_custom", "change the custom hud", (void *)&ud.statusbarcustom, CVAR_INT, 0, ud.statusbarrange },
        //{ "hud_position", "aligns the status bar to the bottom/top", (void *)&ud.hudontop, CVAR_BOOL, 0, 1 },
        //{ "hud_bgstretch", "enable/disable background image stretching in wide resolutions", (void *)&ud.bgstretch, CVAR_BOOL, 0, 1 },
        //{ "hud_messagetime", "length of time to display multiplayer chat messages", (void *)&ud.msgdisptime, CVAR_INT, 0, 3600 },
        //{ "hud_numbertile", "first tile in alt hud number set", (void *)&althud_numbertile, CVAR_INT, 0, MAXUSERTILES-10 },
        //{ "hud_numberpal", "pal for alt hud numbers", (void *)&althud_numberpal, CVAR_INT, 0, MAXPALOOKUPS-1 },
        //{ "hud_shadows", "enable/disable althud shadows", (void *)&althud_shadows, CVAR_BOOL, 0, 1 },
        //{ "hud_flashing", "enable/disable althud flashing", (void *)&althud_flashing, CVAR_BOOL, 0, 1 },
        //{ "hud_glowingquotes", "enable/disable \"glowing\" quote text", (void *)&hud_glowingquotes, CVAR_BOOL, 0, 1 },
        //{ "hud_scale","changes the hud scale", (void *)&ud.statusbarscale, CVAR_INT|CVAR_FUNCPTR, 36, 100 },
        //{ "hud_showmapname", "enable/disable map name display on load", (void *)&hud_showmapname, CVAR_BOOL, 0, 1 },
        //{ "hud_stats", "enable/disable level statistics display", (void *)&ud.levelstats, CVAR_BOOL, 0, 1 },
        //{ "hud_textscale", "sets multiplayer chat message size", (void *)&ud.textscale, CVAR_INT, 100, 400 },
        //{ "hud_weaponscale","changes the weapon scale", (void *)&ud.weaponscale, CVAR_INT, 10, 100 },
        //{ "hud_statusbarmode", "change overlay mode of status bar", (void *)&ud.statusbarmode, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 },

//#ifdef EDUKE32_TOUCH_DEVICES
//        { "hud_hidestick", "hide the touch input stick", (void *)&droidinput.hideStick, CVAR_BOOL, 0, 1 },
//#endif

        { "in_joystick","enables input from the joystick if it is present",(void *)&gSetup.usejoystick, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 },
        { "in_mouse","enables input from the mouse if it is present",(void *)&gSetup.usemouse, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 },

// TODO        { "in_aimmode", "0:toggle, 1:hold to aim", (void *)&mouseaiming, CVAR_BOOL, 0, 1 },
        {
            "in_mousebias", "emulates the original mouse code's weighting of input towards whichever axis is moving the most at any given time",
            (void *)&MouseBias, CVAR_INT, 0, 32
        },
        { "in_mousedeadzone", "amount of mouse movement to filter out", (void *)&MouseDeadZone, CVAR_INT, 0, 512 },
// TODO        { "in_mouseflip", "invert vertical mouse movement", (void *)&mouseflip, CVAR_BOOL, 0, 1 },
// TODO        { "in_mousemode", "toggles vertical mouse view", (void *)&aimmode, CVAR_BOOL, 0, 1 },

        { "in_mousexsensitivity", "horizontal mouse sensitivity", (void*)&CONTROL_MouseAxesSensitivity[0], CVAR_FLOAT, 0, 25 },
        { "in_mouseysensitivity", "vertical mouse sensitivity",   (void*)&CONTROL_MouseAxesSensitivity[1], CVAR_FLOAT, 0, 25 },

        //{ "mus_enabled", "enables/disables music", (void *)&ud.config.MusicToggle, CVAR_BOOL, 0, 1 },
        //{ "mus_volume", "controls music volume", (void *)&ud.config.MusicVolume, CVAR_INT, 0, 255 },

        //{ "osdhightile", "enable/disable hires art replacements for console text", (void *)&osdhightile, CVAR_BOOL, 0, 1 },
        //{ "osdscale", "adjust console text size", (void *)&osdscale, CVAR_FLOAT|CVAR_FUNCPTR, 1, 4 },

        //{ "r_camrefreshdelay", "minimum delay between security camera sprite updates, 120 = 1 second", (void *)&ud.camera_time, CVAR_INT, 1, 240 },
        //{ "r_drawweapon", "enable/disable weapon drawing", (void *)&ud.drawweapon, CVAR_INT, 0, 2 },
// TODO        { "r_showfps", "show the frame rate counter", (void *)&r_showfps, CVAR_INT, 0, 3 },
        //{ "r_showfpsperiod", "time in seconds before averaging min and max stats for r_showfps 2+", (void *)&ud.frameperiod, CVAR_INT, 0, 5 },
        //{ "r_shadows", "enable/disable sprite and model shadows", (void *)&ud.shadows, CVAR_BOOL, 0, 1 },
        //{ "r_size", "change size of viewable area", (void *)&ud.screen_size, CVAR_INT|CVAR_FUNCPTR, 0, 64 },
        //{ "r_rotatespritenowidescreen", "pass bit 1024 to all CON rotatesprite calls", (void *)&g_rotatespriteNoWidescreen, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 },
        //{ "r_upscalefactor", "increase performance by rendering at upscalefactor less than the screen resolution and upscale to the full resolution in the software renderer", (void *)&ud.detail, CVAR_INT|CVAR_FUNCPTR, 1, 16 },
        { "r_precache", "enable/disable the pre-level caching routine", (void *)&useprecache, CVAR_BOOL, 0, 1 },

       // { "r_ambientlight", "sets the global map light level",(void *)&r_ambientlight, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 },

        { "sensitivity","changes the mouse sensitivity", (void *)&CONTROL_MouseSensitivity, CVAR_FLOAT|CVAR_FUNCPTR, 0, 25 },

        //{ "skill","changes the game skill setting", (void *)&ud.m_player_skill, CVAR_INT|CVAR_FUNCPTR|CVAR_NOSAVE/*|CVAR_NOMULTI*/, 0, 5 },

        //{ "snd_ambience", "enables/disables ambient sounds", (void *)&ud.config.AmbienceToggle, CVAR_BOOL, 0, 1 },
        //{ "snd_enabled", "enables/disables sound effects", (void *)&ud.config.SoundToggle, CVAR_BOOL, 0, 1 },
        //{ "snd_fxvolume", "controls volume for sound effects", (void *)&ud.config.FXVolume, CVAR_INT, 0, 255 },
        //{ "snd_mixrate", "sound mixing rate", (void *)&ud.config.MixRate, CVAR_INT, 0, 48000 },
        //{ "snd_numchannels", "the number of sound channels", (void *)&ud.config.NumChannels, CVAR_INT, 0, 2 },
        //{ "snd_numvoices", "the number of concurrent sounds", (void *)&ud.config.NumVoices, CVAR_INT, 1, 128 },
        //{ "snd_reversestereo", "reverses the stereo channels", (void *)&ud.config.ReverseStereo, CVAR_BOOL, 0, 1 },
        //{ "snd_speech", "enables/disables player speech", (void *)&ud.config.VoiceToggle, CVAR_INT, 0, 5 },

        //{ "team","change team in multiplayer", (void *)&ud.team, CVAR_INT|CVAR_MULTI, 0, 3 },

        { "vid_gamma","adjusts gamma component of gamma ramp",(void *)&g_videoGamma, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 },
        { "vid_contrast","adjusts contrast component of gamma ramp",(void *)&g_videoContrast, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 },
        { "vid_brightness","adjusts brightness component of gamma ramp",(void *)&g_videoBrightness, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 },
        //{ "wchoice","sets weapon autoselection order", (void *)ud.wchoice, CVAR_STRING|CVAR_FUNCPTR, 0, MAX_WEAPONS },
    };

    //osdcmd_cheatsinfo_stat.cheatnum = -1;

    for (auto & cv : cvars_game)
    {
        switch (cv.flags & (CVAR_FUNCPTR|CVAR_MULTI))
        {
            case CVAR_FUNCPTR:
                OSD_RegisterCvar(&cv, osdcmd_cvar_set_game); break;
            //case CVAR_MULTI:
            //case CVAR_FUNCPTR|CVAR_MULTI:
            //    OSD_RegisterCvar(&cv, osdcmd_cvar_set_multi); break;
            default:
                OSD_RegisterCvar(&cv, osdcmd_cvar_set); break;
        }
    }

    //if (VOLUMEONE)
// TODO    OSD_RegisterFunction("changelevel","changelevel <level>: warps to the given level", osdcmd_changelevel);
    //else
    //{
    //    OSD_RegisterFunction("changelevel","changelevel <volume> <level>: warps to the given level", osdcmd_changelevel);
    //    OSD_RegisterFunction("map","map <mapfile>: loads the given user map", osdcmd_map);
    //    OSD_RegisterFunction("demo","demo <demofile or demonum>: starts the given demo", osdcmd_demo);
    //}

    OSD_RegisterFunction("addpath","addpath <path>: adds path to game filesystem", osdcmd_addpath);
    OSD_RegisterFunction("bind",R"(bind <key> <string>: associates a keypress with a string of console input. Type "bind showkeys" for a list of keys and "listsymbols" for a list of valid console commands.)", osdcmd_bind);
    //OSD_RegisterFunction("cmenu","cmenu <#>: jumps to menu", osdcmd_cmenu);
    //OSD_RegisterFunction("crosshaircolor","crosshaircolor: changes the crosshair color", osdcmd_crosshaircolor);

    for (auto & func : gamefunctions)
    {
        if (func[0] == '\0')
            continue;

//        if (!Bstrcmp(gamefunctions[i],"Show_Console")) continue;

        Bsprintf(tempbuf, "gamefunc_%s", func);

        char *const t = Bstrtolower(Xstrdup(tempbuf));

        Bstrcat(tempbuf, ": game button");

        OSD_RegisterFunction(t, Xstrdup(tempbuf), osdcmd_button);
    }

    //OSD_RegisterFunction("give","give <all|health|weapons|ammo|armor|keys|inventory>: gives requested item", osdcmd_give);
// TODO    OSD_RegisterFunction("god","god: toggles god mode", osdcmd_god);
    //OSD_RegisterFunction("activatecheat","activatecheat <id>: activates a cheat code", osdcmd_activatecheat);

    OSD_RegisterFunction("initgroupfile","initgroupfile <path>: adds a grp file into the game filesystem", osdcmd_initgroupfile);
//#ifdef DEBUGGINGAIDS
//    OSD_RegisterFunction("inittimer","debug", osdcmd_inittimer);
//#endif
    //OSD_RegisterFunction("music","music E<ep>L<lev>: change music", osdcmd_music);

// TODO    OSD_RegisterFunction("noclip","noclip: toggles clipping mode", osdcmd_noclip);

    //OSD_RegisterFunction("printtimes", "printtimes: prints VM timing statistics", osdcmd_printtimes);

    //OSD_RegisterFunction("purgesaves", "purgesaves: deletes obsolete and unreadable save files", osdcmd_purgesaves);

    //OSD_RegisterFunction("quicksave","quicksave: performs a quick save", osdcmd_quicksave);
    //OSD_RegisterFunction("quickload","quickload: performs a quick load", osdcmd_quickload);
    OSD_RegisterFunction("quit","quit: exits the game immediately", osdcmd_quit);
    OSD_RegisterFunction("exit","exit: exits the game immediately", osdcmd_quit);

    //OSD_RegisterFunction("restartmap", "restartmap: restarts the current map", osdcmd_restartmap);
    //OSD_RegisterFunction("restartsound","restartsound: reinitializes the sound system",osdcmd_restartsound);
    OSD_RegisterFunction("restartvid","restartvid: reinitializes the video mode",osdcmd_restartvid);
    OSD_RegisterFunction("screenshot","screenshot [format]: takes a screenshot.", osdcmd_screenshot);

    //OSD_RegisterFunction("spawn","spawn <picnum> [palnum] [cstat] [ang] [x y z]: spawns a sprite with the given properties",osdcmd_spawn);

    OSD_RegisterFunction("unbind","unbind <key>: unbinds a key", osdcmd_unbind);
    OSD_RegisterFunction("unbindall","unbindall: unbinds all keys", osdcmd_unbindall);
    OSD_RegisterFunction("unbound", NULL, osdcmd_unbound);

    OSD_RegisterFunction("vidmode","vidmode <xdim> <ydim> <bpp> <fullscreen>: change the video mode",osdcmd_vidmode);
#ifdef USE_OPENGL
    baselayer_osdcmd_vidmode_func = osdcmd_vidmode;
#endif

    return 0;
}

void GAME_onshowosd(int shown)
{
    // G_UpdateScreenArea();

    mouseLockToWindow((!shown) + 2);

    //osdshown = shown;

    // XXX: it's weird to fake a keypress like this.
//    if (numplayers == 1 && ((shown && !ud.pause_on) || (!shown && ud.pause_on)))
//        KB_KeyDown[sc_Pause] = 1;
}

void GAME_clearbackground(int numcols, int numrows)
{
    COMMON_clearbackground(numcols, numrows);
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef osdcmds_h_
#define osdcmds_h_

#ifdef __cplusplus
extern "C" {
#endif

int32_t registerosdcommands(void);
void onvideomodechange(int32_t newmode);
void GAME_onshowosd(int shown);
void GAME_clearbackground(int numcols, int numrows);

//extern float r_ambientlight,r_ambientlightrecip;

extern const char *const ConsoleButtons[];

//extern uint32_t cl_cheatmask;

#ifdef __cplusplus
}
#endif

#endif	// osdcmds_h_