diff --git a/src/services/locals/locals_service.cpp b/src/services/locals/locals_service.cpp new file mode 100644 index 00000000..82bfa158 --- /dev/null +++ b/src/services/locals/locals_service.cpp @@ -0,0 +1,126 @@ +#include "locals_service.hpp" + +#include "core/data/all_script_names.hpp" +#include "natives.hpp" +#include "pointers.hpp" +#include "fiber_pool.hpp" + +namespace big +{ + + bool locals_service::is_script_thread_running(GtaThread* thread) + { + if (thread) + { + return thread->m_context.m_state == rage::eThreadState::running || thread->m_context.m_state == rage::eThreadState::idle; + } + return false; + } + + bool locals_service::does_script_exist(std::string script_name) + { + for (auto s : all_script_names) + if (script_name == s) + return true; + bool script_exists = false; + g_fiber_pool->queue_job([&] {script_exists = SCRIPT::DOES_SCRIPT_EXIST(script_name.data());}); + return script_exists; + } + + std::filesystem::path locals_service::get_path() + { + return g_file_manager->get_project_file("locals.json").get_path(); + } + + bool locals_service::load() + { + std::ifstream file(locals_service::get_path()); + if (!file.is_open()) + return false; + + try + { + nlohmann::json j; + file >> j; + m_locals.clear(); + for (const auto& l : j.items()) + { + if (!l.key().empty()) + { + local new_local{"", "", 0, 0, 0, 0}; + new_local.m_base_address = j[l.key()]["base_address"]; + std::string script_name = j[l.key()]["script_thread_name"]; + strcpy(new_local.m_script_thread_name, script_name.data()); + new_local.m_freeze = j[l.key()]["freeze"]; + std::string name = j[l.key()]["name"]; + strcpy(new_local.m_name, name.data()); + new_local.m_value = j[l.key()]["value"]; + if (!j[l.key()]["offsets"].is_null()) + { + for (const auto& offset : j[l.key()]["offsets"].items()) + { + if (!offset.key().empty()) + { + local_offset new_offset{0, 0}; + new_offset.m_offset = j[l.key()]["offsets"][offset.key()]["offset"]; + if (!j[l.key()]["offsets"][offset.key()]["size"].is_null()) + new_offset.m_size = j[l.key()]["offsets"][offset.key()]["size"]; + new_local.m_offsets.push_back(new_offset); + } + } + } + new_local.fetch_local_pointer(); + m_locals.push_back(new_local); + } + } + } + catch (const std::exception&) + { + LOG(WARNING) << "Failure to parse locals.json, aborting..."; + + return false; + } + + return true; + } + + void locals_service::save() + { + std::map locals_with_names; + for (auto& l : m_locals) + { + locals_with_names.insert(std::pair(std::string(l.m_name).empty() ? std::string(l.m_script_thread_name + std::string("_") + std::to_string(l.m_base_address)) : l.m_name, l)); + } + + nlohmann::json j; + for (auto& l : locals_with_names) + { + j[l.first]["script_thread_name"] = l.second.m_script_thread_name; + j[l.first]["base_address"] = l.second.m_base_address; + j[l.first]["freeze"] = l.second.m_freeze; + j[l.first]["name"] = l.second.m_name; + j[l.first]["value"] = l.second.m_value; + + for (int i = 0; i < l.second.m_offsets.size(); i++) + { + j[l.first]["offsets"][std::to_string(i)]["offset"] = l.second.m_offsets[i].m_offset; + if (l.second.m_offsets[i].m_size > 0) + j[l.first]["offsets"][std::to_string(i)]["size"] = l.second.m_offsets[i].m_size; + }; + } + + std::ofstream file(locals_service::get_path(), std::ios::out | std::ios::trunc); + + try + { + file << j.dump(4); + + file.close(); + } + catch (const std::exception&) + { + LOG(WARNING) << "Failed to write to locals.json"; + } + } + +}; \ No newline at end of file diff --git a/src/services/locals/locals_service.hpp b/src/services/locals/locals_service.hpp new file mode 100644 index 00000000..484d518f --- /dev/null +++ b/src/services/locals/locals_service.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include "file_manager.hpp" +#include "file_manager/file.hpp" +#include "gta/script_thread.hpp" +#include "gta_util.hpp" +#include "script_local.hpp" + +namespace big +{ + + struct local_offset + { + local_offset(int offset, int size = 0) + { + m_offset = offset; + + if (size) + m_size = size; + } + + int m_offset = 0; + int m_size = 0; + }; + + + struct local + { + GtaThread* m_script_thread; + char m_script_thread_name[200]; + int m_base_address; + bool m_freeze = false; + char m_name[200]; + std::vector m_offsets; + int m_value; + int m_freeze_value; + int* m_internal_address; + + local(const char* script_thread_name, const char* name, const int base_address, const bool freeze, const int (*offsets)[2], int offset_count) + { + m_internal_id = ++m_instance_count; + + strcpy(m_script_thread_name, script_thread_name); + m_base_address = base_address; + m_freeze = freeze; + strcpy(m_name, name); + m_value = 0; + + for (int i = 0; i < offset_count; i++) + m_offsets.push_back(local_offset(offsets[i][0], offsets[i][1])); + + fetch_local_pointer(); + } + + int get_id() const + { + return m_internal_id; + } + + int* fetch_local_pointer() + { + m_script_thread = gta_util::find_script_thread(rage::joaat(m_script_thread_name)); + + if (m_script_thread) + { + script_local actual_local = script_local(m_script_thread, m_base_address); + + for (auto offset : m_offsets) + { + if (offset.m_size > 0) + actual_local = actual_local.at(offset.m_offset, offset.m_size); + else + actual_local = actual_local.at(offset.m_offset); + } + + m_internal_address = actual_local.as(); + + return m_internal_address; + } + return nullptr; + } + + private: + inline static int m_instance_count; + int m_internal_id; + }; + + + class locals_service + { + public: + std::filesystem::path get_path(); + bool load(); + void save(); + + static bool does_script_exist(std::string script); + static bool is_script_thread_running(GtaThread* thread); + + std::vector m_locals; + bool m_running = false; + }; + + inline locals_service g_locals_service{}; +} \ No newline at end of file diff --git a/src/views/debug/view_debug_locals.cpp b/src/views/debug/view_debug_locals.cpp index 163adb6a..6bac1656 100644 --- a/src/views/debug/view_debug_locals.cpp +++ b/src/views/debug/view_debug_locals.cpp @@ -1,11 +1,183 @@ +#include "gui/components/components.hpp" +#include "services/locals/locals_service.hpp" #include "view_debug.hpp" namespace big { + + void render_local_creator_popup_content() + { + static int base_address = 0; + static bool freeze = false; + static char name[200] = ""; + static char script_thread_name[200] = ""; + static int(*offsets)[2] = nullptr; + static int offset_count = 0; + static int previous_offset_count = 0; + components::input_text_with_hint("##local_name", "Name", name, sizeof(name)); + components::input_text_with_hint("##local_script_thread_name", "Script thread", script_thread_name, sizeof(script_thread_name)); + ImGui::Text("Base address"); + ImGui::InputInt("##local_base_address", &base_address); + ImGui::Text("Offsetcount"); + ImGui::InputInt("##modal_offset_count", &offset_count); + + if (offset_count < 0) + offset_count = 0; + else if (offset_count > 10) + offset_count = 10; + + if (offset_count != previous_offset_count) + { + int(*new_offsets)[2] = new int[offset_count][2]{0}; + memcpy(new_offsets, offsets, sizeof(int) * std::min(offset_count, previous_offset_count) * 2); + + delete[] offsets; + offsets = new_offsets; + + previous_offset_count = offset_count; + } + + ImGui::PushItemWidth(320.f); + for (int i = 0; i < offset_count; i++) + { + ImGui::PushID(i); + + ImGui::Separator(); + + ImGui::Text("DEBUG_GLOBAL_OFFSET"_T.data(), i + 1); + ImGui::InputInt("##offset", &offsets[i][0]); + + ImGui::Text("DEBUG_GLOBAL_SIZE"_T.data()); + ImGui::SameLine(); + ImGui::InputInt("##size", &offsets[i][1]); + + ImGui::PopID(); + } + ImGui::PopItemWidth(); + + static auto reset_values = []() -> void { + strcpy(name, ""); + freeze = false; + delete[] offsets; + offsets = nullptr; + offset_count = 0; + previous_offset_count = 0; + }; + + if (components::button("CANCEL"_T)) + { + reset_values(); + + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (components::button("SAVE"_T)) + { + if (locals_service::does_script_exist(script_thread_name)) + { + auto new_local = local(script_thread_name, name, base_address, freeze, offsets, offset_count); + g_locals_service.m_locals.push_back(new_local); + + reset_values(); + + ImGui::CloseCurrentPopup(); + } + else + { + g_notification_service->push_error("Locals editor", "Script does not exist"); + } + }; + } + void debug::locals() { if (ImGui::BeginTabItem("DEBUG_TAB_LOCALS"_T.data())) { + if (components::button("LOAD"_T)) + g_locals_service.load(); + ImGui::SameLine(); + if (components::button("SAVE"_T)) + g_locals_service.save(); + + if (components::button("Add local")) + { + ImGui::OpenPopup("##addlocal"); + } + + if (ImGui::BeginPopupModal("##addlocal")) + { + render_local_creator_popup_content(); + + ImGui::EndPopup(); + } + + for (auto& local_ : g_locals_service.m_locals) + { + ImGui::BeginGroup(); + ImGui::PushID(local_.get_id()); + + ImGui::Text("%s : %s", local_.m_name, local_.m_script_thread_name); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + char offsetschain[200] = ""; + strcat(offsetschain, std::to_string(local_.m_base_address).data()); + for (auto o : local_.m_offsets) + { + strcat(offsetschain, std::string(".f_" + std::to_string(o.m_offset)).data()); + if (o.m_size) + strcat(offsetschain, std::string("/" + std::to_string(o.m_size)).data()); + } + ImGui::Text(offsetschain); + ImGui::EndTooltip(); + } + + //Find the thread among the script threads + if (!local_.m_script_thread) + local_.m_script_thread = gta_util::find_script_thread(rage::joaat(local_.m_script_thread_name)); + + if (local_.m_script_thread && locals_service::is_script_thread_running(local_.m_script_thread)) + { + //Check whether the address is found + if (local_.m_internal_address) + { + ImGui::Text("Value"); + ImGui::SetNextItemWidth(200); + if (ImGui::InputInt("##local_value", local_.m_internal_address)) + { + local_.m_value = *local_.m_internal_address; + } + ImGui::SameLine(); + if (ImGui::Checkbox("Freeze", &local_.m_freeze)) + local_.m_freeze_value = *local_.m_internal_address; + + if (local_.m_freeze) + *local_.m_internal_address = local_.m_freeze_value; + } + else + { + if (components::button("Fetch")) + { + local_.fetch_local_pointer(); + } + } + } + else + { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + ImGui::Text("%s isn't running", local_.m_script_thread_name); + ImGui::PopStyleColor(); + } + if (components::button("Delete")) + std::erase_if(g_locals_service.m_locals, [local_](local l) { + return l.get_id() == local_.get_id(); + }); + + ImGui::PopID(); + ImGui::Separator(); + ImGui::EndGroup(); + } + ImGui::EndTabItem(); } }