diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 6df69d53..41a25052 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -1,13 +1,10 @@ #include #include "loader/component_loader.hpp" - -#include "game/game.hpp" -#include "game/dvars.hpp" - #include "command.hpp" #include "console.hpp" #include "game_console.hpp" +#include "game/game.hpp" #include #include @@ -18,10 +15,9 @@ namespace command { namespace { - utils::hook::detour dvar_command_hook; + utils::hook::detour client_command_hook; std::unordered_map> handlers; - std::unordered_map> handlers_sv; void main_handler() @@ -45,53 +41,78 @@ namespace command handlers_sv[command](client_num, params); } - dvar_command_hook.invoke(client_num, a2); + client_command_hook.invoke(client_num, a2); } - void enum_assets(const game::XAssetType type, const std::function& callback, const bool includeOverride) + // Shamelessly stolen from Quake3 + // https://github.com/id-Software/Quake-III-Arena/blob/dbe4ddb10315479fc00086f08e25d968b4b43c49/code/qcommon/common.c#L364 + void parse_command_line() { - game::DB_EnumXAssets_Internal(type, static_cast([](game::XAssetHeader header, void* data) - { - const auto& cb = *static_cast*>(data); - cb(header); - }), &callback, includeOverride); - } - - game::dvar_t* dvar_command_stub() - { - const params args; - - if (args.size() <= 0) + static auto parsed = false; + if (parsed) { - return 0; + return; } - const auto dvar = game::Dvar_FindVar(args[0]); + static std::string comand_line_buffer = GetCommandLineA(); + auto* command_line = comand_line_buffer.data(); - if (dvar) + auto& com_num_console_lines = *reinterpret_cast(0x142623FB4); //H1(1.4) + auto* com_console_lines = reinterpret_cast(0x142623FC0); //H1(1.4) + + auto inq = false; + com_console_lines[0] = command_line; + com_num_console_lines = 0; + + while (*command_line) { - if (args.size() == 1) + if (*command_line == '"') { - const auto current = game::Dvar_ValueToString(dvar, dvar->current, 0); - const auto reset = game::Dvar_ValueToString(dvar, dvar->reset, 0); - - game_console::print(game_console::con_type_info, "\"%s\" is: \"%s\" default: \"%s\" hash: %i", - args[0], current, reset, dvar->hash); - - game_console::print(game_console::con_type_info, " %s\n", - dvars::dvar_get_domain(dvar->type, dvar->domain).data()); + inq = !inq; } - //else - //{ - // char command[0x1000] = { 0 }; - // game::Dvar_GetCombinedString(command, 1); - // game::Dvar_SetCommand(dvar->name, command); - //} + // look for a + separating character + // if commandLine came from a file, we might have real line seperators + if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r') + { + if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES + { + break; + } + com_console_lines[com_num_console_lines] = command_line + 1; + com_num_console_lines++; + *command_line = '\0'; + } + command_line++; + } + parsed = true; + } - return dvar; + void parse_commandline_stub() + { + parse_command_line(); + reinterpret_cast(0x1400D8210)(); // mwr: test + } + } + + void read_startup_variable(const std::string& dvar) + { + // parse the commandline if it's not parsed + parse_command_line(); + + auto& com_num_console_lines = *reinterpret_cast(0x142623FB4); //H1(1.4) + auto* com_console_lines = reinterpret_cast(0x142623FC0); //H1(1.4) + + for (int i = 0; i < com_num_console_lines; i++) + { + game::Cmd_TokenizeString(com_console_lines[i]); + + // only +set dvar value + if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar) + { + game::Dvar_SetCommand(game::Cmd_Argv(1), game::Cmd_Argv(2)); } - return 0; + game::Cmd_EndTokenizeString(); } } @@ -164,8 +185,16 @@ namespace command game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate()); } + void add_test(const char* name, void (*callback)()) + { + static game::cmd_function_s cmd_test; + return game::Cmd_AddCommandInternal(name, callback, &cmd_test); + } + void add(const char* name, const std::function& callback) { + static game::cmd_function_s cmd_test; + const auto command = utils::string::to_lower(name); if (handlers.find(command) == handlers.end()) @@ -207,73 +236,13 @@ namespace command } } - void parse_command_line() + void enum_assets(const game::XAssetType type, const std::function& callback, const bool includeOverride) { - static auto parsed = false; - if (parsed) - { - return; - } - - static std::string comand_line_buffer = GetCommandLineA(); - auto* command_line = comand_line_buffer.data(); - - auto& com_num_console_lines = *reinterpret_cast(0x142623FB4); - auto* com_console_lines = reinterpret_cast(0x142623FC0); - - auto inq = false; - com_console_lines[0] = command_line; - com_num_console_lines = 0; - - while (*command_line) - { - if (*command_line == '"') + game::DB_EnumXAssets_Internal(type, static_cast([](game::XAssetHeader header, void* data) { - inq = !inq; - } - // look for a + separating character - // if commandLine came from a file, we might have real line seperators - if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r') - { - if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES - { - break; - } - com_console_lines[com_num_console_lines] = command_line + 1; - com_num_console_lines++; - *command_line = '\0'; - } - command_line++; - } - parsed = true; - } - - void parse_commandline_stub() - { - parse_command_line(); - reinterpret_cast(0x1400D8210)(); // mwr: test - } - - void read_startup_variable(const std::string& dvar) - { - // parse the commandline if it's not parsed - parse_command_line(); - - auto& com_num_console_lines = *reinterpret_cast(0x142623FB4); - auto* com_console_lines = reinterpret_cast(0x142623FC0); - - for (int i = 0; i < com_num_console_lines; i++) - { - game::Cmd_TokenizeString(com_console_lines[i]); - - // only +set dvar value - if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar) - { - game::Dvar_SetCommand(game::Cmd_Argv(1), game::Cmd_Argv(2)); - } - - game::Cmd_EndTokenizeString(); - } + const auto& cb = *static_cast*>(data); + cb(header); + }), &callback, includeOverride); } class component final : public component_interface @@ -281,138 +250,447 @@ namespace command public: void post_unpack() override { - //utils::hook::jump(game::base_address + 0x000000, dvar_command_stub, true); // H1 - - static game::cmd_function_s cmd_test; - - game::Cmd_AddCommandInternal("quit", game::Com_Quit_f, &cmd_test); - /*game::Cmd_AddCommandInternal("connect", []() { - - }, game::cmd_function_s);*/ - //add_raw("quit", main_handler); - - //add("quit", game::Com_Quit_f); - - //add("startmap", [](const params& params) - //{ - // const auto map = params.get(1); - - // const auto exists = utils::hook::invoke(game::base_address + 0x412B50, map, 0); - - // if (!exists) - // { - // game_console::print(game_console::con_type_error, "map '%s' not found\n", map); - // return; - // } - - // // SV_SpawnServer - // utils::hook::invoke(game::base_address + 0x6B3AA0, map, 0, 0, 0, 0); - //}); - - /*add("say", [](const params& params) + if (game::environment::is_sp()) { - chat::print(params.join(1)); - });*/ + add_commands_sp(); + } + else + { + utils::hook::call(0x1400D728F, &parse_commandline_stub); // MWR TEST - //add("listassetpool", [](const params& params) - //{ - // if (params.size() < 2) - // { - // game_console::print(game_console::con_type_info, "listassetpool : list all the assets in the specified pool\n"); + add_commands_mp(); + } + add_commands_generic(); + } - // for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++) - // { - // game_console::print(game_console::con_type_info, "%d %s\n", i, game::g_assetNames[i]); - // } - // } - // else - // { - // const auto type = static_cast(atoi(params.get(1))); + private: + static void add_commands_generic() + { + add_test("quit", game::Com_Quit_f); + add("quit_hard", utils::nt::raise_hard_exception); + add("crash", []() + { + *reinterpret_cast(1) = 0; + }); - // if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT) - // { - // game_console::print(game_console::con_type_info, "Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1); - // return; - // } + /*add("consoleList", [](const params& params) + { + const std::string input = params.get(1); - // game_console::print(game_console::con_type_info, "Listing assets in pool %s\n", game::g_assetNames[type]); + std::vector matches; + game_console::find_matches(input, matches, false); - // enum_assets(type, [type](const game::XAssetHeader header) - // { - // const auto asset = game::XAsset{type, header}; - // const auto* const asset_name = game::DB_GetXAssetName(&asset); - // //const auto entry = game::DB_FindXAssetEntry(type, asset_name); - // //TODO: display which zone the asset is from - // game_console::print(game_console::con_type_info, "%s\n", asset_name); - // }, true); - // } - //}); + for (auto& match : matches) + { + auto* dvar = game::Dvar_FindVar(match.c_str()); + if (!dvar) + { + console::info("[CMD]\t %s\n", match.c_str()); + } + else + { + console::info("[DVAR]\t%s \"%s\"\n", match.c_str(), game::Dvar_ValueToString(dvar, dvar->current, 0)); + } + } - //add("commandDump", []() - //{ - // printf("======== Start command dump =========\n"); + console::info("Total %i matches\n", matches.size()); + }); - // game::cmd_function_s* cmd = (*game::cmd_functions); + add("dvarDump", [](const params& argument) + { + console::info("================================ DVAR DUMP ========================================\n"); + std::string filename; + if (argument.size() == 2) + { + filename = "s1x/"; + filename.append(argument[1]); + if (!filename.ends_with(".txt")) + { + filename.append(".txt"); + } + } + for (auto i = 0; i < *game::dvarCount; i++) + { + const auto dvar = game::sortedDvars[i]; + if (dvar) + { + if (!filename.empty()) + { + const auto line = std::format("{} \"{}\"\r\n", dvar->hash, + game::Dvar_ValueToString(dvar, dvar->current, 0)); + utils::io::write_file(filename, line, i != 0); + } + console::info("%s \"%s\"\n", dvar->hash, + game::Dvar_ValueToString(dvar, dvar->current, 0)); + } + } + console::info("\n%i dvars\n", *game::dvarCount); + console::info("================================ END DVAR DUMP ====================================\n"); + }); - // while (cmd) - // { - // if (cmd->name) - // { - // game_console::print(game_console::con_type_info, "%s\n", cmd->name); - // } + add("commandDump", [](const params& argument) + { + console::info("================================ COMMAND DUMP =====================================\n"); + game::cmd_function_s* cmd = (*game::cmd_functions); + std::string filename; + if (argument.size() == 2) + { + filename = "s1x/"; + filename.append(argument[1]); + if (!filename.ends_with(".txt")) + { + filename.append(".txt"); + } + } + int i = 0; + while (cmd) + { + if (cmd->name) + { + if (!filename.empty()) + { + const auto line = std::format("{}\r\n", cmd->name); + utils::io::write_file(filename, line, i != 0); + } + console::info("%s\n", cmd->name); + i++; + } + cmd = cmd->next; + } + console::info("\n%i commands\n", i); + console::info("================================ END COMMAND DUMP =================================\n"); + }); - // cmd = cmd->next; - // } + add("listassetpool", [](const params& params) + { + if (params.size() < 2) + { + console::info("listassetpool [filter]: list all the assets in the specified pool\n"); - // printf("======== End command dump =========\n"); - //}); + for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++) + { + console::info("%d %s\n", i, game::g_assetNames[i]); + } + } + else + { + const auto type = static_cast(atoi(params.get(1))); + if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT) + { + console::error("Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1); + return; + } - /*add("god", []() + console::info("Listing assets in pool %s\n", game::g_assetNames[type]); + + const std::string filter = params.get(2); + enum_assets(type, [type, filter](const game::XAssetHeader header) + { + const auto asset = game::XAsset{ type, header }; + const auto* const asset_name = game::DB_GetXAssetName(&asset); + //const auto entry = game::DB_FindXAssetEntry(type, asset_name); + //TODO: display which zone the asset is from + + if (!filter.empty() && !game_console::match_compare(filter, asset_name, false)) + { + return; + } + + console::info("%s\n", asset_name); + }, true); + } + }); + + add("vstr", [](const params& params) + { + if (params.size() < 2) + { + console::info("vstr : execute a variable command\n"); + return; + } + + const auto* dvarName = params.get(1); + const auto* dvar = game::Dvar_FindVar(dvarName); + + if (dvar == nullptr) + { + console::info("%s doesn't exist\n", dvarName); + return; + } + + if (dvar->type != game::dvar_type::string + && dvar->type != game::dvar_type::enumeration) + { + console::info("%s is not a string-based dvar\n", dvar->hash); + return; + } + + execute(dvar->current.string); + });*/ + } + + static void add_commands_sp() + { + add("god", []() { if (!game::SV_Loaded()) { return; } - game::mp::g_entities[0].flags ^= game::FL_GODMODE; + game::sp::g_entities[0].flags ^= 1; game::CG_GameMessage(0, utils::string::va("godmode %s", - game::g_entities[0].flags & game::FL_GODMODE + game::sp::g_entities[0].flags & 1 ? "^2on" : "^1off")); + }); + + /*add("demigod", []() + { + if (!game::SV_Loaded()) + { + return; + } + + game::sp::g_entities[0].flags ^= 2; + game::CG_GameMessage(0, utils::string::va("demigod mode %s", + game::sp::g_entities[0].flags & 2 + ? "^2on" + : "^1off")); + }); + + add("notarget", []() + { + if (!game::SV_Loaded()) + { + return; + } + + game::sp::g_entities[0].flags ^= 4; + game::CG_GameMessage(0, utils::string::va("notarget %s", + game::sp::g_entities[0].flags & 4 + ? "^2on" + : "^1off")); + }); + + add("noclip", []() + { + if (!game::SV_Loaded()) + { + return; + } + + game::sp::g_entities[0].client->flags ^= 1; + game::CG_GameMessage(0, utils::string::va("noclip %s", + game::sp::g_entities[0].client->flags & 1 + ? "^2on" + : "^1off")); + }); + + add("ufo", []() + { + if (!game::SV_Loaded()) + { + return; + } + + game::sp::g_entities[0].client->flags ^= 2; + game::CG_GameMessage(0, utils::string::va("ufo %s", + game::sp::g_entities[0].client->flags & 2 + ? "^2on" + : "^1off")); + }); + + add("give", [](const params& params) + { + if (!game::SV_Loaded()) + { + return; + } + + if (params.size() < 2) + { + game::CG_GameMessage(0, "You did not specify a weapon name"); + return; + } + + auto ps = game::SV_GetPlayerstateForClientNum(0); + const auto wp = game::G_GetWeaponForName(params.get(1)); + if (wp) + { + if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0)) + { + game::G_InitializeAmmo(ps, wp, 0); + game::G_SelectWeapon(0, wp); + } + } + }); + + add("take", [](const params& params) + { + if (!game::SV_Loaded()) + { + return; + } + + if (params.size() < 2) + { + game::CG_GameMessage(0, "You did not specify a weapon name"); + return; + } + + auto ps = game::SV_GetPlayerstateForClientNum(0); + const auto wp = game::G_GetWeaponForName(params.get(1)); + if (wp) + { + game::G_TakePlayerWeapon(ps, wp); + } });*/ + } + static void add_commands_mp() + { + //client_command_hook.create(0x1402E98F0, &client_command); + /*add_sv("god", [](const int client_num, const params_sv&) + { + if (!game::Dvar_FindVar("sv_cheats")->current.enabled) + { + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"Cheats are not enabled on this server\""); + return; + } - //add("notarget", []() - //{ - // if (!game::SV_Loaded()) - // { - // return; - // } + game::mp::g_entities[client_num].flags ^= 1; + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + utils::string::va("f \"godmode %s\"", + game::mp::g_entities[client_num].flags & 1 + ? "^2on" + : "^1off")); + }); - // game::g_entities[0].flags ^= game::FL_NOTARGET; - // game::CG_GameMessage(0, utils::string::va("notarget %s", - // game::g_entities[0].flags & game::FL_NOTARGET - // ? "^2on" - // : "^1off")); - //}); + add_sv("demigod", [](const int client_num, const params_sv&) + { + if (!game::Dvar_FindVar("sv_cheats")->current.enabled) + { + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"Cheats are not enabled on this server\""); + return; + } - //add("noclip", []() - //{ - // if (!game::SV_Loaded()) - // { - // return; - // } + game::mp::g_entities[client_num].flags ^= 2; + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + utils::string::va("f \"demigod mode %s\"", + game::mp::g_entities[client_num].flags & 2 + ? "^2on" + : "^1off")); + }); - // game::g_entities[0].client->flags ^= 1; - // game::CG_GameMessage(0, utils::string::va("noclip %s", - // game::g_entities[0].client->flags & 1 - // ? "^2on" - // : "^1off")); - //}); + add_sv("notarget", [](const int client_num, const params_sv&) + { + if (!game::Dvar_FindVar("sv_cheats")->current.enabled) + { + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"Cheats are not enabled on this server\""); + return; + } + + game::mp::g_entities[client_num].flags ^= 4; + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + utils::string::va("f \"notarget %s\"", + game::mp::g_entities[client_num].flags & 4 + ? "^2on" + : "^1off")); + }); + + add_sv("noclip", [](const int client_num, const params_sv&) + { + if (!game::Dvar_FindVar("sv_cheats")->current.enabled) + { + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"Cheats are not enabled on this server\""); + return; + } + + game::mp::g_entities[client_num].client->flags ^= 1; + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + utils::string::va("f \"noclip %s\"", + game::mp::g_entities[client_num].client->flags & 1 + ? "^2on" + : "^1off")); + }); + + add_sv("ufo", [](const int client_num, const params_sv&) + { + if (!game::Dvar_FindVar("sv_cheats")->current.enabled) + { + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"Cheats are not enabled on this server\""); + return; + } + + game::mp::g_entities[client_num].client->flags ^= 2; + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + utils::string::va("f \"ufo %s\"", + game::mp::g_entities[client_num].client->flags & 2 + ? "^2on" + : "^1off")); + }); + + add_sv("give", [](const int client_num, const params_sv& params) + { + if (!game::Dvar_FindVar("sv_cheats")->current.enabled) + { + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"Cheats are not enabled on this server\""); + return; + } + + if (params.size() < 2) + { + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"You did not specify a weapon name\""); + return; + } + + auto ps = game::SV_GetPlayerstateForClientNum(client_num); + const auto wp = game::G_GetWeaponForName(params.get(1)); + if (wp) + { + if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0)) + { + game::G_InitializeAmmo(ps, wp, 0); + game::G_SelectWeapon(client_num, wp); + } + } + }); + + add_sv("take", [](const int client_num, const params_sv& params) + { + if (!game::Dvar_FindVar("sv_cheats")->current.enabled) + { + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"Cheats are not enabled on this server\""); + return; + } + + if (params.size() < 2) + { + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"You did not specify a weapon name\""); + return; + } + + auto ps = game::SV_GetPlayerstateForClientNum(client_num); + const auto wp = game::G_GetWeaponForName(params.get(1)); + if (wp) + { + game::G_TakePlayerWeapon(ps, wp); + } + }); */ } }; } -REGISTER_COMPONENT(command::component) +REGISTER_COMPONENT(command::component) \ No newline at end of file diff --git a/src/client/component/console.cpp b/src/client/component/console.cpp index ddcac0ad..260d8f7e 100644 --- a/src/client/component/console.cpp +++ b/src/client/component/console.cpp @@ -46,11 +46,11 @@ namespace console void dispatch_message(const int type, const std::string& message) { - /*if (rcon::message_redirect(message)) - { - return; - }*/ - + game_console::print(type, message); + messages.access([&message](message_queue& msgs) + { + msgs.emplace(message); + }); } void append_text(const char* text) @@ -172,7 +172,6 @@ namespace console }); } - void log_messages() { /*while*/ @@ -203,10 +202,6 @@ namespace console { OutputDebugStringA(message.data()); game::Conbuf_AppendText(message.data()); - FILE* pFile = fopen("debug.log", "a"); - fprintf(pFile, "%s\n", message.data()); - fclose(pFile); - } void runner() diff --git a/src/client/component/demonware.cpp b/src/client/component/demonware.cpp index 1a75d780..9bf97a75 100644 --- a/src/client/component/demonware.cpp +++ b/src/client/component/demonware.cpp @@ -416,9 +416,13 @@ namespace demonware void post_unpack() override { + /* + mwr has upgraded some networking methods and the gethostbyname import from winsock library is no longer used + gethostbyname has been replaced with getaddrinfo + btw, still you can't get online.. + */ utils::hook::jump(SELECT_VALUE(0x140610320, 0x1407400B0), bd_logger_stub); // H1MP64(1.4) - //singleplayer not supported so far. if (game::environment::is_sp()) { utils::hook::set(0x1405FCA00, 0xC3); // bdAuthSteam H1(1.4) diff --git a/src/client/component/game_console.cpp b/src/client/component/game_console.cpp index f76852f2..98a1d03e 100644 --- a/src/client/component/game_console.cpp +++ b/src/client/component/game_console.cpp @@ -1,15 +1,18 @@ #include #include "loader/component_loader.hpp" +#include "game_console.hpp" +#include "command.hpp" +#include "console.hpp" +#include "scheduler.hpp" #include "game/game.hpp" #include "game/dvars.hpp" -#include "game_console.hpp" -#include "command.hpp" -#include "scheduler.hpp" - #include #include +#include + +#include "version.hpp" #define console_font game::R_RegisterFont("fonts/fira_mono_regular.ttf", 18) #define material_white game::Material_RegisterHandle("white") @@ -20,66 +23,68 @@ namespace game_console { struct console_globals { - float x; - float y; - float left_x; - float font_height; - bool may_auto_complete; - char auto_complete_choice[64]; - int info_line_count; + float x{}; + float y{}; + float left_x{}; + float font_height{}; + bool may_auto_complete{}; + char auto_complete_choice[64]{}; + int info_line_count{}; }; + using output_queue = std::deque; + struct ingame_console { - char buffer[256]; - int cursor; - int font_height; - int visible_line_count; - int visible_pixel_width; - float screen_min[2]; //left & top - float screen_max[2]; //right & bottom - console_globals globals; - bool output_visible; - int display_line_offset; - int line_count; - std::deque output; + char buffer[256]{}; + int cursor{}; + int font_height{}; + int visible_line_count{}; + int visible_pixel_width{}; + float screen_min[2]{}; //left & top + float screen_max[2]{}; //right & bottom + console_globals globals{}; + bool output_visible{}; + int display_line_offset{}; + int line_count{}; + utils::concurrency::container output{}; }; - ingame_console con; + ingame_console con{}; std::int32_t history_index = -1; - std::deque history; + std::deque history{}; - std::string fixed_input; - std::vector matches; + std::string fixed_input{}; + std::vector matches{}; float color_white[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; float color_title[4] = { 0.9f, 0.9f, 0.5f, 1.0f }; void clear() { - strncpy_s(con.buffer, "", 256); + strncpy_s(con.buffer, "", sizeof(con.buffer)); con.cursor = 0; fixed_input = ""; matches.clear(); } - void print(const std::string& data) + void print_internal(const std::string& data) { - if (con.visible_line_count > 0 && con.display_line_offset == (con.output.size() - con.visible_line_count)) - { - con.display_line_offset++; - } - - con.output.push_back(data); - - printf("%s\n", data.data()); - - if (con.output.size() > 1024) - { - con.output.pop_front(); - } + con.output.access([&](output_queue& output) + { + if (con.visible_line_count > 0 + && con.display_line_offset == (output.size() - con.visible_line_count)) + { + con.display_line_offset++; + } + output.push_back(data); + if (output.size() > 512) + { + output.pop_front(); + } + }); } void toggle_console() @@ -146,6 +151,8 @@ namespace game_console color); } + + void draw_input_text_and_over(const char* str, float* color) { game::R_AddCmdDrawText(str, 0x7FFFFFFF, console_font, con.globals.x, @@ -171,6 +178,7 @@ namespace game_console game::R_AddCmdDrawText(text, 0x7FFFFFFF, console_font, con.globals.x + offset, _y, 1.0f, 1.0f, 0.0f, color, 0); } + bool match_compare(const std::string& input, const std::string& text, const bool exact) { if (exact && text == input) return true; @@ -230,7 +238,7 @@ namespace game_console con.globals.left_x = con.screen_min[0] + 6.0f; draw_input_box(1, dvars::con_inputBoxColor->current.vector); - draw_input_text_and_over("H1-Mod >", color_title); + draw_input_text_and_over("H1-Mod: " VERSION ">", color_title); con.globals.left_x = con.globals.x; con.globals.auto_complete_choice[0] = 0; @@ -240,8 +248,8 @@ namespace game_console con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0, color_white, 0, con.cursor, '|'); - //game::R_AddCmdDrawText(con.buffer, 0x7FFF, console_font, con.globals.x, - // con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0.0f, color_white, 0); + game::R_AddCmdDrawText(con.buffer, 0x7FFF, console_font, con.globals.x, + con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0.0f, color_white, 0); // check if using a prefixed '/' or not @@ -328,19 +336,19 @@ namespace game_console } } - void draw_output_scrollbar(const float x, float y, const float width, const float height) + void draw_output_scrollbar(const float x, float y, const float width, const float height, output_queue& output) { const auto _x = (x + width) - 10.0f; draw_box(_x, y, 10.0f, height, dvars::con_outputBarColor->current.vector); auto _height = height; - if (con.output.size() > con.visible_line_count) + if (output.size() > con.visible_line_count) { - const auto percentage = static_cast(con.visible_line_count) / con.output.size(); + const auto percentage = static_cast(con.visible_line_count) / output.size(); _height *= percentage; const auto remainingSpace = height - _height; - const auto percentageAbove = static_cast(con.display_line_offset) / (con.output.size() - con. + const auto percentageAbove = static_cast(con.display_line_offset) / (output.size() - con. visible_line_count); y = y + (remainingSpace * percentageAbove); @@ -349,42 +357,45 @@ namespace game_console draw_box(_x, y, 10.0f, _height, dvars::con_outputSliderColor->current.vector); } - void draw_output_text(const float x, float y) + void draw_output_text(const float x, float y, output_queue& output) { - const auto offset = con.output.size() >= con.visible_line_count + const auto offset = output.size() >= con.visible_line_count ? 0.0f - : (con.font_height * (con.visible_line_count - con.output.size())); + : (con.font_height * (con.visible_line_count - output.size())); for (auto i = 0; i < con.visible_line_count; i++) { y = console_font->pixelHeight + y; const auto index = i + con.display_line_offset; - if (index >= con.output.size()) + if (index >= output.size()) { break; } - game::R_AddCmdDrawText(con.output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f, + game::R_AddCmdDrawText(output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f, 0.0f, color_white, 0); } } void draw_output_window() { - draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0], - (con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector); + con.output.access([](output_queue& output) + { + draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0], + (con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector); - const auto x = con.screen_min[0] + 6.0f; - const auto y = (con.screen_min[1] + 32.0f) + 6.0f; - const auto width = (con.screen_max[0] - con.screen_min[0]) - 12.0f; - const auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f; + const auto x = con.screen_min[0] + 6.0f; + const auto y = (con.screen_min[1] + 32.0f) + 6.0f; + const auto width = (con.screen_max[0] - con.screen_min[0]) - 12.0f; + const auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f; - game::R_AddCmdDrawText("H1-Mod 1.4", 0x7FFFFFFF, console_font, x, - ((height - 16.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_title, 0); + game::R_AddCmdDrawText("H1-Mod 1.4", 0x7FFFFFFF, console_font, x, + ((height - 16.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_title, 0); - draw_output_scrollbar(x, y, width, height); - draw_output_text(x, y); + draw_output_scrollbar(x, y, width, height, output); + draw_output_text(x, y, output); + }); } void draw_console() @@ -408,7 +419,7 @@ namespace game_console } } - void print(const int type, const char* fmt, ...) + void print_internal(const char* fmt, ...) { char va_buffer[0x200] = { 0 }; @@ -420,22 +431,38 @@ namespace game_console const auto formatted = std::string(va_buffer); const auto lines = utils::string::split(formatted, '\n'); - for (auto& line : lines) + for (const auto& line : lines) { - print(type == con_type_info ? line : "^"s.append(std::to_string(type)).append(line)); + print_internal(line); } } - bool console_char_event(const int localClientNum, const int key) + void print(const int type, const std::string& data) { - if (key == '`' || key == '~' || key == '|' || key == '\\') + try { - return false; + if (game::environment::is_dedi()) + { + return; + } + } + catch (std::exception&) + { + return; } - if (key > 127) + const auto lines = utils::string::split(data, '\n'); + for (const auto& line : lines) { - return true; + print_internal(type == console::con_type_info ? line : "^"s.append(std::to_string(type)).append(line)); + } + } + + bool console_char_event(const int local_client_num, const int key) + { + if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE) + { + return false; } if (*game::keyCatchers & 1) @@ -444,13 +471,13 @@ namespace game_console { if (con.globals.may_auto_complete) { - const auto firstChar = con.buffer[0]; + const auto first_char = con.buffer[0]; clear(); - if (firstChar == '\\' || firstChar == '/') + if (first_char == '\\' || first_char == '/') { - con.buffer[0] = firstChar; + con.buffer[0] = first_char; con.buffer[1] = '\0'; } @@ -473,9 +500,9 @@ namespace game_console return false; } - for (auto i = 0; i < clipboard.length(); i++) + for (size_t i = 0; i < clipboard.length(); i++) { - console_char_event(localClientNum, clipboard[i]); + console_char_event(local_client_num, clipboard[i]); } return false; @@ -485,7 +512,11 @@ namespace game_console { clear(); con.line_count = 0; - con.output.clear(); + con.display_line_offset = 0; + con.output.access([](output_queue& output) + { + output.clear(); + }); history_index = -1; history.clear(); @@ -527,13 +558,18 @@ namespace game_console return true; } - void execute(const char* cmd) + bool console_key_event(const int local_client_num, const int key, const int down) { - game::Cbuf_AddText(0, utils::string::va("%s \n", cmd)); - } + if (key == game::keyNum_t::K_F10) + { + if (game::mp::svs_clients[local_client_num].header.state >= 1) + { + return false; + } + + game::Cmd_ExecuteSingleCommand(local_client_num, 0, "lui_open menu_systemlink_join\n"); + } - bool console_key_event(const int localClientNum, const int key, const int down) - { if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE) { if (!down) @@ -541,7 +577,7 @@ namespace game_console return false; } - if (game::playerKeys[localClientNum].keys[game::keyNum_t::K_SHIFT].down) + if (game::playerKeys[local_client_num].keys[game::keyNum_t::K_SHIFT].down) { if (!(*game::keyCatchers & 1)) toggle_console(); @@ -613,24 +649,29 @@ namespace game_console //scroll through output if (key == game::keyNum_t::K_MWHEELUP || key == game::keyNum_t::K_PGUP) { - if (con.output.size() > con.visible_line_count && con.display_line_offset > 0) - { - con.display_line_offset--; - } + con.output.access([](output_queue& output) + { + if (output.size() > con.visible_line_count && con.display_line_offset > 0) + { + con.display_line_offset--; + } + }); } else if (key == game::keyNum_t::K_MWHEELDOWN || key == game::keyNum_t::K_PGDN) { - if (con.output.size() > con.visible_line_count && con.display_line_offset < (con.output.size() - - con. - visible_line_count)) - { - con.display_line_offset++; - } + con.output.access([](output_queue& output) + { + if (output.size() > con.visible_line_count + && con.display_line_offset < (output.size() - con.visible_line_count)) + { + con.display_line_offset++; + } + }); } if (key == game::keyNum_t::K_ENTER) { - execute(fixed_input.data()); + game::Cbuf_AddText(0, utils::string::va("%s \n", fixed_input.data())); if (history_index != -1) { @@ -644,7 +685,7 @@ namespace game_console history.push_front(con.buffer); - print(""s.append(con.buffer)); + console::info("]%s\n", con.buffer); if (history.size() > 10) { @@ -661,13 +702,31 @@ namespace game_console return true; } + class component final : public component_interface { public: + void post_load() override + { + if (game::environment::is_dedi()) + { + return; + } + + //scheduler::loop(draw_console, scheduler::pipeline::renderer); + } + void post_unpack() override { scheduler::loop(draw_console, scheduler::pipeline::renderer); + + if (game::environment::is_dedi()) + { + return; + } + + // initialize our structs con.cursor = 0; con.visible_line_count = 0; con.output_visible = false; @@ -683,70 +742,49 @@ namespace game_console con.globals.info_line_count = 0; strncpy_s(con.globals.auto_complete_choice, "", 64); - // //add clear command - //command::add("clear", [&]() - //{ - //clear(); - //con.line_count = 0; - //con.output.clear(); - //history_index = -1; - //history.clear(); - //}); + // add clear command + command::add("clear", [&]() + { + clear(); + con.line_count = 0; + con.display_line_offset = 0; + con.output.access([](output_queue& output) + { + output.clear(); + }); + history_index = -1; + history.clear(); + }); // add our dvars - dvars::con_inputBoxColor = dvars::register_vec4( - "con_inputBoxColor", - 0.2f, 0.2f, 0.2f, 0.9f, + dvars::con_inputBoxColor = dvars::register_vec4("con_inputBoxColor", 0.2f, 0.2f, 0.2f, 0.9f, 0.0f, 1.0f, + game::DVAR_FLAG_SAVED, + "color of console input box"); + dvars::con_inputHintBoxColor = dvars::register_vec4("con_inputHintBoxColor", 0.3f, 0.3f, 0.3f, 1.0f, 0.0f, 1.0f, - game::DVAR_FLAG_SAVED); - - dvars::con_inputHintBoxColor = dvars::register_vec4( - "con_inputHintBoxColor", - 0.3f, 0.3f, 0.3f, 1.0f, + game::DVAR_FLAG_SAVED, "color of console input hint box"); + dvars::con_outputBarColor = dvars::register_vec4("con_outputBarColor", 0.5f, 0.5f, 0.5f, 0.6f, 0.0f, + 1.0f, game::DVAR_FLAG_SAVED, + "color of console output bar"); + dvars::con_outputSliderColor = dvars::register_vec4("con_outputSliderColor", 1.0f, 0.8f, 0.0f, 1.0f, 0.0f, 1.0f, - game::DVAR_FLAG_SAVED); - - dvars::con_outputBarColor = dvars::register_vec4( - "con_outputBarColor", - 0.5f, 0.5f, 0.5f, 0.6f, - 0.0f, 1.0f, - game::DVAR_FLAG_SAVED); - - dvars::con_outputSliderColor = dvars::register_vec4( - "con_outputSliderColor", - 0.9f, 0.9f, 0.5f, 1.00f, - 0.0f, 1.0f, - game::DVAR_FLAG_SAVED); - - dvars::con_outputWindowColor = dvars::register_vec4( - "con_outputWindowColor", - 0.25f, 0.25f, 0.25f, 0.85f, - 0.0f, 1.0f, - game::DVAR_FLAG_SAVED); - - dvars::con_inputDvarMatchColor = dvars::register_vec4( - "con_inputDvarMatchColor", - 1.0f, 1.0f, 0.8f, 1.0f, - 0.0f, 1.0f, - game::DVAR_FLAG_SAVED); - - dvars::con_inputDvarValueColor = dvars::register_vec4( - "con_inputDvarValueColor", - 1.0f, 1.0f, 0.8f, 1.0f, - 0.0f, 1.0f, - game::DVAR_FLAG_SAVED); - + game::DVAR_FLAG_SAVED, "color of console output slider"); + dvars::con_outputWindowColor = dvars::register_vec4("con_outputWindowColor", 0.25f, 0.25f, 0.25f, 0.85f, + 0.0f, + 1.0f, game::DVAR_FLAG_SAVED, "color of console output window"); + dvars::con_inputDvarMatchColor = dvars::register_vec4("con_inputDvarMatchColor", 1.0f, 1.0f, 0.8f, 1.0f, + 0.0f, + 1.0f, game::DVAR_FLAG_SAVED, "color of console matched dvar"); + dvars::con_inputDvarValueColor = dvars::register_vec4("con_inputDvarValueColor", 1.0f, 1.0f, 0.8f, 1.0f, + 0.0f, + 1.0f, game::DVAR_FLAG_SAVED, "color of console matched dvar value"); dvars::con_inputDvarInactiveValueColor = dvars::register_vec4( - "con_inputDvarInactiveValueColor", - 0.8f, 0.8f, 0.8f, 1.0f, - 0.0f, 1.0f, - game::DVAR_FLAG_SAVED); - - dvars::con_inputCmdMatchColor = dvars::register_vec4( - "con_inputCmdMatchColor", - 0.80f, 0.80f, 1.0f, 1.0f, - 0.0f, 1.0f, - game::DVAR_FLAG_SAVED); + "con_inputDvarInactiveValueColor", 0.8f, 0.8f, + 0.8f, 1.0f, 0.0f, 1.0f, game::DVAR_FLAG_SAVED, + "color of console inactive dvar value"); + dvars::con_inputCmdMatchColor = dvars::register_vec4("con_inputCmdMatchColor", 0.80f, 0.80f, 1.0f, 1.0f, + 0.0f, + 1.0f, game::DVAR_FLAG_SAVED, "color of console matched command"); } }; } diff --git a/src/client/component/game_console.hpp b/src/client/component/game_console.hpp index 7c4fc21a..144f2064 100644 --- a/src/client/component/game_console.hpp +++ b/src/client/component/game_console.hpp @@ -9,10 +9,12 @@ namespace game_console con_type_info = 7 }; - void print(int type, const char* fmt, ...); + //void print(int type, const char* fmt, ...); bool console_char_event(int local_client_num, int key); bool console_key_event(int local_client_num, int key, int down); + bool match_compare(const std::string& input, const std::string& text, const bool exact); + void find_matches(std::string input, std::vector& suggestions, const bool exact); void execute(const char* cmd); } \ No newline at end of file diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp index 91db06ed..790bb125 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace network { @@ -268,7 +269,7 @@ namespace network } else { - game_console::print(game_console::con_type_info, "%s\n", message.data()); + console::info("%s\n", message.data()); //test } }); } diff --git a/src/client/game/dvars.cpp b/src/client/game/dvars.cpp index dc71fe83..d30761c5 100644 --- a/src/client/game/dvars.cpp +++ b/src/client/game/dvars.cpp @@ -46,6 +46,26 @@ namespace dvars } } + namespace + { + template + T* find_dvar(std::unordered_map& map, const std::string& name) + { + auto i = map.find(name); + if (i != map.end()) + { + return &i->second; + } + + return nullptr; + } + + bool find_dvar(std::unordered_set& set, const std::string& name) + { + return set.find(name) != set.end(); + } + } + std::string dvar_get_domain(const game::dvar_type type, const game::dvar_limits& domain) { std::string str; diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 4b3968a8..d6544076 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -932,7 +932,7 @@ namespace game struct dvar_t { - int hash; + const char* hash; unsigned int flags; dvar_type type; bool modified; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index e23aab9e..4dceb49c 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -46,7 +46,7 @@ namespace game WEAK symbol CL_IsCgameInitialized{ 0x14017EE30, 0x140245650 }; // H1(1.4) WEAK symbol Live_SyncOnlineDataFlags{ 0, 0x14059A700 }; // H1(1.4) - WEAK symbol Sys_Milliseconds{ 0x1403E2B10, 0x140513710 }; // H1(1.4) + WEAK symbol Sys_Milliseconds{ 0x1403E2B10, 0x140513710 }; // H1(1.4) WEAK symbol Sys_IsDatabaseReady2{ 0, 0x14042B090 }; // H1(1.4) WEAK symbol SV_DirectConnect{ 0, 0x140480860 }; // H1(1.4) @@ -151,6 +151,11 @@ namespace game WEAK symbol Sys_ShowConsole{ 0x1403E3B90, 0x140514910 }; // H1(1.4) + WEAK symbol UI_GetMapDisplayName{ 0, 0x140408CC0 }; // H1(1.4) + + WEAK symbol UI_GetGameTypeDisplayName{ 0, 0x1404086A0 }; // H1(1.4) + + WEAK symbol UI_SafeTranslateString{ 0x140350430, 0x1405A2930 }; // H1(1.4) WEAK symbol longjmp{ 0x140648FD4, 0x14089EED0 }; // H1(1.4)