diff --git a/src/function_types.hpp b/src/function_types.hpp index 9fb7b8c7..3d401e7b 100644 --- a/src/function_types.hpp +++ b/src/function_types.hpp @@ -139,4 +139,6 @@ namespace big::functions using migrate_object = void (*)(CNetGamePlayer* player, rage::netObject* object, int type); using handle_chat_message = void (*)(void* chat_data, void*, rage::rlGamerHandle* handle, const char* text, bool is_team); + + using update_language = void (*)(bool); } diff --git a/src/gta_pointers.hpp b/src/gta_pointers.hpp index 91349232..02f64b3c 100644 --- a/src/gta_pointers.hpp +++ b/src/gta_pointers.hpp @@ -260,6 +260,9 @@ namespace big PVOID m_netfilter_handle_message{}; functions::handle_chat_message m_handle_chat_message{}; + + int* m_language; + functions::update_language m_update_language{}; }; #pragma pack(pop) static_assert(sizeof(gta_pointers) % 8 == 0, "Pointers are not properly aligned"); diff --git a/src/main.cpp b/src/main.cpp index 83280f2e..222d64cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,9 +66,6 @@ BOOL APIENTRY DllMain(HMODULE hmod, DWORD reason, PVOID) LOG(INFO) << "Yim's Menu Initializing"; LOGF(INFO, "Git Info\n\tBranch:\t{}\n\tHash:\t{}\n\tDate:\t{}", version::GIT_BRANCH, version::GIT_SHA1, version::GIT_DATE); - g_translation_service.init(); - LOG(INFO) << "Translation Service initialized."; - auto thread_pool_instance = std::make_unique(); LOG(INFO) << "Thread pool initialized."; @@ -88,6 +85,9 @@ BOOL APIENTRY DllMain(HMODULE hmod, DWORD reason, PVOID) auto fiber_pool_instance = std::make_unique(11); LOG(INFO) << "Fiber pool initialized."; + g_translation_service.init(); + LOG(INFO) << "Translation Service initialized."; + auto hooking_instance = std::make_unique(); LOG(INFO) << "Hooking initialized."; diff --git a/src/pointers.cpp b/src/pointers.cpp index bbe02e17..d60da233 100644 --- a/src/pointers.cpp +++ b/src/pointers.cpp @@ -1213,6 +1213,16 @@ namespace big g_pointers->m_gta.m_handle_chat_message = ptr.as(); } }, + // Language & Update Language + { + "L&UL", + "83 3D ? ? ? ? ? 44 8B C3", + [](memory::handle ptr) + { + g_pointers->m_gta.m_language = ptr.add(2).rip().add(1).as(); + g_pointers->m_gta.m_update_language = ptr.add(0x38).rip().as(); + } + }, // Max Wanted Level { "MWL", diff --git a/src/services/api/api_service.cpp b/src/services/api/api_service.cpp index 4fd08a55..d8aa97cd 100644 --- a/src/services/api/api_service.cpp +++ b/src/services/api/api_service.cpp @@ -22,11 +22,19 @@ namespace big if (response.status_code == 200) { - nlohmann::json obj = nlohmann::json::parse(response.text); - if (obj["Total"] > 0 && username.compare(obj["Accounts"].at(0)["Nickname"]) == 0) + try { - result = obj["Accounts"].at(0)["RockstarId"]; - return true; + nlohmann::json obj = nlohmann::json::parse(response.text); + + if (obj["Total"] > 0 && username.compare(obj["Accounts"].at(0)["Nickname"]) == 0) + { + result = obj["Accounts"].at(0)["RockstarId"]; + return true; + } + } + catch (std::exception& e) + { + return false; } } @@ -39,9 +47,16 @@ namespace big if (response.status_code == 200) { - nlohmann::json obj = nlohmann::json::parse(response.text); - result = obj["Accounts"].at(0)["RockstarAccount"]["Name"]; - return true; + try + { + nlohmann::json obj = nlohmann::json::parse(response.text); + result = obj["Accounts"].at(0)["RockstarAccount"]["Name"]; + return true; + } + catch (std::exception& e) + { + return false; + } } return false; @@ -61,9 +76,18 @@ namespace big cpr::Header{{"X-AMC", "true"}, {"X-Requested-With", "XMLHttpRequest"}}, cpr::Parameters{{"title", "gtav"}, {"contentId", content_id.data()}}); - result = nlohmann::json::parse(response.text); + if (response.status_code != 200) + return false; - return response.status_code == 200; + try + { + result = nlohmann::json::parse(response.text); + return true; + } + catch (std::exception& e) + { + return false; + } } bool api_service::download_job_metadata(std::string_view content_id, int f1, int f0, int lang) diff --git a/src/services/gui/gui_service.hpp b/src/services/gui/gui_service.hpp index f9cc9105..fdc007ff 100644 --- a/src/services/gui/gui_service.hpp +++ b/src/services/gui/gui_service.hpp @@ -147,7 +147,6 @@ namespace big {tabs::HOTKEY_SETTINGS, {"GUI_TAB_HOTKEYS", view::hotkey_settings}}, {tabs::REACTION_SETTINGS, {"GUI_TAB_REACTIONS", view::reaction_settings}}, {tabs::PROTECTION_SETTINGS, {"GUI_TAB_PROTECTION", view::protection_settings}}, - {tabs::TRANSLATION_SETTINGS, {"GUI_TAB_TRANSLATION", view::translation_settings}}, {tabs::DEBUG, {"GUI_TAB_DEBUG", nullptr}}, }, }, diff --git a/src/services/player_database/player_database_service.cpp b/src/services/player_database/player_database_service.cpp index e637cda6..f40f7350 100644 --- a/src/services/player_database/player_database_service.cpp +++ b/src/services/player_database/player_database_service.cpp @@ -40,20 +40,27 @@ namespace big m_selected = nullptr; if (std::filesystem::exists(m_file_path)) { - std::ifstream file_stream(m_file_path); - - nlohmann::json json; - file_stream >> json; - file_stream.close(); - - for (auto& [key, value] : json.items()) + try { - auto player = value.get>(); - m_players[std::stoll(key)] = player; + std::ifstream file_stream(m_file_path); - std::string lower = player->name; - std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); - m_sorted_players[lower] = player; + nlohmann::json json; + file_stream >> json; + file_stream.close(); + + for (auto& [key, value] : json.items()) + { + auto player = value.get>(); + m_players[std::stoll(key)] = player; + + std::string lower = player->name; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + m_sorted_players[lower] = player; + } + } + catch (std::exception& e) + { + LOG(WARNING) << "Failed to load player database file. " << e.what(); } } } @@ -195,7 +202,8 @@ namespace big { if (it->second->online_state == PlayerOnlineStatus::OFFLINE && it->second->notify_online) { - g_notification_service->push_success("Player DB", std::format("{} is now online!", it->second->name)); + g_notification_service->push_success("Player DB", + std::format("{} is now online!", it->second->name)); } it->second->online_state = PlayerOnlineStatus::ONLINE; diff --git a/src/services/translation_service/local_index.hpp b/src/services/translation_service/local_index.hpp index aa34b35c..af823191 100644 --- a/src/services/translation_service/local_index.hpp +++ b/src/services/translation_service/local_index.hpp @@ -10,8 +10,9 @@ namespace big std::string selected_language; std::string fallback_default_language; std::map fallback_languages; + bool default_language_set = false; - NLOHMANN_DEFINE_TYPE_INTRUSIVE(local_index, version, selected_language, fallback_default_language, fallback_languages) + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(local_index, version, selected_language, fallback_default_language, fallback_languages, default_language_set) }; } \ No newline at end of file diff --git a/src/services/translation_service/translation_service.cpp b/src/services/translation_service/translation_service.cpp index ecbb776b..d75cc463 100644 --- a/src/services/translation_service/translation_service.cpp +++ b/src/services/translation_service/translation_service.cpp @@ -1,6 +1,8 @@ #include "translation_service.hpp" +#include "fiber_pool.hpp" #include "file_manager.hpp" +#include "pointers.hpp" #include "thread_pool.hpp" #include @@ -39,7 +41,12 @@ namespace big update_language_packs(); m_local_index.version = m_remote_index.version; } + load_translations(); + + if (loaded_remote_index) + try_set_default_language(); + return; } @@ -56,6 +63,7 @@ namespace big m_local_index.version = m_remote_index.version; load_translations(); + try_set_default_language(); } std::string_view translation_service::get_translation(const std::string_view translation_key) const @@ -130,6 +138,18 @@ namespace big save_local_index(); } + bool translation_service::does_language_exist(const std::string_view language) + { + auto file = m_translation_directory->get_file(std::format("./{}.json", language)); + if (file.exists()) + return true; + + if (auto it = m_remote_index.translations.find(language.data()); it != m_remote_index.translations.end()) + return true; + + return false; + } + nlohmann::json translation_service::load_translation(const std::string_view pack_id) { auto file = m_translation_directory->get_file(std::format("./{}.json", pack_id)); @@ -144,7 +164,20 @@ namespace big // make a copy available m_local_index.fallback_languages[pack_id.data()] = m_remote_index.translations[pack_id.data()]; } - return nlohmann::json::parse(std::ifstream(file.get_path(), std::ios::binary)); + + try + { + return nlohmann::json::parse(std::ifstream(file.get_path(), std::ios::binary)); + } + catch (std::exception& e) + { + LOG(WARNING) << "Failed to parse language pack. " << e.what(); + + if (auto it = m_remote_index.translations.find(pack_id.data()); it != m_remote_index.translations.end()) // ensure that local language files are not removed + std::filesystem::remove(file.get_path()); + + return {}; + } } bool translation_service::download_language_pack(const std::string_view pack_id) @@ -155,12 +188,20 @@ namespace big if (response.status_code == 200) { - auto json = nlohmann::json::parse(response.text); - auto lang_file = m_translation_directory->get_file("./" + it->second.file); + try + { + auto json = nlohmann::json::parse(response.text); + auto lang_file = m_translation_directory->get_file("./" + it->second.file); - auto out_file = std::ofstream(lang_file.get_path(), std::ios::binary | std::ios::trunc); - out_file << json.dump(4); - out_file.close(); + auto out_file = std::ofstream(lang_file.get_path(), std::ios::binary | std::ios::trunc); + out_file << json.dump(4); + out_file.close(); + } + catch (std::exception& e) + { + LOG(WARNING) << "Failed to parse language pack. " << e.what(); + return false; + } return true; } @@ -174,7 +215,15 @@ namespace big if (response.status_code == 200) { - m_remote_index = nlohmann::json::parse(response.text); + try + { + m_remote_index = nlohmann::json::parse(response.text); + } + catch (std::exception& e) + { + LOG(WARNING) << "Failed to load remote index. " << e.what(); + return false; + } return true; } @@ -186,8 +235,16 @@ namespace big const auto local_index = m_translation_directory->get_file("./index.json"); if (local_index.exists()) { - const auto path = local_index.get_path(); - m_local_index = nlohmann::json::parse(std::ifstream(path, std::ios::binary)); + try + { + const auto path = local_index.get_path(); + m_local_index = nlohmann::json::parse(std::ifstream(path, std::ios::binary)); + } + catch (std::exception& e) + { + LOG(WARNING) << "Failed to load local index. " << e.what(); + return false; + } return true; } @@ -220,4 +277,40 @@ namespace big return response; } + + void translation_service::try_set_default_language() + { + if (!m_local_index.default_language_set) + { + g_fiber_pool->queue_job([this] { + std::string preferred_lang = "en_US"; + auto game_lang = *g_pointers->m_gta.m_language; + + switch (game_lang) + { + case 1: preferred_lang = "fr_FR"; break; + case 2: preferred_lang = "de_DE"; break; + case 3: preferred_lang = "it_IT"; break; + case 4: + case 11: preferred_lang = "es_ES"; break; + case 5: preferred_lang = "pt_BR"; break; + case 6: preferred_lang = "pl_PL"; break; + case 7: preferred_lang = "ru_RU"; break; + case 8: preferred_lang = "ko_KR"; break; + case 9: preferred_lang = "zh_TW"; break; + case 10: preferred_lang = "ja_JP"; break; + case 12: preferred_lang = "zh_CN"; break; + } + + if (does_language_exist(preferred_lang)) + { + m_local_index.selected_language = preferred_lang; + save_local_index(); + } + + m_local_index.default_language_set = true; + load_translations(); + }); + } + } } diff --git a/src/services/translation_service/translation_service.hpp b/src/services/translation_service/translation_service.hpp index 3061fb4b..cc9484f3 100644 --- a/src/services/translation_service/translation_service.hpp +++ b/src/services/translation_service/translation_service.hpp @@ -23,7 +23,7 @@ namespace big void init(); std::string_view get_translation(const std::string_view translation_key) const; - std::string_view get_translation(const rage::joaat_t translation_key, const std::string_view fallback = { 0, 0 }) const; + std::string_view get_translation(const rage::joaat_t translation_key, const std::string_view fallback = {0, 0}) const; std::map& available_translations(); const std::string& current_language_pack(); @@ -32,6 +32,7 @@ namespace big private: void load_translations(); + bool does_language_exist(const std::string_view language); nlohmann::json load_translation(const std::string_view pack_id); bool download_language_pack(const std::string_view pack_id); @@ -51,6 +52,8 @@ namespace big void use_fallback_remote(); cpr::Response download_file(const std::string& filename); + void try_set_default_language(); + private: const std::string m_url; const std::string m_fallback_url; diff --git a/src/services/vehicle/handling_service.cpp b/src/services/vehicle/handling_service.cpp index e7b551e3..3b5ee6f2 100644 --- a/src/services/vehicle/handling_service.cpp +++ b/src/services/vehicle/handling_service.cpp @@ -30,14 +30,21 @@ namespace big auto file_path = item.path(); if (file_path.extension() == ".json") { - auto profile_file = std::ifstream(file_path, std::ios::binary); - nlohmann::json j; - profile_file >> j; - profile_file.close(); + try + { + auto profile_file = std::ifstream(file_path, std::ios::binary); + nlohmann::json j; + profile_file >> j; + profile_file.close(); - m_handling_profiles.emplace(file_path.stem().string(), j.get()); + m_handling_profiles.emplace(file_path.stem().string(), j.get()); - ++files_loaded; + ++files_loaded; + } + catch (std::exception& e) + { + LOG(WARNING) << "Failed to load " << file_path.filename() << ". " << e.what(); + } } // deprecate this else if (file_path.extension() == ".bin") @@ -46,7 +53,7 @@ namespace big auto profile_file = std::ifstream(file_path, std::ios::binary); auto profile = handling_profile(); - profile_file.read(reinterpret_cast(&profile), 328);// hardcoded old size to prevent overreading + profile_file.read(reinterpret_cast(&profile), 328); // hardcoded old size to prevent overreading profile_file.close(); const auto new_save = file_path.stem().string(); diff --git a/src/services/vehicle/persist_car_service.cpp b/src/services/vehicle/persist_car_service.cpp index 56dc0eca..e4910beb 100644 --- a/src/services/vehicle/persist_car_service.cpp +++ b/src/services/vehicle/persist_car_service.cpp @@ -35,8 +35,16 @@ namespace big nlohmann::json vehicle_json; - file_stream >> vehicle_json; - file_stream.close(); + try + { + file_stream >> vehicle_json; + file_stream.close(); + } + catch (std::exception& e) + { + g_notification_service->push_warning("PERSIST_CAR_TITLE"_T.data(), "Failed to load JSON file"); + return NULL; + } return spawn_vehicle_full(vehicle_json, self::ped); } diff --git a/src/views/settings/view_settings.cpp b/src/views/settings/view_settings.cpp index eb3ba3f4..6a21e242 100644 --- a/src/views/settings/view_settings.cpp +++ b/src/views/settings/view_settings.cpp @@ -1,9 +1,63 @@ +#include "core/data/language_codes.hpp" +#include "pointers.hpp" +#include "thread_pool.hpp" #include "views/view.hpp" namespace big { void view::settings() { + const auto& language_entries = g_translation_service.available_translations(); + const auto& current_pack = g_translation_service.current_language_pack(); + + ImGui::SeparatorText("SETTINGS_LANGUAGES"_T.data()); + + if (ImGui::BeginCombo("Menu Language", language_entries.at(current_pack).name.c_str())) + { + for (auto& i : language_entries) + { + if (ImGui::Selectable(i.second.name.c_str(), i.first == current_pack)) + g_translation_service.select_language_pack(i.first); + + if (i.first == current_pack) + { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + if (ImGui::BeginCombo("Game Language", languages[*g_pointers->m_gta.m_language].name)) + { + for (auto& language : languages) + { + if (ImGui::Selectable(language.name, language.id == *g_pointers->m_gta.m_language)) + { + *g_pointers->m_gta.m_language = language.id; + + g_fiber_pool->queue_job([] { + g_pointers->m_gta.m_update_language(true); + }); + } + + if (language.id == *g_pointers->m_gta.m_language) + { + ImGui::SetItemDefaultFocus(); + } + } + + ImGui::EndCombo(); + } + + if (components::button("Force Update Languages")) + { + g_thread_pool->push([] { + g_translation_service.update_language_packs(); + + g_notification_service->push_success("Translations", "Finished updating translations."); + }); + } + ImGui::SeparatorText("SETTINGS_MISC"_T.data()); ImGui::Checkbox("SETTINGS_MISC_DEV_DLC"_T.data(), &g.settings.dev_dlc); diff --git a/src/views/settings/view_translation_settings.cpp b/src/views/settings/view_translation_settings.cpp deleted file mode 100644 index 01c8033e..00000000 --- a/src/views/settings/view_translation_settings.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "thread_pool.hpp" -#include "views/view.hpp" - - -namespace big -{ - void view::translation_settings() - { - const auto& language_entries = g_translation_service.available_translations(); - const auto current_pack = g_translation_service.current_language_pack(); - - ImGui::Text("SETTINGS_LANGUAGES"_T.data()); - if (ImGui::BeginCombo("##combo-languages", language_entries.at(current_pack).name.c_str())) - { - for (auto& i : language_entries) - { - if (ImGui::Selectable(i.second.name.c_str(), i.first == current_pack)) - g_translation_service.select_language_pack(i.first); - - if (i.first == current_pack) - { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } - - if (components::button("Force Update Languages")) - { - g_thread_pool->push([] { - g_translation_service.update_language_packs(); - - g_notification_service->push_success("Translations", "Finished updating translations."); - }); - } - } -} diff --git a/src/views/view.hpp b/src/views/view.hpp index cc5f7065..f90e9fb7 100644 --- a/src/views/view.hpp +++ b/src/views/view.hpp @@ -24,7 +24,6 @@ namespace big public: static void active_view(); static void esp_settings(); - static void context_menu_settings(); static void outfit_editor(); static void outfit_slots(); static void stat_editor(); @@ -34,7 +33,6 @@ namespace big static void handling_saved_profiles(); static void reaction_settings(); static void protection_settings(); - static void translation_settings(); static void heading(); static void mobile(); static void navigation(); diff --git a/src/views/world/view_creator.cpp b/src/views/world/view_creator.cpp index 11e05530..4798ce2b 100644 --- a/src/views/world/view_creator.cpp +++ b/src/views/world/view_creator.cpp @@ -75,7 +75,7 @@ namespace big components::button("CREATOR_JOB_IMPORT"_T, [] { g_thread_pool->push([] { std::string content_id = job_link; - nlohmann::json job_details; + if (content_id.starts_with("https://")) content_id = content_id.substr(46);