diff --git a/scripts/gtav-classes.cmake b/scripts/gtav-classes.cmake index 0f2d7817..713c6dfd 100644 --- a/scripts/gtav-classes.cmake +++ b/scripts/gtav-classes.cmake @@ -3,7 +3,7 @@ include(FetchContent) FetchContent_Declare( gtav_classes GIT_REPOSITORY https://github.com/Yimura/GTAV-Classes.git - GIT_TAG d8304d3e608dac2c22754962420c19f6e74b2c47 + GIT_TAG 95dadd6e7ee7bfa15d66bb2c597d13e97c631727 GIT_PROGRESS TRUE CONFIGURE_COMMAND "" BUILD_COMMAND "" diff --git a/src/byte_patch_manager.cpp b/src/byte_patch_manager.cpp index 116d6b00..7de78013 100644 --- a/src/byte_patch_manager.cpp +++ b/src/byte_patch_manager.cpp @@ -73,6 +73,10 @@ namespace big // Patch script network check memory::byte_patch::make(g_pointers->m_gta.m_model_spawn_bypass, std::vector{0x90, 0x90})->apply(); // this is no longer integrity checked + + // Prevent the attribute task from failing + memory::byte_patch::make(g_pointers->m_sc.m_read_attribute_patch, std::vector{0x90, 0x90})->apply(); + memory::byte_patch::make(g_pointers->m_sc.m_read_attribute_patch_2, std::vector{0xB0, 0x01})->apply(); } byte_patch_manager::byte_patch_manager() diff --git a/src/core/settings.hpp b/src/core/settings.hpp index b298e1a5..0487ea64 100644 --- a/src/core/settings.hpp +++ b/src/core/settings.hpp @@ -237,8 +237,12 @@ namespace big bool notify_when_offline = false; bool notify_on_session_type_change = false; bool notify_on_session_change = false; + bool notify_on_spectator_change = false; + bool notify_on_become_host = false; + bool notify_on_transition_change = false; + bool notify_on_mission_change = false; - NLOHMANN_DEFINE_TYPE_INTRUSIVE(player_db, update_player_online_states, notify_when_online, notify_when_joinable, notify_when_unjoinable, notify_when_offline, notify_on_session_type_change, notify_on_session_change) + NLOHMANN_DEFINE_TYPE_INTRUSIVE(player_db, update_player_online_states, notify_when_online, notify_when_joinable, notify_when_unjoinable, notify_when_offline, notify_on_session_type_change, notify_on_session_change, notify_on_spectator_change, notify_on_become_host, notify_on_transition_change, notify_on_mission_change) } player_db{}; struct protections diff --git a/src/function_types.hpp b/src/function_types.hpp index fae225ca..742619e0 100644 --- a/src/function_types.hpp +++ b/src/function_types.hpp @@ -149,4 +149,7 @@ namespace big::functions using get_host_array_handler_by_index = rage::netArrayHandlerBase* (*)(CGameScriptHandlerNetComponent* component, int index); using get_title_caption_error_message_box = const wchar_t* (*)(rage::joaat_t joaated_error_code); + + using update_presence_attribute_int = void (*)(void* presence_data, int profile_index, char* attr, std::uint64_t value); + using update_presence_attribute_string = void (*)(void* presence_data, int profile_index, char* attr, char* value); } diff --git a/src/gta/enums.hpp b/src/gta/enums.hpp index 69e940ea..257a1831 100644 --- a/src/gta/enums.hpp +++ b/src/gta/enums.hpp @@ -2019,4 +2019,17 @@ enum class GSType : int32_t Public, Max, Modder = 69 // stand? +}; + +enum class GameMode : int32_t +{ + None = -1, + Mission = 0, + Deathmatch = 1, // or koth + Race = 2, + Survival = 3, + GangAttack = 6, + Golf = 0xB, + Tennis = 0xC, + ShootingRange = 0xD }; \ No newline at end of file diff --git a/src/gta_pointers.hpp b/src/gta_pointers.hpp index 424e7d10..83fda0d4 100644 --- a/src/gta_pointers.hpp +++ b/src/gta_pointers.hpp @@ -272,6 +272,8 @@ namespace big functions::get_title_caption_error_message_box m_get_title_caption_error_message_box{}; PVOID m_send_non_physical_player_data; + + void** m_presence_data{}; }; #pragma pack(pop) static_assert(sizeof(gta_pointers) % 8 == 0, "Pointers are not properly aligned"); diff --git a/src/hooks/protections/can_apply_data.cpp b/src/hooks/protections/can_apply_data.cpp index 7c58282a..f9d92929 100644 --- a/src/hooks/protections/can_apply_data.cpp +++ b/src/hooks/protections/can_apply_data.cpp @@ -34,6 +34,7 @@ namespace big struct sync_node_id { Hash id; + const char* name; constexpr sync_node_id() { @@ -43,7 +44,8 @@ namespace big template constexpr sync_node_id(char const (&pp)[N]) { - id = rage::consteval_joaat(pp); + id = rage::consteval_joaat(pp); + name = pp; } // implicit conversion @@ -728,6 +730,113 @@ namespace big return !crash; } +#define LOG_FIELD_H(type, field) LOG(INFO) << "\t" << #field << ": " << HEX_TO_UPPER((((type*)(node))->field)); +#define LOG_FIELD(type, field) LOG(INFO) << "\t" << #field << ": " << ((((type*)(node))->field)); +#define LOG_FIELD_C(type, field) LOG(INFO) << "\t" << #field << ": " << (int)((((type*)(node))->field)); +#define LOG_FIELD_B(type, field) LOG(INFO) << "\t" << #field << ": " << ((((type*)(node))->field) ? "YES" : "NO"); +#define LOG_FIELD_V3(type, field) \ + LOG(INFO) << "\t" << #field << ": X: " << ((((type*)(node))->field)).x << " Y: " << ((((type*)(node))->field)).y \ + << " Z: " << ((((type*)(node))->field)).z; +#define LOG_FIELD_V4(type, field) \ + LOG(INFO) << "\t" << #field << ": X: " << ((((type*)(node))->field)).x << " Y: " << ((((type*)(node))->field)).y \ + << " Z: " << ((((type*)(node))->field)).z << " W: " << ((((type*)(node))->field)).w; + + void log_node(sync_node_id node_id, player_ptr sender, CProjectBaseSyncDataNode* node) + { + LOG(INFO) << sender->get_name() << ": " << node_id.name; + + switch (node_id) + { + case sync_node_id("CVehicleCreationDataNode"): + LOG_FIELD(CVehicleCreationDataNode, m_pop_type); + LOG_FIELD(CVehicleCreationDataNode, m_random_seed); + LOG_FIELD_H(CVehicleCreationDataNode, m_model); + LOG_FIELD(CVehicleCreationDataNode, m_vehicle_status); + LOG_FIELD(CVehicleCreationDataNode, m_max_health); + LOG_FIELD(CVehicleCreationDataNode, m_creation_token); + LOG_FIELD_B(CVehicleCreationDataNode, m_needs_to_be_hotwired); + LOG_FIELD_B(CVehicleCreationDataNode, m_tires_dont_burst); + break; + case sync_node_id("CPedCreationDataNode"): + LOG_FIELD(CPedCreationDataNode, m_pop_type); + LOG_FIELD_H(CPedCreationDataNode, m_model); + LOG_FIELD(CPedCreationDataNode, m_random_seed); + LOG_FIELD(CPedCreationDataNode, m_max_health); + LOG_FIELD_B(CPedCreationDataNode, m_in_vehicle); + LOG_FIELD(CPedCreationDataNode, pad_0xD1[0]); + LOG_FIELD(CPedCreationDataNode, m_vehicle_id); + LOG_FIELD(CPedCreationDataNode, m_vehicle_seat); + LOG_FIELD_B(CPedCreationDataNode, m_has_prop); + LOG_FIELD(CPedCreationDataNode, m_prop_model); + LOG_FIELD_B(CPedCreationDataNode, m_is_standing); + LOG_FIELD_B(CPedCreationDataNode, m_is_respawn_object_id); + LOG_FIELD_B(CPedCreationDataNode, m_is_respawn_flagged_for_removal); + LOG_FIELD_B(CPedCreationDataNode, m_has_attr_damage_to_player); + LOG_FIELD_C(CPedCreationDataNode, m_attribute_damage_to_player); + LOG_FIELD_H(CPedCreationDataNode, m_voice_hash); + break; + case sync_node_id("CObjectCreationDataNode"): + LOG_FIELD(CObjectCreationDataNode, unk_00C0); + LOG_FIELD_V4(CObjectCreationDataNode, m_object_orientation); + LOG_FIELD_V3(CObjectCreationDataNode, m_object_position); + LOG_FIELD_V3(CObjectCreationDataNode, m_dummy_position); + LOG_FIELD_V3(CObjectCreationDataNode, m_script_grab_position); + LOG_FIELD(CObjectCreationDataNode, m_script_grab_radius); + LOG_FIELD(CObjectCreationDataNode, m_created_by); + LOG_FIELD_H(CObjectCreationDataNode, m_model); + LOG_FIELD(CObjectCreationDataNode, m_frag_group_index); + LOG_FIELD(CObjectCreationDataNode, m_ownership_token); + LOG_FIELD(CObjectCreationDataNode, unk_015C); + LOG_FIELD_B(CObjectCreationDataNode, m_no_reassign); + LOG_FIELD_B(CObjectCreationDataNode, unk_0161); + LOG_FIELD_B(CObjectCreationDataNode, m_player_wants_control); + LOG_FIELD_B(CObjectCreationDataNode, m_has_init_physics); + LOG_FIELD_B(CObjectCreationDataNode, m_script_grabbed_from_world); + LOG_FIELD_B(CObjectCreationDataNode, m_has_frag_group); + LOG_FIELD_B(CObjectCreationDataNode, m_is_broken); + LOG_FIELD_B(CObjectCreationDataNode, m_has_exploded); + LOG_FIELD_B(CObjectCreationDataNode, m_keep_registered); + LOG_FIELD_B(CObjectCreationDataNode, m_has_frag_group); + LOG_FIELD_B(CObjectCreationDataNode, unk_0169); + LOG_FIELD_B(CObjectCreationDataNode, unk_016A); + LOG_FIELD_B(CObjectCreationDataNode, unk_016B); + break; + case sync_node_id("CTrainGameStateDataNode"): + LOG_FIELD_B(CTrainGameStateDataNode, m_is_engine); + LOG_FIELD_B(CTrainGameStateDataNode, m_is_caboose); + LOG_FIELD_B(CTrainGameStateDataNode, m_is_mission_train); + LOG_FIELD_B(CTrainGameStateDataNode, m_direction); + LOG_FIELD_B(CTrainGameStateDataNode, m_has_passenger_carriages); + LOG_FIELD_B(CTrainGameStateDataNode, m_render_derailed); + LOG_FIELD_B(CTrainGameStateDataNode, unk_00C6); + LOG_FIELD_B(CTrainGameStateDataNode, unk_00C7); + LOG_FIELD(CTrainGameStateDataNode, m_engine_id); + LOG_FIELD_C(CTrainGameStateDataNode, m_train_config_index); + LOG_FIELD_C(CTrainGameStateDataNode, m_carriage_config_index); + LOG_FIELD_C(CTrainGameStateDataNode, m_track_id); + LOG_FIELD(CTrainGameStateDataNode, m_distance_from_engine); + LOG_FIELD(CTrainGameStateDataNode, m_cruise_speed); + LOG_FIELD(CTrainGameStateDataNode, m_linked_to_backward_id); + LOG_FIELD(CTrainGameStateDataNode, m_linked_to_forward_id); + LOG_FIELD(CTrainGameStateDataNode, m_train_state); + LOG_FIELD_B(CTrainGameStateDataNode, unk_00E0); + LOG_FIELD_B(CTrainGameStateDataNode, m_force_doors_open); + break; + case sync_node_id("CDynamicEntityGameStateDataNode"): + LOG_FIELD(CDynamicEntityGameStateDataNode, m_interior_index); + LOG_FIELD_B(CDynamicEntityGameStateDataNode, unk_00C4); + LOG_FIELD_B(CDynamicEntityGameStateDataNode, unk_00C5); + LOG_FIELD(CDynamicEntityGameStateDataNode, m_decor_count); + for (int i = 0; i < ((CDynamicEntityGameStateDataNode*)node)->m_decor_count; i++) + { + LOG_FIELD(CDynamicEntityGameStateDataNode, m_decors[i].m_type); + LOG_FIELD_H(CDynamicEntityGameStateDataNode, m_decors[i].m_name_hash); + LOG_FIELD(CDynamicEntityGameStateDataNode, m_decors[i].m_value); + } + break; + } + } + bool check_node(rage::netSyncNodeBase* node, CNetGamePlayer* sender, rage::netObject* object) { if (node->IsParentNode()) @@ -748,6 +857,9 @@ namespace big if ((((CProjectBaseSyncDataNode*)node)->flags & 1) == 0) continue; + if (sender_plyr && sender_plyr->log_clones) + log_node(node_id, sender_plyr, (CProjectBaseSyncDataNode*)node); + switch (node_id) { case sync_node_id("CVehicleCreationDataNode"): diff --git a/src/hooks/protections/received_clone_sync.cpp b/src/hooks/protections/received_clone_sync.cpp index 9bf73c9f..16a552c5 100644 --- a/src/hooks/protections/received_clone_sync.cpp +++ b/src/hooks/protections/received_clone_sync.cpp @@ -20,7 +20,7 @@ namespace big auto plyr = g_player_service->get_by_id(src->m_player_id); - if (plyr && plyr->block_clone_create) + if (plyr && plyr->block_clone_sync) return eAckCode::ACKCODE_FAIL; g.m_syncing_player = src; diff --git a/src/hooks/protections/update_presence_attribute.cpp b/src/hooks/protections/update_presence_attribute.cpp index 14f96870..34f2a212 100644 --- a/src/hooks/protections/update_presence_attribute.cpp +++ b/src/hooks/protections/update_presence_attribute.cpp @@ -1,11 +1,17 @@ #include "hooking.hpp" +#include "services/player_database/player_database_service.hpp" namespace big { + inline bool block_session_presence() + { + return g.protections.rid_join || (g_player_database_service && g_player_database_service->is_redirect_join_active()); + } + bool hooks::update_presence_attribute_int(void* presence_data, int profile_index, char* attr, std::uint64_t value) { auto hash = rage::joaat(attr); - if (g.protections.rid_join && (hash == RAGE_JOAAT("gstok") || hash == RAGE_JOAAT("gsid") || hash == RAGE_JOAAT("gstype") || hash == RAGE_JOAAT("gshost") || hash == RAGE_JOAAT("gsjoin"))) + if (block_session_presence() && (hash == RAGE_JOAAT("gstok") || hash == RAGE_JOAAT("gsid") || hash == RAGE_JOAAT("gstype") || hash == RAGE_JOAAT("gshost") || hash == RAGE_JOAAT("gsjoin"))) { return true; } @@ -16,7 +22,7 @@ namespace big bool hooks::update_presence_attribute_string(void* presence_data, int profile_index, char* attr, char* value) { auto hash = rage::joaat(attr); - if (g.protections.rid_join && hash == RAGE_JOAAT("gsinfo")) + if (block_session_presence() && hash == RAGE_JOAAT("gsinfo")) { return true; } diff --git a/src/memory/byte_patch.cpp b/src/memory/byte_patch.cpp index 2a7684e8..cb3a1363 100644 --- a/src/memory/byte_patch.cpp +++ b/src/memory/byte_patch.cpp @@ -9,11 +9,14 @@ namespace memory void byte_patch::apply() const { + VirtualProtect(m_address, m_size, PAGE_EXECUTE_READWRITE, (PDWORD)&m_old_protect); memcpy(m_address, m_value.get(), m_size); } void byte_patch::restore() const { + DWORD temp; + VirtualProtect(m_address, m_size, m_old_protect, &temp); memcpy(m_address, m_original_bytes.get(), m_size); } diff --git a/src/memory/byte_patch.hpp b/src/memory/byte_patch.hpp index 4b5af90b..2af66790 100644 --- a/src/memory/byte_patch.hpp +++ b/src/memory/byte_patch.hpp @@ -4,10 +4,7 @@ namespace memory { template - concept SpanCompatibleType = requires(T a) - { - std::span{a}; - }; + concept SpanCompatibleType = requires(T a) { std::span{a}; }; class byte_patch { @@ -27,7 +24,7 @@ namespace memory } template - requires SpanCompatibleType + requires SpanCompatibleType static const std::unique_ptr& make(TAddr address, T span_compatible) { return m_patches.emplace_back(std::unique_ptr(new byte_patch(address, std::span{span_compatible}))); @@ -71,6 +68,7 @@ namespace memory std::unique_ptr m_value; std::unique_ptr m_original_bytes; std::size_t m_size; + DWORD m_old_protect; friend bool operator==(const std::unique_ptr& a, const byte_patch* b); }; diff --git a/src/native_hooks/all_scripts.hpp b/src/native_hooks/all_scripts.hpp index 523cad1f..603b96dd 100644 --- a/src/native_hooks/all_scripts.hpp +++ b/src/native_hooks/all_scripts.hpp @@ -26,7 +26,7 @@ namespace big void NETWORK_SET_THIS_SCRIPT_IS_NETWORK_SCRIPT(rage::scrNativeCallContext* src) { - if (src->get_arg(2) >= 0x100) + if (src->get_arg(2) != -1 && src->get_arg(2) >= 0x100) { notify::crash_blocked(nullptr, "out of bounds instance id"); return; @@ -37,7 +37,7 @@ namespace big void NETWORK_TRY_TO_SET_THIS_SCRIPT_IS_NETWORK_SCRIPT(rage::scrNativeCallContext* src) { - if (src->get_arg(2) >= 0x100) + if (src->get_arg(2) != -1 && src->get_arg(2) >= 0x100) { notify::crash_blocked(nullptr, "out of bounds instance id"); src->set_return_value(FALSE); diff --git a/src/pointers.cpp b/src/pointers.cpp index 5836727a..52e9f962 100644 --- a/src/pointers.cpp +++ b/src/pointers.cpp @@ -1217,6 +1217,15 @@ namespace big g_pointers->m_gta.m_send_non_physical_player_data = ptr.add(1).rip().as(); } }, + // Presence Data + { + "PD", + "48 8B 0D ? ? ? ? 44 8B 4B 60", + [](memory::handle ptr) + { + g_pointers->m_gta.m_presence_data = ptr.add(3).rip().as(); + } + }, // Max Wanted Level { "MWL", @@ -1382,8 +1391,8 @@ namespace big [](memory::handle ptr) { auto presence_data_vft = ptr.add(3).rip().as(); - g_pointers->m_sc.m_update_presence_attribute_int = presence_data_vft[1]; - g_pointers->m_sc.m_update_presence_attribute_string = presence_data_vft[3]; + g_pointers->m_sc.m_update_presence_attribute_int = (functions::update_presence_attribute_int)presence_data_vft[1]; + g_pointers->m_sc.m_update_presence_attribute_string = (functions::update_presence_attribute_string)presence_data_vft[3]; } }, // Start Get Presence Attributes @@ -1394,6 +1403,16 @@ namespace big { g_pointers->m_sc.m_start_get_presence_attributes = ptr.as(); } + }, + // Read Attribute Patch + { + "RAP", + "75 72 EB 23 80 F9 03", + [](memory::handle ptr) + { + g_pointers->m_sc.m_read_attribute_patch = ptr.as(); + g_pointers->m_sc.m_read_attribute_patch_2 = ptr.add(0x74).as(); + } } >(); diff --git a/src/sc_pointers.hpp b/src/sc_pointers.hpp index 5601ef55..be91e119 100644 --- a/src/sc_pointers.hpp +++ b/src/sc_pointers.hpp @@ -6,10 +6,12 @@ namespace big #pragma pack(push, 1) struct socialclub_pointers { - PVOID m_update_presence_attribute_int; - PVOID m_update_presence_attribute_string; + functions::update_presence_attribute_int m_update_presence_attribute_int; + functions::update_presence_attribute_string m_update_presence_attribute_string; functions::start_get_presence_attributes m_start_get_presence_attributes; + PVOID m_read_attribute_patch; + PVOID m_read_attribute_patch_2; }; #pragma pack(pop) diff --git a/src/services/player_database/persistent_player.hpp b/src/services/player_database/persistent_player.hpp index bd6fde8c..fc0bb9fe 100644 --- a/src/services/player_database/persistent_player.hpp +++ b/src/services/player_database/persistent_player.hpp @@ -36,6 +36,7 @@ namespace nlohmann } enum class GSType : int32_t; +enum class GameMode : int32_t; namespace big { @@ -50,10 +51,22 @@ namespace big std::unordered_set infractions; std::string notes = ""; std::optional command_access_level = std::nullopt; - GSType session_type = GSType(-2); - int64_t session_id = -1; + bool join_redirect = false; + int join_redirect_preference = 1; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(persistent_player, name, rockstar_id, block_join, block_join_reason, is_modder, notify_online, infractions, notes, command_access_level) + // non-persistent tracker info + GSType session_type = GSType(-2); + int64_t session_id = -1; + bool is_spectating = false; + bool is_host_of_session = false; + int64_t transition_session_id = -1; + bool is_host_of_transition_session = false; + GameMode game_mode = GameMode(-1); + std::string game_mode_name = "Unknown"; + std::string game_mode_id = ""; + rage::rlSessionInfo redirect_info{}; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(persistent_player, name, rockstar_id, block_join, block_join_reason, is_modder, notify_online, infractions, notes, command_access_level, join_redirect, join_redirect_preference) }; }; diff --git a/src/services/player_database/player_database_service.cpp b/src/services/player_database/player_database_service.cpp index 4f52fb66..0d7485fa 100644 --- a/src/services/player_database/player_database_service.cpp +++ b/src/services/player_database/player_database_service.cpp @@ -3,14 +3,31 @@ #include "backend/bool_command.hpp" #include "file_manager.hpp" #include "gta/enums.hpp" +#include "hooking.hpp" #include "pointers.hpp" #include "util/session.hpp" namespace big { - bool_command g_player_db_auto_update_online_states("player_db_auto_update_states", "Auto Update Tracked Player States", "Toggling this feature will automatically update the tracked players' online states every minute", + bool_command g_player_db_auto_update_online_states("player_db_auto_update_states", "Auto Update Tracked Player States", "Toggling this feature will automatically update the tracked players' online states every minute. You must enable this for join redirect to work", g.player_db.update_player_online_states); + const char* player_database_service::get_name_by_content_id(const std::string& content_id) + { + if (NETWORK::UGC_QUERY_BY_CONTENT_ID(content_id.c_str(), false, "gta5mission")) + { + while (NETWORK::UGC_IS_GETTING()) + script::get_current()->yield(); + + if (!NETWORK::UGC_DID_GET_SUCCEED()) + return ""; + + return NETWORK::UGC_GET_CONTENT_NAME(0); + } + + return ""; + } + void player_database_service::handle_session_type_change(persistent_player& player, GSType new_session_type) { if (!player.notify_online) @@ -39,6 +56,84 @@ namespace big } } + void player_database_service::handle_game_mode_change(std::uint64_t rid, GameMode old_game_mode, GameMode new_game_mode, std::string mission_id, std::string mission_name) + { + const char* old_game_mode_str = get_game_mode_str(old_game_mode); + const char* new_game_mode_str = get_game_mode_str(new_game_mode); + auto player = g_player_database_service->get_player_by_rockstar_id(rid); + + if (new_game_mode == GameMode::None && old_game_mode != GameMode::None && old_game_mode_str != "None") + { + g_notification_service->push("Player DB", std::format("{} is no longer in a {}", player->name, old_game_mode_str)); + return; + } + + if (!can_fetch_name(new_game_mode)) + { + if (new_game_mode_str != "None") + g_notification_service->push("Player DB", std::format("{} is now in a {}", player->name, new_game_mode_str)); + + return; + } + + if (mission_name.empty()) + { + mission_name = get_name_by_content_id(mission_id); + } + + if (mission_name.empty()) + { + g_notification_service->push("Player DB", std::format("{} is now in a {}", player->name, new_game_mode_str)); + return; + } + + g_notification_service->push("Player DB", std::format("{} has joined the {} \"{}\"", player->name, new_game_mode_str, mission_name)); + player->game_mode_name = mission_name; + } + + void player_database_service::handle_join_redirect() + { + if (!*g_pointers->m_gta.m_presence_data) + return; + + int current_preference_level = 0; + rage::rlSessionInfo preferred_session{}; + + for (auto& player : m_players) + { + if (player.second->join_redirect && is_joinable_session(player.second->session_type) + && current_preference_level < player.second->join_redirect_preference) + { + current_preference_level = player.second->join_redirect_preference; + preferred_session = player.second->redirect_info; + } + } + + if (current_preference_level != 0) + { + join_being_redirected = true; + char buf[0x100]{}; + g_pointers->m_gta.m_encode_session_info(&preferred_session, buf, 0xA9, nullptr); + + g_hooking->get_original()(*g_pointers->m_gta.m_presence_data, 0, (char*)"gsinfo", buf); + g_hooking->get_original()(*g_pointers->m_gta.m_presence_data, + 0, + (char*)"gstok", + preferred_session.m_session_token); + g_hooking->get_original()(*g_pointers->m_gta.m_presence_data, + 0, + (char*)"gsid", + preferred_session.m_unk); + g_hooking->get_original()(*g_pointers->m_gta.m_presence_data, 0, (char*)"gstype", 5); + g_hooking->get_original()(*g_pointers->m_gta.m_presence_data, 0, (char*)"gshost", 0); + g_hooking->get_original()(*g_pointers->m_gta.m_presence_data, 0, (char*)"gsjoin", 1); + } + else + { + join_being_redirected = false; + } + } + player_database_service::player_database_service() : m_file_path(g_file_manager.get_project_file("./players.json").get_path()) { @@ -183,12 +278,14 @@ namespace big while (g_running && g.player_db.update_player_online_states) { const auto cur = std::chrono::high_resolution_clock::now(); - if (cur - last_update > 45s) + if (cur - last_update > 45s && !updating) { + updating = true; g_fiber_pool->queue_job([this] { update_player_states(true); + updating = false; + last_update = std::chrono::high_resolution_clock::now(); }); - last_update = cur; } std::this_thread::sleep_for(1s); @@ -198,17 +295,21 @@ namespace big void player_database_service::update_player_states(bool tracked_only) { - const auto player_count = m_players.size(); constexpr auto bucket_size = 100; - std::vector> gamer_handle_buckets; - gamer_handle_buckets.resize(std::ceil(player_count / (float)bucket_size)); + std::vector> gamer_handle_buckets{}; size_t i = 0; for (auto& player : m_players) { - if (!tracked_only || player.second->notify_online) + if (!tracked_only || (player.second->notify_online || player.second->join_redirect)) { + if (gamer_handle_buckets.size() <= i / bucket_size) + gamer_handle_buckets.push_back({}); + + if (player.second->rockstar_id == 0 || ((int64_t)player.second->rockstar_id) < 0) + continue; + gamer_handle_buckets[i / bucket_size].push_back(player.second->rockstar_id); i++; } @@ -220,20 +321,35 @@ namespace big for (auto& bucket : gamer_handle_buckets) { rage::rlTaskStatus status{}; - rage::rlQueryPresenceAttributesContext contexts[bucket_size][2]{}; + + rage::rlQueryPresenceAttributesContext contexts[bucket_size][9]{}; rage::rlQueryPresenceAttributesContext* contexts_per_player[bucket_size]{}; - for (int i = 0; i < bucket_size; i++) + for (int i = 0; i < bucket.size(); i++) { - contexts[i][0].m_presence_attibute_type = 3; + contexts[i][0].m_presence_attibute_type = 1; strcpy(contexts[i][0].m_presence_attribute_key, "gstype"); - strcpy(contexts[i][0].m_presence_attribute_value, "-1"); - contexts[i][1].m_presence_attibute_type = 3; + contexts[i][0].m_presence_attribute_int_value = -1; + contexts[i][1].m_presence_attibute_type = 3; strcpy(contexts[i][1].m_presence_attribute_key, "gsinfo"); + contexts[i][2].m_presence_attibute_type = 1; + strcpy(contexts[i][2].m_presence_attribute_key, "sctv"); + contexts[i][3].m_presence_attibute_type = 1; + strcpy(contexts[i][3].m_presence_attribute_key, "gshost"); + contexts[i][4].m_presence_attibute_type = 3; + strcpy(contexts[i][4].m_presence_attribute_key, "trinfo"); + contexts[i][5].m_presence_attibute_type = 1; + strcpy(contexts[i][5].m_presence_attribute_key, "trhost"); + contexts[i][6].m_presence_attibute_type = 3; + strcpy(contexts[i][6].m_presence_attribute_key, "mp_mis_str"); + contexts[i][7].m_presence_attibute_type = 3; + strcpy(contexts[i][7].m_presence_attribute_key, "mp_mis_id"); + contexts[i][8].m_presence_attibute_type = 1; + strcpy(contexts[i][8].m_presence_attribute_key, "mp_curr_gamemode"); contexts_per_player[i] = contexts[i]; } - if (g_pointers->m_sc.m_start_get_presence_attributes(0, bucket.data(), bucket.size(), contexts_per_player, 2, &status)) + if (g_pointers->m_sc.m_start_get_presence_attributes(0, bucket.data(), bucket.size(), contexts_per_player, 9, &status)) { while (status.status == 1) { @@ -247,26 +363,110 @@ namespace big if (const auto& it = m_players.find(bucket[i].m_rockstar_id); it != m_players.end()) { rage::rlSessionInfo info{}; - info.m_session_token = -1; - GSType gstype = (GSType)atoi(contexts[i][0].m_presence_attribute_value); + rage::rlSessionInfo transition_info{}; + info.m_session_token = -1; + transition_info.m_session_token = -1; + GSType gstype = (GSType)(int)contexts[i][0].m_presence_attribute_int_value; + bool is_spectating = (bool)contexts[i][2].m_presence_attribute_int_value; + bool is_host_of_session = (bool)contexts[i][3].m_presence_attribute_int_value; + bool is_host_of_transition_session = (bool)contexts[i][5].m_presence_attribute_int_value; + GameMode game_mode = (GameMode)contexts[i][8].m_presence_attribute_int_value; + std::string mission_id = contexts[i][7].m_presence_attribute_string_value; + std::string mission_name = contexts[i][6].m_presence_attribute_string_value; - if (!g_pointers->m_gta.m_decode_session_info(&info, contexts[i][1].m_presence_attribute_value, nullptr)) + if (contexts[i][1].m_presence_attribute_string_value[0] == 0 + || !g_pointers->m_gta.m_decode_session_info(&info, contexts[i][1].m_presence_attribute_string_value, nullptr)) gstype = GSType::Invalid; + if (can_fetch_name(game_mode) && mission_name.empty() && mission_id.empty()) + game_mode = GameMode::None; + + if (contexts[i][4].m_presence_attribute_string_value[0] == 0 + || !g_pointers->m_gta.m_decode_session_info(&transition_info, contexts[i][4].m_presence_attribute_string_value, nullptr)) + transition_info.m_session_token = -1; + if (it->second->session_type != gstype) { handle_session_type_change(*it->second, gstype); } - else if (it->second->notify_online && it->second->session_id != info.m_session_token) + else if (it->second->notify_online && it->second->session_id != info.m_session_token + && g.player_db.notify_on_session_change) { g_notification_service->push("Player DB", std::format("{} has joined a new session", it->second->name)); } - it->second->session_type = gstype; - it->second->session_id = info.m_session_token; + if (gstype != GSType::Invalid) + { + if (it->second->notify_online && is_spectating != it->second->is_spectating + && g.player_db.notify_on_spectator_change) + { + if (is_spectating) + { + g_notification_service->push("Player DB", + std::format("{} is now spectating", it->second->name)); + } + else + { + g_notification_service->push("Player DB", + std::format("{} is no longer spectating", it->second->name)); + } + } + + if (it->second->notify_online && is_host_of_session != it->second->is_host_of_session + && g.player_db.notify_on_become_host && is_host_of_session && it->second->session_id == info.m_session_token) + { + g_notification_service->push("Player DB", + std::format("{} is now the host of their session", it->second->name)); + } + + if (it->second->notify_online && g.player_db.notify_on_transition_change + && transition_info.m_session_token != -1 && it->second->transition_session_id == -1) + { + if (is_host_of_transition_session) + { + g_notification_service->push("Player DB", + std::format("{} has hosted a job lobby", it->second->name)); + } + else + { + g_notification_service->push("Player DB", + std::format("{} has joined a job lobby", it->second->name)); + } + } + else if (it->second->notify_online && g.player_db.notify_on_transition_change + && transition_info.m_session_token == -1 && it->second->transition_session_id != -1) + { + g_notification_service->push("Player DB", + std::format("{} is no longer in a job lobby", it->second->name)); + } + + if (it->second->notify_online && g.player_db.notify_on_mission_change + && game_mode != it->second->game_mode) + { + auto rid = it->second->rockstar_id; + auto old_game_mode = it->second->game_mode; + g_fiber_pool->queue_job([rid, old_game_mode, game_mode, mission_id, mission_name] { + handle_game_mode_change(rid, old_game_mode, game_mode, mission_id, mission_name); + }); + } + } + + if (it->second->join_redirect) + it->second->redirect_info = info; + + it->second->session_type = gstype; + it->second->session_id = info.m_session_token; + it->second->is_spectating = is_spectating; + it->second->is_host_of_session = is_host_of_session; + it->second->transition_session_id = transition_info.m_session_token; + it->second->is_host_of_transition_session = is_host_of_transition_session; + it->second->game_mode = game_mode; + it->second->game_mode_id = mission_id; + it->second->game_mode_name = mission_name; } } + handle_join_redirect(); } else { @@ -297,4 +497,35 @@ namespace big return "Unknown"; } + + const char* player_database_service::get_game_mode_str(GameMode mode) + { + switch (mode) + { + case GameMode::None: return "None"; + case GameMode::Mission: return "Mission"; + case GameMode::Deathmatch: return "Deathmatch"; + case GameMode::Race: return "Race"; + case GameMode::Survival: return "Survival"; + case GameMode::GangAttack: return "Gang Attack"; + case GameMode::Golf: return "Golf"; + case GameMode::Tennis: return "Tennis"; + case GameMode::ShootingRange: return "Shooting Range"; + } + + return "Unknown"; + } + + bool player_database_service::can_fetch_name(GameMode mode) + { + switch (mode) + { + case GameMode::Mission: + case GameMode::Deathmatch: + case GameMode::Race: + case GameMode::Survival: return true; + } + + return false; + } } \ No newline at end of file diff --git a/src/services/player_database/player_database_service.hpp b/src/services/player_database/player_database_service.hpp index e248f89d..dc0e6bf7 100644 --- a/src/services/player_database/player_database_service.hpp +++ b/src/services/player_database/player_database_service.hpp @@ -29,6 +29,10 @@ namespace big std::shared_ptr m_selected = nullptr; void handle_session_type_change(persistent_player& player, GSType new_session_type); + static void handle_game_mode_change(std::uint64_t rid, GameMode old_game_mode, GameMode new_game_mode, std::string mission_id, std::string mission_name); // run in fiber pool + bool join_being_redirected = false; + void handle_join_redirect(); + std::atomic_bool updating = false; public: std::filesystem::path m_file_path; @@ -54,6 +58,13 @@ namespace big static bool is_joinable_session(GSType type); static const char* get_session_type_str(GSType type); + static const char* get_game_mode_str(GameMode mode); + static bool can_fetch_name(GameMode mode); + static const char* get_name_by_content_id(const std::string& content_id); + inline bool is_redirect_join_active() + { + return join_being_redirected; + } }; inline player_database_service* g_player_database_service; diff --git a/src/util/session.hpp b/src/util/session.hpp index d9a73172..5a3cb54b 100644 --- a/src/util/session.hpp +++ b/src/util/session.hpp @@ -58,6 +58,10 @@ namespace big::session { *scr_globals::transition_state.as() = eTransitionState::TRANSITION_STATE_LOOK_TO_JOIN_ANOTHER_SESSION_FM; } + else if (session == eSessionType::LEAVE_ONLINE) + { + *scr_globals::transition_state.as() = eTransitionState::TRANSITION_STATE_RETURN_TO_SINGLEPLAYER; + } scr_functions::reset_session_data({true, true}); *script_global(32284).as() = 0; diff --git a/src/views/network/view_player_database.cpp b/src/views/network/view_player_database.cpp index 01fccf0a..cfe8b85c 100644 --- a/src/views/network/view_player_database.cpp +++ b/src/views/network/view_player_database.cpp @@ -190,6 +190,15 @@ namespace big notes_dirty = true; } + ImGui::Checkbox("Join Redirect", ¤t_player->join_redirect); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Anyone trying to join you will join this player instead if they are active. The preference slider will control redirect priority if multiple players with join redirect are active"); + + if (current_player->join_redirect) + { + ImGui::SliderInt("Preference", ¤t_player->join_redirect_preference, 1, 10); + } + components::button("JOIN_SESSION"_T, [] { session::join_by_rockstar_id(current_player->rockstar_id); }); @@ -208,6 +217,30 @@ namespace big }); }; + ImGui::Text("Session Type: %s", player_database_service::get_session_type_str(selected->session_type)); + + if (selected->session_type != GSType::Invalid && selected->session_type != GSType::Unknown) + { + ImGui::Text("Is Host Of Session: %s", selected->is_host_of_session ? "Yes" : "No"); + ImGui::Text("Is Spectating: %s", selected->is_spectating ? "Yes" : "No"); + ImGui::Text("In Job Lobby: %s", selected->transition_session_id != -1 ? "Yes" : "No"); + ImGui::Text("Is Host Of Job Loby: %s", selected->is_host_of_transition_session ? "Yes" : "No"); + ImGui::Text("Current Mission Type: %s", player_database_service::get_game_mode_str(selected->game_mode)); + if (selected->game_mode != GameMode::None && player_database_service::can_fetch_name(selected->game_mode)) + { + ImGui::Text("Current Mission Name: %s", selected->game_mode_name.c_str()); + if ((selected->game_mode_name == "Unknown" || selected->game_mode_name.empty()) + && !selected->game_mode_id.empty()) + { + ImGui::SameLine(); + components::button("Fetch", [] { + current_player->game_mode_name = + player_database_service::get_name_by_content_id(current_player->game_mode_id); + }); + } + } + } + if (ImGui::Button("SAVE"_T.data())) { if (current_player->rockstar_id != selected->rockstar_id) @@ -269,6 +302,10 @@ namespace big ImGui::Checkbox("Notify When Offline", &g.player_db.notify_when_offline); ImGui::Checkbox("Notify On Session Type Change", &g.player_db.notify_on_session_type_change); ImGui::Checkbox("Notify On Session Change", &g.player_db.notify_on_session_change); + ImGui::Checkbox("Notify On Spectator Change", &g.player_db.notify_on_spectator_change); + ImGui::Checkbox("Notify On Become Host", &g.player_db.notify_on_become_host); + ImGui::Checkbox("Notify On Job Lobby Change", &g.player_db.notify_on_transition_change); + ImGui::Checkbox("Notify On Mission Change", &g.player_db.notify_on_mission_change); ImGui::TreePop(); }