diff --git a/src/client/component/fps.cpp b/src/client/component/fps.cpp new file mode 100644 index 00000000..6f3b72a2 --- /dev/null +++ b/src/client/component/fps.cpp @@ -0,0 +1,177 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include +#include +#include + +namespace fps +{ + namespace + { + float fps_color_good[4] = { 0.6f, 1.0f, 0.0f, 1.0f }; + float fps_color_ok[4] = { 1.0f, 0.7f, 0.3f, 1.0f }; + float fps_color_bad[4] = { 1.0f, 0.3f, 0.3f, 1.0f }; + + //float origin_color[4] = { 1.0f, 0.67f, 0.13f, 1.0f }; + float ping_color[4] = { 1.0f, 1.0f, 1.0f, 0.65f }; + + struct cg_perf_data + { + std::chrono::time_point perf_start; + std::int32_t current_ms{}; + std::int32_t previous_ms{}; + std::int32_t frame_ms{}; + std::int32_t history[32]{}; + std::int32_t count{}; + std::int32_t index{}; + std::int32_t instant{}; + std::int32_t total{}; + float average{}; + float variance{}; + std::int32_t min{}; + std::int32_t max{}; + }; + + cg_perf_data cg_perf = cg_perf_data(); + + void perf_calc_fps(cg_perf_data* data, const std::int32_t value) + { + data->history[data->index % 32] = value; + data->instant = value; + data->min = 0x7FFFFFFF; + data->max = 0; + data->average = 0.0f; + data->variance = 0.0f; + data->total = 0; + + for (auto i = 0; i < data->count; ++i) + { + const std::int32_t idx = (data->index - i) % 32; + + if (idx < 0) + { + break; + } + + data->total += data->history[idx]; + + if (data->min > data->history[idx]) + { + data->min = data->history[idx]; + } + + if (data->max < data->history[idx]) + { + data->max = data->history[idx]; + } + } + + data->average = static_cast(data->total) / static_cast(data->count); + ++data->index; + } + + void perf_update() + { + cg_perf.count = 32; + + cg_perf.current_ms = static_cast(std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - cg_perf.perf_start).count()); + cg_perf.frame_ms = cg_perf.current_ms - cg_perf.previous_ms; + cg_perf.previous_ms = cg_perf.current_ms; + + perf_calc_fps(&cg_perf, cg_perf.frame_ms); + + utils::hook::invoke(SELECT_VALUE(0x1405487A0, 0x1406575A0)); // H1(1.4) + } + + void cg_draw_fps() + { + const auto* draw_fps = game::Dvar_FindVar("cg_drawFPS"); + if (draw_fps && draw_fps->current.integer > 0 /*&& game::CL_IsCgameInitialized()*/) + { + const auto fps = static_cast(static_cast(1000.0f / static_cast(cg_perf. + average)) + + 9.313225746154785e-10); + + auto* font = game::R_RegisterFont("fonts/fira_mono_regular.ttf", 19); + if (!font) return; + + const auto* const fps_string = utils::string::va("%i", fps); + + const auto scale = 1.0f; + + const auto x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 10.0f) - game::R_TextWidth( + fps_string, 0x7FFFFFFF, font) * scale; + + const auto y = font->pixelHeight * 1.2f; + + const auto fps_color = fps >= 60 ? fps_color_good : (fps >= 30 ? fps_color_ok : fps_color_bad); + game::R_AddCmdDrawText(fps_string, 0x7FFFFFFF, font, x, y, scale, scale, 0.0f, fps_color, 6); + } + } + + void cg_draw_ping() + { + const auto* draw_ping = game::Dvar_FindVar("cg_drawPing"); + if (draw_ping && draw_ping->current.integer > 0 && game::CL_IsCgameInitialized()) + { + const auto ping = *reinterpret_cast(0x142D106F0); // H1MP(1.4) + + auto* font = game::R_RegisterFont("fonts/consolefont", 20); + if (!font) return; + + auto* const ping_string = utils::string::va("Ping: %i", ping); + + const auto scale = 1.0f; + + const auto x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 375.0f) - game::R_TextWidth( + ping_string, 0x7FFFFFFF, font) * scale; + + const auto y = font->pixelHeight * 1.2f; + + game::R_AddCmdDrawText(ping_string, 0x7FFFFFFF, font, x, y, scale, scale, 0.0f, ping_color, 6); + } + } + + void cg_draw_fps_register_stub(const char* name, const char** _enum, const int value, unsigned int /*flags*/, + const char* desc) + { + game::Dvar_RegisterEnum(name, _enum, value, game::DVAR_FLAG_SAVED); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + if (game::environment::is_dedi()) + { + return; + } + + // fps setup + cg_perf.perf_start = std::chrono::high_resolution_clock::now(); + utils::hook::call(SELECT_VALUE(0x14018D261, 0x140213B27), &perf_update); //h1sp + + // change cg_drawfps flags to saved + utils::hook::call(SELECT_VALUE(0x140139F48, 0x1401A4B8E), &cg_draw_fps_register_stub); //h1sp + + // fix ping value + utils::hook::nop(0x14025AC41, 2); // H1MP(1.4) + + scheduler::loop(cg_draw_fps, scheduler::pipeline::renderer); + if (game::environment::is_mp()) + { + dvars::register_int("cg_drawPing", 0, 0, 1, game::DVAR_FLAG_SAVED, true); + scheduler::loop(cg_draw_ping, scheduler::pipeline::renderer); + } + } + }; +} + +REGISTER_COMPONENT(fps::component) \ No newline at end of file diff --git a/src/client/component/game_console.cpp b/src/client/component/game_console.cpp index 98a1d03e..e2563a5f 100644 --- a/src/client/component/game_console.cpp +++ b/src/client/component/game_console.cpp @@ -299,10 +299,10 @@ namespace game_console { const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f; - draw_hint_text(0, game::Dvar_ValueToString(dvar, dvar->current, 0), + draw_hint_text(0, game::Dvar_ValueToString(dvar, dvar->current), dvars::con_inputDvarValueColor->current.vector, offset); draw_hint_text(1, " default", dvars::con_inputDvarInactiveValueColor->current.vector); - draw_hint_text(1, game::Dvar_ValueToString(dvar, dvar->reset, 0), + draw_hint_text(1, game::Dvar_ValueToString(dvar, dvar->reset), dvars::con_inputDvarInactiveValueColor->current.vector, offset); } @@ -326,7 +326,7 @@ namespace game_console if (dvar) { - draw_hint_text(static_cast(i), game::Dvar_ValueToString(dvar, dvar->current, 0), + draw_hint_text(static_cast(i), game::Dvar_ValueToString(dvar, dvar->current), dvars::con_inputDvarValueColor->current.vector, offset); } } diff --git a/src/client/game/dvars.cpp b/src/client/game/dvars.cpp index d30761c5..c11e00d7 100644 --- a/src/client/game/dvars.cpp +++ b/src/client/game/dvars.cpp @@ -46,26 +46,6 @@ 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; @@ -82,16 +62,19 @@ namespace dvars { return "Domain is any number"s; } - - return utils::string::va("Domain is any number %g or smaller", domain.value.max); + else + { + return utils::string::va("Domain is any number %g or smaller", domain.value.max); + } } - - if (domain.value.max == FLT_MAX) + else if (domain.value.max == FLT_MAX) { return utils::string::va("Domain is any number %g or bigger", domain.value.min); } - - return utils::string::va("Domain is any number from %g to %g", domain.value.min, domain.value.max); + else + { + return utils::string::va("Domain is any number from %g to %g", domain.value.min, domain.value.max); + } case game::dvar_type::vec2: return dvar_get_vector_domain(2, domain); @@ -110,16 +93,19 @@ namespace dvars { return "Domain is any integer"s; } - - return utils::string::va("Domain is any integer %i or smaller", domain.integer.max); + else + { + return utils::string::va("Domain is any integer %i or smaller", domain.integer.max); + } } - - if (domain.integer.max == INT_MAX) + else if (domain.integer.max == INT_MAX) { return utils::string::va("Domain is any integer %i or bigger", domain.integer.min); } - - return utils::string::va("Domain is any integer from %i to %i", domain.integer.min, domain.integer.max); + else + { + return utils::string::va("Domain is any integer from %i to %i", domain.integer.min, domain.integer.max); + } case game::dvar_type::color: return "Domain is any 4-component color, in RGBA format"s; @@ -142,6 +128,28 @@ 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::vector dvar_list = { "accessToSubscriberContent", @@ -167,6 +175,7 @@ namespace dvars "aim_assist_min_target_distance", "aim_assist_script_disable", "cg_draw2D", + "cg_drawPing", "cg_drawBigFPS", "cg_drawBreathHint", "cg_drawBuildName", @@ -582,5 +591,31 @@ namespace dvars { set_string_overrides[name] = value; } + + game::dvar_t* register_float(const std::string& name, float value, float min, + float max, game::DvarFlags flags, bool add_to_list) + { + const auto hash = game::generateHashValue(name.data()); + + if (add_to_list && can_add_dvar_to_list(name)) + { + dvar_list.push_back(name); + } + + return game::Dvar_RegisterFloat(hash, "", value, min, max, flags); + } + + game::dvar_t* register_string(const std::string& name, const char* value, + game::DvarFlags flags, bool add_to_list) + { + const auto hash = game::generateHashValue(name.data()); + + if (add_to_list && can_add_dvar_to_list(name)) + { + dvar_list.push_back(name); + } + + return game::Dvar_RegisterString(hash, "", value, flags); + } } } diff --git a/src/client/game/dvars.hpp b/src/client/game/dvars.hpp index 16b80b67..251c3546 100644 --- a/src/client/game/dvars.hpp +++ b/src/client/game/dvars.hpp @@ -35,5 +35,7 @@ namespace dvars namespace override { game::dvar_t* register_int(const std::string& name, int value, int min, int max, const unsigned int flags, bool add_to_list = true); void Dvar_SetString(const std::string& name, const std::string& string); + game::dvar_t* register_float(const std::string& name, float value, float min, float max, game::DvarFlags flags, bool add_to_list = true); + game::dvar_t* register_string(const std::string& name, const char* value, game::DvarFlags flags, bool add_to_list = true); } } diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index d6544076..4b3968a8 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -932,7 +932,7 @@ namespace game struct dvar_t { - const char* hash; + int hash; unsigned int flags; dvar_type type; bool modified; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 4dceb49c..8298668d 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -32,7 +32,9 @@ namespace game WEAK symbol Cmd_EndTokenizeString{ 0x140343630, 0x140403C20 }; // H1(1.4) WEAK symbol Dvar_FindVar{ 0x1403C5D50, 0x1404FBB00 }; // H1(1.4) - WEAK symbol Dvar_ValueToString{ 0x1403C8560,0x1404FE660 }; // H1(1.4); different typedef from previous titles + WEAK symbol Dvar_GetCombinedString{ 0x140354DF0, 0x14041D830 }; // H1(1.4) + //WEAK symbol Dvar_ValueToString{ 0x1403C8560,0x1404FE660 }; // H1(1.4); different typedef from previous titles + WEAK symbol Dvar_ValueToString{ 0x1403C8560, 0x1404FE660 }; // H1(1.4) //WEAK symbol Dvar_SetCommand{ 0, 0x1404FD0A0 }; // H1(1.4) WEAK symbol Dvar_RegisterBool{ 0x1403C47E0,0x1404FA540 }; // H1 @@ -40,6 +42,17 @@ namespace game WEAK symbol Dvar_RegisterFloat{ 0x1403C4BB0,0x1404FA910 }; // H1(1.4) WEAK symbol Dvar_RegisterString{ 0x1403C4DA0,0x1404FAB00 }; // H1(1.4) WEAK symbol Dvar_RegisterVec4{ 0x1403C5220, 0x1404FAF40 }; // H1(1.4) + WEAK symbol Dvar_RegisterEnum{ 0x1403C4AC0, 0x1404C0EC0 }; // H1(1.4) + + + + WEAK symbol FS_ReadFile{ 0x1403B9020, 0x1404EE720 }; // H1(1.4) + + WEAK symbol FS_FreeFile{ 0x1403B9010, 0x1404EE710 }; // H1(1.4) + + WEAK symbol FS_Startup{ 0x1403B85D0, 0x1404EDD30 }; // H1(1.4) + + WEAK symbol FS_AddLocalizedGameDirectory{ 0x1403B6030, 0x1404EBE20 }; // H1(1.4) WEAK symbol generateHashValue{ 0x1400FE8A0, 0x1401B1010 }; // H1(1.4) @@ -115,6 +128,7 @@ namespace game WEAK symbol DB_FindXAssetHeader{ 0, 0x140412F60 }; WEAK symbol DB_GetRawFileLen{ 0,0x140413D80 }; WEAK symbol DB_GetRawBuffer{ 0,0x140413C40 }; + WEAK symbol DB_GetXAssetTypeSize{ 0x14019A3B0, 0x14028BE70 }; // H1(1.4) WEAK symbol FindVariable{ 0,0x1405C1D50 }; WEAK symbol FindEntityId{ 0, 0x1405C1C50 }; @@ -155,6 +169,7 @@ namespace game WEAK symbol UI_GetGameTypeDisplayName{ 0, 0x1404086A0 }; // H1(1.4) + WEAK symbol Sys_Error{ 0x1403E0C40, 0x140511520 }; // H1(1.4) WEAK symbol UI_SafeTranslateString{ 0x140350430, 0x1405A2930 }; // H1(1.4) @@ -183,6 +198,7 @@ namespace game WEAK symbol scr_VarGlob{ 0, 0x14B617C00 }; WEAK symbol scr_VmPub{ 0,0x14BA9EE40 }; WEAK symbol scr_function_stack{ 0,0x14BAA93C0 }; + WEAK symbol DB_XAssetPool{ 0x140DE8C80, 0x140FEB5D0 }; // H1(1.4) namespace mp {