From 6f40a38045fce1d3071a2c987e88b0757a45197d Mon Sep 17 00:00:00 2001 From: gir489 <100792176+gir489returns@users.noreply.github.com> Date: Fri, 7 Jul 2023 18:52:52 -0400 Subject: [PATCH] Refactored weapons.bin into weapons.json for extensibility and readability. (#1632) * Refactored weapons.bin into weapons.json for extensibility and human readability. Added weapon attachments scraping from the meta files (currently is missing a lot of attachments, more than half, requires RPF reading refactoring to fix.) Added Ammunation to Self -> Weapons, because it's vital you protect yourself, the patriotic way. * Fixed weapons.xml not properly populating all the components. Refactored buttons to use components::button. * Refactored the Attachments code to implicitly trust that the attachments will be there now. Added proper versioning to the weapons.json file. Removed debug logging from gta_data_service.cpp. * Fixed Ammunation buttons. Added loading message for the new weapons.json system. Fixed a bug where two components shared the same name, the user could not select the 2nd component. Fixed Attachments displaying an attachment from a previous weapon if the user changed weapons. * Fixed Tint Apply button not using the components::button template. --- src/services/gta_data/gta_data_service.cpp | 164 ++++++++++++++++----- src/services/gta_data/gta_data_service.hpp | 18 ++- src/services/gta_data/weapon_component.hpp | 15 ++ src/services/gta_data/weapon_file.hpp | 30 ++++ src/services/gta_data/weapon_item.hpp | 11 +- src/services/gta_data/yim_fipackfile.cpp | 2 +- src/views/self/view_weapons.cpp | 88 +++++++++++ src/views/world/view_spawn_ped.cpp | 4 +- src/views/world/view_squad_spawner.cpp | 2 +- 9 files changed, 283 insertions(+), 51 deletions(-) create mode 100644 src/services/gta_data/weapon_component.hpp create mode 100644 src/services/gta_data/weapon_file.hpp diff --git a/src/services/gta_data/gta_data_service.cpp b/src/services/gta_data/gta_data_service.cpp index b8289d9d..409473ff 100644 --- a/src/services/gta_data/gta_data_service.cpp +++ b/src/services/gta_data/gta_data_service.cpp @@ -32,7 +32,6 @@ namespace big gta_data_service::gta_data_service() : m_peds_cache(g_file_manager->get_project_file("./cache/peds.bin"), 5), m_vehicles_cache(g_file_manager->get_project_file("./cache/vehicles.bin"), 4), - m_weapons_cache(g_file_manager->get_project_file("./cache/weapons.bin"), 5), m_update_state(eGtaDataUpdateState::IDLE) { if (!is_cache_up_to_date()) @@ -120,12 +119,28 @@ namespace big const weapon_item& gta_data_service::weapon_by_hash(std::uint32_t hash) { - for (const auto& [name, weapon] : m_weapons) + for (const auto& [name, weapon] : m_weapons_cache.weapon_map) if (rage::joaat(name) == hash) return weapon; return gta_data_service::empty_weapon; } + const weapon_component& gta_data_service::weapon_component_by_hash(std::uint32_t hash) + { + for (const auto& component : m_weapons_cache.weapon_components) + if (component.second.m_hash == hash) + return component.second; + return gta_data_service::empty_component; + } + + const weapon_component& gta_data_service::weapon_component_by_name(std::string name) + { + for (const auto& component : m_weapons_cache.weapon_components) + if (component.first == name) + return component.second; + return gta_data_service::empty_component; + } + string_vec& gta_data_service::ped_types() { return m_ped_types; @@ -145,7 +160,26 @@ namespace big { m_peds_cache.load(); m_vehicles_cache.load(); - m_weapons_cache.load(); + + auto weapons_file = g_file_manager->get_project_file("./cache/weapons.json"); + if (weapons_file.exists()) + { + std::ifstream file(weapons_file.get_path()); + file.open(weapons_file.get_path()); + + try + { + nlohmann::json weapons_file_json; + file >> weapons_file_json; + m_weapons_cache = weapons_file_json["weapons_cache"]; + file.close(); + } + catch (const std::exception& exception) + { + file.close(); + LOG(WARNING) << "Detected corrupt weapons: " << exception.what(); + } + } const auto file_version = memory::module("GTA5.exe").size(); @@ -158,7 +192,8 @@ namespace big load_peds(); load_vehicles(); - load_weapons(); + LOG(INFO) << "Loading " << m_weapons_cache.weapon_map.size() << " weapons from cache."; + LOG(INFO) << "Loading " << m_weapons_cache.weapon_components.size() << " weapon components from cache."; LOG(VERBOSE) << "Loaded all data from cache."; } @@ -199,24 +234,6 @@ namespace big m_vehicles_cache.free(); } - void gta_data_service::load_weapons() - { - const auto weapon_count = m_weapons_cache.data_size() / sizeof(weapon_item); - LOG(INFO) << "Loading " << weapon_count << " weapons from cache."; - - auto cached_weapons = reinterpret_cast(m_weapons_cache.data()); - for (size_t i = 0; i < weapon_count; i++) - { - const auto weapon = cached_weapons[i]; - - add_if_not_exists(m_weapon_types, weapon.m_weapon_type); - m_weapons.insert({weapon.m_name, weapon}); - } - - std::sort(m_weapon_types.begin(), m_weapon_types.end()); - m_weapons_cache.free(); - } - inline void parse_ped(std::vector& peds, std::vector& mapped_peds, pugi::xml_document& doc) { const auto& items = doc.select_nodes("/CPedModelInfo__InitDataList/InitDatas/Item"); @@ -253,10 +270,12 @@ namespace big hash_array mapped_peds; hash_array mapped_vehicles; hash_array mapped_weapons; + hash_array mapped_components; std::vector peds; std::vector vehicles; std::vector weapons; + std::vector weapon_components; constexpr auto exists = [](const hash_array& arr, std::uint32_t val) -> bool { return std::find(arr.begin(), arr.end(), val) != arr.end(); @@ -313,6 +332,46 @@ namespace big } }); } + else if (const auto file_str = path.string(); file_str.find("weaponcomponents") != std::string::npos && path.extension() == ".meta") + { + rpf_wrapper.read_xml_file(path, [&exists, &weapon_components, &mapped_components](pugi::xml_document& doc) { + const auto& items = doc.select_nodes("/CWeaponComponentInfoBlob/Infos/*[self::Item[@type='CWeaponComponentInfo'] or self::Item[@type='CWeaponComponentFlashLightInfo'] or self::Item[@type='CWeaponComponentScopeInfo'] or self::Item[@type='CWeaponComponentSuppressorInfo'] or self::Item[@type='CWeaponComponentVariantModelInfo'] or self::Item[@type='CWeaponComponentClipInfo']]"); + for (const auto& item_node : items) + { + const auto item = item_node.node(); + const std::string name = item.child("Name").text().as_string(); + const auto hash = rage::joaat(name); + + if (!name.starts_with("COMPONENT")) + { + continue; + } + + if (exists(mapped_components, hash)) + { + continue; + } + mapped_components.emplace_back(hash); + + std::string LocName = item.child("LocName").text().as_string(); + std::string LocDesc = item.child("LocDesc").text().as_string(); + + if (LocName.ends_with("INVALID") || LocName.ends_with("RAIL")) + { + continue; + } + + weapon_component component; + + component.m_name = name; + component.m_hash = hash; + component.m_display_name = LocName; + component.m_display_desc = LocDesc; + + weapon_components.push_back(component); + } + }); + } else if (const auto file_str = path.string(); file_str.find("weapon") != std::string::npos && path.extension() == ".meta") { rpf_wrapper.read_xml_file(path, [&exists, &weapons, &mapped_weapons](pugi::xml_document& doc) { @@ -336,9 +395,9 @@ namespace big auto weapon = weapon_item{}; - std::strncpy(weapon.m_name, name, sizeof(weapon.m_name)); + weapon.m_name = name; - std::strncpy(weapon.m_display_name, human_name_hash, sizeof(weapon.m_display_name)); + weapon.m_display_name = human_name_hash; auto weapon_flags = std::string(item.child("WeaponFlags").text().as_string()); @@ -378,10 +437,10 @@ namespace big if (std::strlen(category) > 6) { - std::strncpy(weapon.m_weapon_type, category + 6, sizeof(weapon.m_weapon_type)); + weapon.m_weapon_type = category + 6; } - if (is_gun || !std::strcmp(weapon.m_weapon_type, "MELEE") || !std::strcmp(weapon.m_weapon_type, "UNARMED")) + if (is_gun || weapon.m_weapon_type == "MELEE" || weapon.m_weapon_type == "UNARMED") { const std::string reward_prefix = "REWARD_"; weapon.m_reward_hash = rage::joaat(reward_prefix + name); @@ -393,6 +452,14 @@ namespace big } } + for (pugi::xml_node attach_point : item.child("AttachPoints").children("Item")) + { + for (pugi::xml_node component : attach_point.child("Components").children("Item")) + { + weapon.m_attachments.push_back(component.child_value("Name")); + } + } + weapon.m_hash = hash; weapons.emplace_back(std::move(weapon)); @@ -453,7 +520,12 @@ namespace big } for (auto& item : weapons) { - std::strncpy(item.m_display_name, HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(item.m_display_name), sizeof(item.m_display_name)); + item.m_display_name = HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(item.m_display_name.c_str()); + } + for (auto& item : weapon_components) + { + item.m_display_name = HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(item.m_display_name.c_str()); + item.m_display_desc = HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(item.m_display_desc.c_str()); } for (auto it = peds.begin(); it != peds.end();) { @@ -481,10 +553,10 @@ namespace big m_update_state = eGtaDataUpdateState::IDLE; LOG(INFO) << "Cache has been rebuilt.\n\tPeds: " << peds.size() << "\n\tVehicles: " << vehicles.size() - << "\n\tWeapons: " << weapons.size(); + << "\n\tWeapons: " << weapons.size() << "\n\tWeaponComponents: " << weapon_components.size(); LOG(VERBOSE) << "Starting cache saving procedure..."; - g_thread_pool->push([this, peds = std::move(peds), vehicles = std::move(vehicles), weapons = std::move(weapons)] { + g_thread_pool->push([this, peds = std::move(peds), vehicles = std::move(vehicles), weapons = std::move(weapons), weapon_components = std::move(weapon_components)] { const auto file_version = memory::module("GTA5.exe").size(); { @@ -506,12 +578,34 @@ namespace big } { - const auto data_size = sizeof(weapon_item) * weapons.size(); - m_weapons_cache.set_data(std::make_unique(data_size), data_size); - std::memcpy(m_weapons_cache.data(), weapons.data(), data_size); + m_weapons_cache.version_info.m_game_build = g_pointers->m_gta.m_game_version; + m_weapons_cache.version_info.m_online_version = g_pointers->m_gta.m_online_version; + m_weapons_cache.version_info.m_file_version = file_version; - m_weapons_cache.set_header_version(file_version); - m_weapons_cache.write(); + for (auto weapon : weapons) + { + add_if_not_exists(m_weapon_types, weapon.m_weapon_type); + m_weapons_cache.weapon_map.insert({weapon.m_name, weapon}); + } + + for (auto weapon_component : weapon_components) + { + m_weapons_cache.weapon_components.insert({weapon_component.m_name, weapon_component}); + } + + auto weapons_file = big::g_file_manager->get_project_file("./cache/weapons.json"); + std::ofstream file(weapons_file.get_path()); + try + { + nlohmann::json weapons_file_json; + weapons_file_json["weapons_cache"] = m_weapons_cache; + file << weapons_file_json; + file.flush(); + } + catch (const std::exception& exception) + { + LOG(WARNING) << "Failed to write weapons JSON: " << exception.what(); + } } LOG(INFO) << "Finished writing cache to disk."; @@ -519,4 +613,4 @@ namespace big load_data(); }); } -} +} \ No newline at end of file diff --git a/src/services/gta_data/gta_data_service.hpp b/src/services/gta_data/gta_data_service.hpp index 44b16370..2c547b17 100644 --- a/src/services/gta_data/gta_data_service.hpp +++ b/src/services/gta_data/gta_data_service.hpp @@ -2,7 +2,7 @@ #include "cache_file.hpp" #include "ped_item.hpp" #include "vehicle_item.hpp" -#include "weapon_item.hpp" +#include "weapon_file.hpp" namespace big { @@ -20,7 +20,6 @@ namespace big using ped_map = std::map; using vehicle_map = std::map; - using weapon_map = std::map; using string_vec = std::vector; class gta_data_service final @@ -39,6 +38,8 @@ namespace big const ped_item& ped_by_hash(std::uint32_t hash); const vehicle_item& vehicle_by_hash(std::uint32_t hash); const weapon_item& weapon_by_hash(std::uint32_t hash); + const weapon_component& weapon_component_by_hash(std::uint32_t hash); + const weapon_component& weapon_component_by_name(std::string name); string_vec& ped_types(); string_vec& vehicle_classes(); @@ -52,9 +53,13 @@ namespace big { return m_vehicles; } - weapon_map& weapons() + std::map& weapons() { - return m_weapons; + return m_weapons_cache.weapon_map; + } + std::map& weapon_components() + { + return m_weapons_cache.weapon_components; } private: @@ -63,19 +68,17 @@ namespace big void load_data(); void load_peds(); void load_vehicles(); - void load_weapons(); void rebuild_cache(); private: cache_file m_peds_cache; cache_file m_vehicles_cache; - cache_file m_weapons_cache; + weapon_file m_weapons_cache; // std::map is free sorting algo ped_map m_peds; vehicle_map m_vehicles; - weapon_map m_weapons; string_vec m_ped_types; string_vec m_vehicle_classes; @@ -87,6 +90,7 @@ namespace big static constexpr ped_item empty_ped{}; static constexpr vehicle_item empty_vehicle{}; static constexpr weapon_item empty_weapon{}; + static constexpr weapon_component empty_component{}; }; inline gta_data_service* g_gta_data_service{}; diff --git a/src/services/gta_data/weapon_component.hpp b/src/services/gta_data/weapon_component.hpp new file mode 100644 index 00000000..299bb337 --- /dev/null +++ b/src/services/gta_data/weapon_component.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace big +{ + class weapon_component final + { + public: + std::string m_name; + Hash m_hash; + std::string m_display_name; + std::string m_display_desc; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(weapon_component, m_name, m_hash, m_display_name, m_display_desc) + }; +} \ No newline at end of file diff --git a/src/services/gta_data/weapon_file.hpp b/src/services/gta_data/weapon_file.hpp new file mode 100644 index 00000000..aa1406b0 --- /dev/null +++ b/src/services/gta_data/weapon_file.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "weapon_item.hpp" +#include "weapon_component.hpp" + +namespace big +{ + + class weapon_file + { + public: + struct version_info + { + std::string m_game_build; + std::string m_online_version; + std::uint32_t m_file_version; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(version_info, m_game_build, m_online_version, m_file_version) + } version_info{}; + + std::map weapon_map; + std::map weapon_components; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(weapon_file, version_info, weapon_map, weapon_components) + + bool up_to_date(std::uint32_t file_version) const + { + return file_version == version_info.m_file_version; + } + }; +} \ No newline at end of file diff --git a/src/services/gta_data/weapon_item.hpp b/src/services/gta_data/weapon_item.hpp index 7be5bee8..fb841446 100644 --- a/src/services/gta_data/weapon_item.hpp +++ b/src/services/gta_data/weapon_item.hpp @@ -2,17 +2,18 @@ namespace big { -#pragma pack(push, 4) class weapon_item final { public: - char m_name[32]; - char m_display_name[32]; - char m_weapon_type[16]; + std::string m_name; + std::string m_display_name; + std::string m_weapon_type; std::uint32_t m_hash; std::uint32_t m_reward_hash; std::uint32_t m_reward_ammo_hash; + std::vector m_attachments; bool m_throwable; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(weapon_item, m_name, m_display_name, m_weapon_type, m_hash, m_reward_hash, m_reward_ammo_hash, m_attachments, m_throwable) }; -#pragma pack(pop) } \ No newline at end of file diff --git a/src/services/gta_data/yim_fipackfile.cpp b/src/services/gta_data/yim_fipackfile.cpp index 2e38a3f0..08897f3c 100644 --- a/src/services/gta_data/yim_fipackfile.cpp +++ b/src/services/gta_data/yim_fipackfile.cpp @@ -181,4 +181,4 @@ namespace big } }); } -} +} \ No newline at end of file diff --git a/src/views/self/view_weapons.cpp b/src/views/self/view_weapons.cpp index a22906d7..2eb13ea2 100644 --- a/src/views/self/view_weapons.cpp +++ b/src/views/self/view_weapons.cpp @@ -176,5 +176,93 @@ namespace big ImGui::SliderFloat("Distance", &g.weapons.aimbot.distance, 1.f, 1000.f, "%.0f"); ImGui::PopItemWidth(); } + + ImGui::SeparatorText("Ammunation"); + static Hash selected_weapon_hash, selected_weapon_attachment_hash{}; + static std::string selected_weapon, selected_weapon_attachment; + ImGui::PushItemWidth(300); + if (ImGui::BeginCombo("Weapons", selected_weapon.c_str())) + { + for (auto& weapon : g_gta_data_service->weapons()) + { + bool is_selected = weapon.second.m_hash == selected_weapon_hash; + if (weapon.second.m_display_name != "NULL" && ImGui::Selectable(weapon.second.m_display_name.c_str(), is_selected, ImGuiSelectableFlags_None)) + { + selected_weapon = weapon.second.m_display_name; + selected_weapon_hash = weapon.second.m_hash; + selected_weapon_attachment_hash = {}; + selected_weapon_attachment.clear(); + } + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::SameLine(); + components::button("Give Weapon", [] { + WEAPON::GIVE_WEAPON_TO_PED(self::ped, selected_weapon_hash, 9999, false, true); + }); + ImGui::SameLine(); + components::button("Remove Weapon", [] { + WEAPON::REMOVE_WEAPON_FROM_PED(self::ped, selected_weapon_hash); + }); + + ImGui::PushItemWidth(250); + if (ImGui::BeginCombo("Attachments", selected_weapon_attachment.c_str())) + { + auto weapon = g_gta_data_service->weapon_by_hash(selected_weapon_hash); + if (!weapon.m_attachments.empty()) + { + for (std::string attachment : weapon.m_attachments) + { + auto attachment_component = g_gta_data_service->weapon_component_by_name(attachment); + std::string attachment_name = attachment_component.m_display_name; + Hash attachment_hash = attachment_component.m_hash; + if (attachment_hash == NULL) + { + attachment_name = attachment; + attachment_hash = rage::joaat(attachment); + } + bool is_selected = attachment_hash == selected_weapon_attachment_hash; + std::string display_name = attachment_name.append("##").append(std::to_string(attachment_hash)); + if (ImGui::Selectable(display_name.c_str(), is_selected, ImGuiSelectableFlags_None)) + { + selected_weapon_attachment = attachment_name; + selected_weapon_attachment_hash = attachment_hash; + } + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + } + + ImGui::EndCombo(); + } + ImGui::SameLine(); + components::button("Add to Weapon", [] { + WEAPON::GIVE_WEAPON_COMPONENT_TO_PED(self::ped, selected_weapon_hash, selected_weapon_attachment_hash); + }); + ImGui::SameLine(); + components::button("Remove from Weapon", [] { + WEAPON::REMOVE_WEAPON_COMPONENT_FROM_PED(self::ped, selected_weapon_hash, selected_weapon_attachment_hash); + }); + ImGui::PopItemWidth(); + + static const char* default_tints[]{"Black tint", "Green tint", "Gold tint", "Pink tint", "Army tint", "LSPD tint", "Orange tint", "Platinum tint"}; + static const char* mk2_tints[]{"Classic Black", "Classic Grey", "Classic Two - Tone", "Classic White", "Classic Beige", "Classic Green", "Classic Blue", "Classic Earth", "Classic Brown & Black", "Red Contrast", "Blue Contrast", "Yellow Contrast", "Orange Contrast", "Bold Pink", "Bold Purple & Yellow", "Bold Orange", "Bold Green & Purple", "Bold Red Features", "Bold Green Features", "Bold Cyan Features", "Bold Yellow Features", "Bold Red & White", "Bold Blue & White", "Metallic Gold", "Metallic Platinum", "Metallic Grey & Lilac", "Metallic Purple & Lime", "Metallic Red", "Metallic Green", "Metallic Blue", "Metallic White & Aqua", "Metallic Red & Yellow"}; + static int tint; + + if (selected_weapon.ends_with("Mk II")) + { + ImGui::Combo("Tints", &tint, mk2_tints, IM_ARRAYSIZE(mk2_tints)); + } + else + { + ImGui::Combo("Tints", &tint, default_tints, IM_ARRAYSIZE(default_tints)); + } + ImGui::SameLine(); + components::button("Apply", [] { + WEAPON::SET_PED_WEAPON_TINT_INDEX(self::ped, selected_weapon_hash, tint); + }); } } diff --git a/src/views/world/view_spawn_ped.cpp b/src/views/world/view_spawn_ped.cpp index 76a4c237..f4a18431 100644 --- a/src/views/world/view_spawn_ped.cpp +++ b/src/views/world/view_spawn_ped.cpp @@ -491,7 +491,7 @@ namespace big if (ImGui::BeginCombo("##ped_weapon", selected_ped_weapon_type == SPAWN_PED_NO_WEAPONS ? "NO_WEAPONS"_T.data() : selected_ped_weapon_hash == 0 ? "ALL"_T.data() : - g_gta_data_service->weapon_by_hash(selected_ped_weapon_hash).m_display_name)) + g_gta_data_service->weapon_by_hash(selected_ped_weapon_hash).m_display_name.c_str())) { if (selected_ped_weapon_type != SPAWN_PED_NO_WEAPONS) { @@ -509,7 +509,7 @@ namespace big { if (selected_ped_weapon_type == SPAWN_PED_ALL_WEAPONS || weapon.m_weapon_type == weapon_type_arr[selected_ped_weapon_type]) { - if (ImGui::Selectable(weapon.m_display_name, weapon.m_hash == selected_ped_weapon_hash)) + if (ImGui::Selectable(weapon.m_display_name.c_str(), weapon.m_hash == selected_ped_weapon_hash)) { selected_ped_weapon_hash = weapon.m_hash; } diff --git a/src/views/world/view_squad_spawner.cpp b/src/views/world/view_squad_spawner.cpp index b073786a..b5d471a3 100644 --- a/src/views/world/view_squad_spawner.cpp +++ b/src/views/world/view_squad_spawner.cpp @@ -186,7 +186,7 @@ namespace big std::string filter = new_template.m_weapon_model; std::transform(p_model.begin(), p_model.end(), p_model.begin(), ::tolower); std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower); - if (p_model.find(filter) != std::string::npos && ImGui::Selectable(p.m_name)) + if (p_model.find(filter) != std::string::npos && ImGui::Selectable(p.m_name.c_str())) { new_template.m_weapon_model = p.m_name; }