diff --git a/src/services/tunables/tunables_service.cpp b/src/services/tunables/tunables_service.cpp index 876396fb..a3bcdb2a 100644 --- a/src/services/tunables/tunables_service.cpp +++ b/src/services/tunables/tunables_service.cpp @@ -52,7 +52,7 @@ namespace big if (SCRIPT::HAS_SCRIPT_WITH_NAME_HASH_LOADED("tuneables_processing"_J) && SCRIPT::HAS_SCRIPT_WITH_NAME_HASH_LOADED("tunables_registration"_J)) { - m_num_tunables = gta_util::find_script_program("tunables_registration"_J)->m_global_count - 0x40000; + m_num_tunables = gta_util::find_script_program("tunables_registration"_J)->m_global_count - TUNABLE_BASE_ADDRESS; uint64_t args[] = {6, 27}; // TODO: check args @@ -65,7 +65,7 @@ namespace big } m_tunables_backup = std::make_unique(m_num_tunables); - memcpy(m_tunables_backup.get(), script_global(0x40000).as(), m_num_tunables * 8); + memcpy(m_tunables_backup.get(), script_global(TUNABLE_BASE_ADDRESS).as(), m_num_tunables * 8); SCRIPT::SET_SCRIPT_WITH_NAME_HASH_AS_NO_LONGER_NEEDED("tuneables_processing"_J); SCRIPT::SET_SCRIPT_WITH_NAME_HASH_AS_NO_LONGER_NEEDED("tunables_registration"_J); @@ -78,13 +78,13 @@ namespace big { for (int i = 0; i < m_num_tunables; i++) { - auto value = *script_global(0x40000).at(i).as(); + auto value = *script_global(TUNABLE_BASE_ADDRESS).at(i).as(); if (auto it = m_junk_values.find(value); it != m_junk_values.end()) { - m_tunables.emplace(it->second, 0x40000 + i); + m_tunables.emplace(it->second, TUNABLE_BASE_ADDRESS + i); } } - memcpy(script_global(0x40000).as(), m_tunables_backup.get(), m_num_tunables * 8); + memcpy(script_global(TUNABLE_BASE_ADDRESS).as(), m_tunables_backup.get(), m_num_tunables * 8); if (m_tunables.size() == 0) { diff --git a/src/services/tunables/tunables_service.hpp b/src/services/tunables/tunables_service.hpp index aff84ecf..a48ba103 100644 --- a/src/services/tunables/tunables_service.hpp +++ b/src/services/tunables/tunables_service.hpp @@ -6,6 +6,8 @@ namespace big { + constexpr int TUNABLE_BASE_ADDRESS = 0x40000; // This never changes + #pragma pack(push, 1) struct tunable_save_struct { @@ -69,6 +71,21 @@ namespace big } } + inline int get_tunable_offset(rage::joaat_t hash) + { + if (auto it = m_tunables.find(hash); it != m_tunables.end()) + { + auto offset = it->second; + + if (offset > TUNABLE_BASE_ADDRESS) + return (offset - TUNABLE_BASE_ADDRESS) - 1; + + return (TUNABLE_BASE_ADDRESS - offset) - 1; + } + + return 0; + } + int m_current_junk_val = 0x1000000; std::unordered_map m_junk_values{}; diff --git a/src/util/animations.hpp b/src/util/animations.hpp index 8a92df8c..c6ffcadb 100644 --- a/src/util/animations.hpp +++ b/src/util/animations.hpp @@ -92,7 +92,7 @@ namespace big::animations if (!std::filesystem::exists(g_file_manager.get_project_file("animDictsCompact.json").get_path())) { LOG(WARNING) << "Animations file is not in directory. https://raw.githubusercontent.com/DurtyFree/gta-v-data-dumps/master/animDictsCompact.json"; - g_notification_service.push_warning("Animations", "Please download the appropriate animations json and put it in the mod directory."); + g_notification_service.push_warning("Animations", "JSON_ANIMATIONS_WARNING"_T.data()); return; } diff --git a/src/views/debug/view_debug.cpp b/src/views/debug/view_debug.cpp index 2808bf91..15cee591 100644 --- a/src/views/debug/view_debug.cpp +++ b/src/views/debug/view_debug.cpp @@ -14,17 +14,12 @@ namespace big ImGui::BeginTabBar("debug_tabbar"); misc(); logs(); + tunables(); globals(); locals(); script_events(); scripts(); threads(); - if (ImGui::BeginTabItem("GUI_TAB_ANIMATIONS"_T.data())) - { - animations(); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); } ImGui::End(); } diff --git a/src/views/debug/view_debug.hpp b/src/views/debug/view_debug.hpp index 2f2319f0..0c2bcf13 100644 --- a/src/views/debug/view_debug.hpp +++ b/src/views/debug/view_debug.hpp @@ -2,14 +2,14 @@ namespace big::debug { + extern void misc(); + extern void logs(); + extern void tunables(); extern void globals(); extern void locals(); - extern void logs(); - extern void misc(); extern void script_events(); extern void scripts(); extern void threads(); - extern void animations(std::string* dict = nullptr, std::string* anim = nullptr); // Can be used to retrieve animations extern void main(); } \ No newline at end of file diff --git a/src/views/debug/view_debug_animations.cpp b/src/views/debug/view_debug_animations.cpp deleted file mode 100644 index 6f800b72..00000000 --- a/src/views/debug/view_debug_animations.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "gui/components/components.hpp" -#include "util/animations.hpp" -#include "util/ped.hpp" -#include "view_debug.hpp" - -namespace big -{ - void debug::animations(std::string* dict, std::string* anim) - { - static std::string current_dict, current_anim; - static std::vector selected_dict_anim_list{}; - - if(dict && anim) - { - *dict = current_dict; - *anim = current_anim; - } - - static auto reload_anim_list = []() -> void { - selected_dict_anim_list.clear(); - auto range = animations::all_anims.equal_range(current_dict); - for (auto it = range.first; it != range.second; ++it) - { - selected_dict_anim_list.push_back(it->second); - } - }; - - if (animations::has_anim_list_been_populated()) - { - ImGui::Text("VIEW_DEBUG_ANIMATIONS_ANIMATIONS_IN_MEMORY"_T.data(), animations::anim_dict_count(), animations::total_anim_count()); - } - - components::button("VIEW_DEBUG_ANIMATIONS_FETCH_ALL_ANIMS"_T, [] { - animations::fetch_all_anims(); - }); - - ImGui::SetNextItemWidth(400); - components::input_text_with_hint("##dictionaryfilter", "DICT"_T, current_dict); - - if (animations::has_anim_list_been_populated() && ImGui::BeginListBox("##dictionaries", ImVec2(400, 200))) - { - for (auto& entry : animations::all_dicts) - { - std::string entry_lowercase = entry; - std::string search_lowercase = current_dict; - std::transform(entry.begin(), entry.end(), entry.begin(), ::tolower); - std::transform(current_dict.begin(), current_dict.end(), current_dict.begin(), ::tolower); - if (entry.find(search_lowercase) != std::string::npos && ImGui::Selectable(entry.data())) - { - current_dict = entry; - reload_anim_list(); - } - } - - ImGui::EndListBox(); - } - - if (selected_dict_anim_list.size() > 0 && ImGui::BeginListBox("##animations", ImVec2(400, 200))) - { - for (auto& entry : selected_dict_anim_list) - { - if (ImGui::Selectable(entry.data(), entry == current_anim)) - { - current_anim = entry; - } - } - - ImGui::EndListBox(); - } - - components::button("VIEW_DEBUG_ANIMATIONS_PLAY"_T, [] { - TASK::CLEAR_PED_TASKS_IMMEDIATELY(self::ped); - ped::ped_play_animation(self::ped, current_dict, current_anim, 4.f, -4.f, -1, 0, 0, false); - }); - ImGui::SameLine(); - components::button("VIEW_DEBUG_ANIMATIONS_STOP"_T, [] { - TASK::CLEAR_PED_TASKS(self::ped); - }); - } -} \ No newline at end of file diff --git a/src/views/debug/view_debug_misc.cpp b/src/views/debug/view_debug_misc.cpp index 7046bc64..cb282daf 100644 --- a/src/views/debug/view_debug_misc.cpp +++ b/src/views/debug/view_debug_misc.cpp @@ -1,9 +1,7 @@ #include "gta/joaat.hpp" #include "gui/components/components.hpp" #include "hooking/hooking.hpp" -#include "natives.hpp" #include "util/pathfind.hpp" -#include "util/ped.hpp" #include "util/system.hpp" #include "view_debug.hpp" #include "network/CNetworkPlayerMgr.hpp" @@ -154,21 +152,6 @@ namespace big ImGui::TreePop(); } - if (ImGui::TreeNode("ANIMATION_PLAYER"_T.data())) - { - static char dict[100], anim[100]; - - ImGui::PushItemWidth(200); - components::input_text_with_hint("##dictionary", "DICT"_T, dict, IM_ARRAYSIZE(dict)); - components::input_text_with_hint("##animation", "ANIMATION"_T, anim, IM_ARRAYSIZE(anim)); - if (ImGui::Button("PLAY_ANIMATION"_T.data())) - g_fiber_pool->queue_job([=] { - ped::ped_play_animation(self::ped, dict, anim); - }); - ImGui::PopItemWidth(); - ImGui::TreePop(); - } - ImGui::EndTabItem(); } } diff --git a/src/views/debug/view_debug_tunables.cpp b/src/views/debug/view_debug_tunables.cpp new file mode 100644 index 00000000..f5ea31a1 --- /dev/null +++ b/src/views/debug/view_debug_tunables.cpp @@ -0,0 +1,273 @@ +#include "gui/components/components.hpp" +#include "services/tunables/tunables_service.hpp" +#include "widgets/imgui_bitfield.hpp" +#include "view_debug.hpp" + +namespace big +{ + enum TunableValueType : int + { + INT, + BOOLEAN, + BITSET, + FLOAT + }; + + struct tunable_debug + { + std::string tunable_name{}; + TunableValueType tunable_value_type = TunableValueType::INT; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(tunable_debug, tunable_name, tunable_value_type) + }; + + nlohmann::json get_tunables_json() + { + nlohmann::json tunables{}; + auto file = g_file_manager.get_project_file("./tunables.json"); + if (file.exists()) + { + std::ifstream iffstream_file(file.get_path()); + iffstream_file >> tunables; + } + return tunables; + } + + void load_tunable_menu(const std::string& selected_tunable, tunable_debug& tunable_obj) + { + if (!selected_tunable.empty()) + { + auto tunables = get_tunables_json(); + if (tunables[selected_tunable].is_null()) + return; + tunable_obj = tunables[selected_tunable].get(); + } + } + + bool is_numeric(const std::string& str) + { + if (str[0] == '-') + return str.size() > 1 && std::all_of(str.begin() + 1, str.end(), ::isdigit); + + return std::all_of(str.begin(), str.end(), ::isdigit); + } + + bool is_hexadecimal(const std::string& str) + { + if (str.size() > 2 && str[0] == '0' && str[1] == 'x') + { + return std::all_of(str.begin() + 2, str.end(), [](unsigned char c) { + return std::isxdigit(c); + }); + } + + return false; + } + + int64_t parse_tunable(const std::string& tunable) + { + if (tunable.empty()) + return 0; + + std::string str = tunable; + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + try + { + if (is_numeric(str)) + { + return std::stoll(str); + } + else if (is_hexadecimal(str)) + { + return std::stoll(str, nullptr, 16); + } + + return rage::joaat(str); + } + catch (const std::out_of_range&) + { + return 0; + } + } + + int64_t* get_tunable_ptr(tunable_debug& tunable_test) + { + auto retn_val = g_tunables_service->get_tunable(parse_tunable(tunable_test.tunable_name)); + if ((size_t)retn_val < UINT32_MAX) + return nullptr; + return retn_val; + } + + std::string get_tunable_display(tunable_debug& tunable_test) + { + auto ptr = get_tunable_ptr(tunable_test); + if (ptr != nullptr) + { + switch (tunable_test.tunable_value_type) + { + case TunableValueType::INT: + { + return std::to_string(*(PINT)ptr); + } + case TunableValueType::BOOLEAN: + { + return (*ptr == TRUE) ? "TRUE" : "FALSE"; + } + case TunableValueType::BITSET: + { + std::ostringstream o; + o << "0x" << std::hex << std::uppercase << (DWORD)*ptr; + return o.str(); + } + case TunableValueType::FLOAT: + { + return std::to_string(*(PFLOAT)ptr); + } + } + } + return "VIEW_DEBUG_TUNABLE_INVALID_TUNABLE_READ"_T.data(); + } + + std::map list_tunables() + { + auto json = get_tunables_json(); + std::map return_value; + for (auto& item : json.items()) + return_value[item.key()] = item.value(); + return return_value; + } + + void save_tunable(char* tunable_name, tunable_debug& tunable_obj) + { + std::string tunable_name_string = tunable_name; + if (!tunable_name_string.empty()) + { + auto json = get_tunables_json(); + json[tunable_name_string] = tunable_obj; + + auto file_path = g_file_manager.get_project_file("./tunables.json").get_path(); + std::ofstream file(file_path, std::ios::out | std::ios::trunc); + file << json.dump(4); + file.close(); + ZeroMemory(tunable_name, sizeof(tunable_name)); + } + } + + void delete_tunable(std::string name) + { + auto locations = get_tunables_json(); + if (locations[name].is_null()) + return; + locations.erase(name); + auto file_path = g_file_manager.get_project_file("./tunables.json").get_path(); + std::ofstream file(file_path, std::ios::out | std::ios::trunc); + file << locations.dump(4); + file.close(); + } + + void debug::tunables() + { + if (ImGui::BeginTabItem("DEBUG_TAB_TUNABLES"_T.data())) + { + static tunable_debug tunable_test{}; + ImGui::SetNextItemWidth(300.f); + components::input_text("VIEW_DEBUG_TUNABLE"_T.data(), tunable_test.tunable_name); + if (auto ptr = get_tunable_ptr(tunable_test)) + { + auto tunable_offset = g_tunables_service->get_tunable_offset(parse_tunable(tunable_test.tunable_name)); + ImGui::SetNextItemWidth(300.f); + ImGui::InputScalar("VIEW_DEBUG_TUNABLE_OFFSET"_T.data(), ImGuiDataType_S32, &tunable_offset, 0, 0, 0, ImGuiInputTextFlags_ReadOnly); + + switch (tunable_test.tunable_value_type) + { + case TunableValueType::INT: + { + ImGui::SetNextItemWidth(300.f); + ImGui::InputScalar("VIEW_DEBUG_GLOBAL_VALUE"_T.data(), ImGuiDataType_S32, ptr); + break; + } + case TunableValueType::BOOLEAN: + { + bool is_tunable_enabled = (*ptr == TRUE); + if (ImGui::Checkbox("VIEW_DEBUG_GLOBAL_VALUE"_T.data(), &is_tunable_enabled)) + { + *ptr = is_tunable_enabled; + } + break; + } + case TunableValueType::BITSET: + { + ImGui::SetNextItemWidth(300.f); + ImGui::Bitfield("VIEW_DEBUG_GLOBAL_VALUE"_T.data(), (PINT)ptr); + break; + } + case TunableValueType::FLOAT: + { + ImGui::SetNextItemWidth(300.f); + ImGui::InputScalar("VIEW_DEBUG_GLOBAL_VALUE"_T.data(), ImGuiDataType_Float, ptr); + break; + } + } + } + else + { + ImGui::Text("VIEW_DEBUG_TUNABLE_INVALID_TUNABLE_READ"_T.data()); + } + + ImGui::SetNextItemWidth(150.f); + ImGui::Combo("VIEW_DEBUG_GLOBAL_TYPE"_T.data(), (int*)&tunable_test.tunable_value_type, "INT\0BOOLEAN\0BITSET\0FLOAT\0"); + + auto tunables = list_tunables(); + static std::string selected_tunable; + ImGui::Text("VIEW_DEBUG_TUNABLE_SAVED_TUNABLES"_T.data()); + if (ImGui::BeginListBox("##savedtunables", ImVec2(200, 250))) + { + for (auto pair : tunables) + { + if (ImGui::Selectable(pair.first.c_str(), selected_tunable == pair.first)) + selected_tunable = std::string(pair.first); + } + ImGui::EndListBox(); + } + ImGui::SameLine(); + if (ImGui::BeginListBox("##tunablevalues", ImVec2(250, 250))) + { + for (auto pair : tunables) + { + ImGui::Selectable(get_tunable_display(pair.second).c_str(), false, ImGuiSelectableFlags_Disabled); + } + ImGui::EndListBox(); + } + ImGui::SameLine(); + ImGui::BeginGroup(); + static char tunable_name_to_save[50]{}; + ImGui::SetNextItemWidth(200.f); + components::input_text("##tunableName", tunable_name_to_save, IM_ARRAYSIZE(tunable_name_to_save)); + if (ImGui::Button("VIEW_DEBUG_TUNABLE_SAVE_TUNABLE"_T.data())) + { + save_tunable(tunable_name_to_save, tunable_test); + } + ImGui::SameLine(); + if (ImGui::Button("VIEW_DEBUG_TUNABLE_LOAD_TUNABLE"_T.data())) + { + load_tunable_menu(selected_tunable, tunable_test); + } + + if (ImGui::Button("VIEW_DEBUG_TUNABLE_DELETE_TUNABLE"_T.data())) + { + if (!selected_tunable.empty()) + { + delete_tunable(selected_tunable); + selected_tunable.clear(); + } + } + ImGui::SameLine(); + if (ImGui::Button("VIEW_DEBUG_GLOBAL_CLEAR"_T.data())) + { + tunable_test.tunable_name = ""; + } + ImGui::EndGroup(); + ImGui::EndTabItem(); + } + } +} \ No newline at end of file diff --git a/src/views/self/view_animations.cpp b/src/views/self/view_animations.cpp index 3fcdb66c..4e11839f 100644 --- a/src/views/self/view_animations.cpp +++ b/src/views/self/view_animations.cpp @@ -1,10 +1,84 @@ #include "services/ped_animations/ped_animations_service.hpp" #include "util/animations.hpp" +#include "util/ped.hpp" #include "views/debug/view_debug.hpp" #include "views/view.hpp" namespace big { + void json_animations(std::string* dict, std::string* anim) + { + static std::string current_dict, current_anim; + static std::vector selected_dict_anim_list{}; + + if (dict && anim) + { + *dict = current_dict; + *anim = current_anim; + } + + static auto reload_anim_list = []() -> void { + selected_dict_anim_list.clear(); + auto range = animations::all_anims.equal_range(current_dict); + for (auto it = range.first; it != range.second; ++it) + { + selected_dict_anim_list.push_back(it->second); + } + }; + + if (animations::has_anim_list_been_populated()) + { + ImGui::Text("VIEW_DEBUG_ANIMATIONS_ANIMATIONS_IN_MEMORY"_T.data(), animations::anim_dict_count(), animations::total_anim_count()); + } + + components::button("VIEW_DEBUG_ANIMATIONS_FETCH_ALL_ANIMS"_T, [] { + animations::fetch_all_anims(); + }); + + ImGui::SetNextItemWidth(400); + components::input_text_with_hint("##dictionaryfilter", "DICT"_T, current_dict); + + if (animations::has_anim_list_been_populated() && ImGui::BeginListBox("##dictionaries", ImVec2(400, 200))) + { + for (auto& entry : animations::all_dicts) + { + std::string entry_lowercase = entry; + std::string search_lowercase = current_dict; + std::transform(entry.begin(), entry.end(), entry.begin(), ::tolower); + std::transform(current_dict.begin(), current_dict.end(), current_dict.begin(), ::tolower); + if (entry.find(search_lowercase) != std::string::npos && ImGui::Selectable(entry.data())) + { + current_dict = entry; + reload_anim_list(); + } + } + + ImGui::EndListBox(); + } + + if (selected_dict_anim_list.size() > 0 && ImGui::BeginListBox("##animations", ImVec2(400, 200))) + { + for (auto& entry : selected_dict_anim_list) + { + if (ImGui::Selectable(entry.data(), entry == current_anim)) + { + current_anim = entry; + } + } + + ImGui::EndListBox(); + } + + components::button("VIEW_DEBUG_ANIMATIONS_PLAY"_T, [] { + TASK::CLEAR_PED_TASKS_IMMEDIATELY(self::ped); + ped::ped_play_animation(self::ped, current_dict, current_anim, 4.f, -4.f, -1, 0, 0, false); + }); + ImGui::SameLine(); + components::button("VIEW_DEBUG_ANIMATIONS_STOP"_T, [] { + TASK::CLEAR_PED_TASKS(self::ped); + }); + } + void view::animations() { ImGui::BeginGroup(); @@ -42,7 +116,7 @@ namespace big components::options_modal( "VIEW_SELF_ANIMATIONS_DEBUG_ANIMATIONS"_T.data(), [] { - debug::animations(&g_ped_animation_service.current_animation.dict, &g_ped_animation_service.current_animation.anim); + json_animations(&g_ped_animation_service.current_animation.dict, &g_ped_animation_service.current_animation.anim); }, true, "VIEW_SELF_ANIMATIONS_LIST_FROM_DEBUG"_T.data());