feat(debug): Add Tunables Tab (#3431)

Added a Tunables tab similar to Globals/Locals that can be used to read and write tunables by inputting their unsigned/signed or hexadecimal hash, as well as their pre-joaat name. It also displays the offset of the given tunable.

Removed the animation players in the Debug tab as the one in the Self tab makes them redundant.
This commit is contained in:
Arthur 2024-07-26 12:05:47 +03:00 committed by GitHub
parent afd7bcf8c3
commit eb68394382
9 changed files with 375 additions and 113 deletions

View File

@ -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<std::uint64_t[]>(m_num_tunables);
memcpy(m_tunables_backup.get(), script_global(0x40000).as<void*>(), m_num_tunables * 8);
memcpy(m_tunables_backup.get(), script_global(TUNABLE_BASE_ADDRESS).as<void*>(), 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<int*>();
auto value = *script_global(TUNABLE_BASE_ADDRESS).at(i).as<int*>();
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<void*>(), m_tunables_backup.get(), m_num_tunables * 8);
memcpy(script_global(TUNABLE_BASE_ADDRESS).as<void*>(), m_tunables_backup.get(), m_num_tunables * 8);
if (m_tunables.size() == 0)
{

View File

@ -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<int, rage::joaat_t> m_junk_values{};

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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<std::string> 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);
});
}
}

View File

@ -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();
}
}

View File

@ -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<tunable_debug>();
}
}
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<int64_t*>(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<std::string, tunable_debug> list_tunables()
{
auto json = get_tunables_json();
std::map<std::string, tunable_debug> 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();
}
}
}

View File

@ -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<std::string> 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());