mirror of
https://github.com/Mr-X-GTA/YimMenu.git
synced 2025-01-03 16:13:36 +08:00
More player database improvements (#1725)
This commit is contained in:
parent
a40b4a0dd8
commit
973121076e
@ -3,7 +3,7 @@ include(FetchContent)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
gtav_classes
|
gtav_classes
|
||||||
GIT_REPOSITORY https://github.com/Yimura/GTAV-Classes.git
|
GIT_REPOSITORY https://github.com/Yimura/GTAV-Classes.git
|
||||||
GIT_TAG d8304d3e608dac2c22754962420c19f6e74b2c47
|
GIT_TAG 95dadd6e7ee7bfa15d66bb2c597d13e97c631727
|
||||||
GIT_PROGRESS TRUE
|
GIT_PROGRESS TRUE
|
||||||
CONFIGURE_COMMAND ""
|
CONFIGURE_COMMAND ""
|
||||||
BUILD_COMMAND ""
|
BUILD_COMMAND ""
|
||||||
|
@ -73,6 +73,10 @@ namespace big
|
|||||||
|
|
||||||
// Patch script network check
|
// 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
|
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()
|
byte_patch_manager::byte_patch_manager()
|
||||||
|
@ -237,8 +237,12 @@ namespace big
|
|||||||
bool notify_when_offline = false;
|
bool notify_when_offline = false;
|
||||||
bool notify_on_session_type_change = false;
|
bool notify_on_session_type_change = false;
|
||||||
bool notify_on_session_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{};
|
} player_db{};
|
||||||
|
|
||||||
struct protections
|
struct protections
|
||||||
|
@ -149,4 +149,7 @@ namespace big::functions
|
|||||||
using get_host_array_handler_by_index = rage::netArrayHandlerBase* (*)(CGameScriptHandlerNetComponent* component, int index);
|
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 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);
|
||||||
}
|
}
|
||||||
|
@ -2020,3 +2020,16 @@ enum class GSType : int32_t
|
|||||||
Max,
|
Max,
|
||||||
Modder = 69 // stand?
|
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
|
||||||
|
};
|
@ -272,6 +272,8 @@ namespace big
|
|||||||
functions::get_title_caption_error_message_box m_get_title_caption_error_message_box{};
|
functions::get_title_caption_error_message_box m_get_title_caption_error_message_box{};
|
||||||
|
|
||||||
PVOID m_send_non_physical_player_data;
|
PVOID m_send_non_physical_player_data;
|
||||||
|
|
||||||
|
void** m_presence_data{};
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
static_assert(sizeof(gta_pointers) % 8 == 0, "Pointers are not properly aligned");
|
static_assert(sizeof(gta_pointers) % 8 == 0, "Pointers are not properly aligned");
|
||||||
|
@ -20,7 +20,7 @@ namespace big
|
|||||||
|
|
||||||
auto plyr = g_player_service->get_by_id(src->m_player_id);
|
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;
|
return eAckCode::ACKCODE_FAIL;
|
||||||
|
|
||||||
g.m_syncing_player = src;
|
g.m_syncing_player = src;
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
#include "hooking.hpp"
|
#include "hooking.hpp"
|
||||||
|
#include "services/player_database/player_database_service.hpp"
|
||||||
|
|
||||||
namespace big
|
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)
|
bool hooks::update_presence_attribute_int(void* presence_data, int profile_index, char* attr, std::uint64_t value)
|
||||||
{
|
{
|
||||||
auto hash = rage::joaat(attr);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@ -16,7 +22,7 @@ namespace big
|
|||||||
bool hooks::update_presence_attribute_string(void* presence_data, int profile_index, char* attr, char* value)
|
bool hooks::update_presence_attribute_string(void* presence_data, int profile_index, char* attr, char* value)
|
||||||
{
|
{
|
||||||
auto hash = rage::joaat(attr);
|
auto hash = rage::joaat(attr);
|
||||||
if (g.protections.rid_join && hash == RAGE_JOAAT("gsinfo"))
|
if (block_session_presence() && hash == RAGE_JOAAT("gsinfo"))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,14 @@ namespace memory
|
|||||||
|
|
||||||
void byte_patch::apply() const
|
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);
|
memcpy(m_address, m_value.get(), m_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void byte_patch::restore() const
|
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);
|
memcpy(m_address, m_original_bytes.get(), m_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,7 @@
|
|||||||
namespace memory
|
namespace memory
|
||||||
{
|
{
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept SpanCompatibleType = requires(T a)
|
concept SpanCompatibleType = requires(T a) { std::span{a}; };
|
||||||
{
|
|
||||||
std::span{a};
|
|
||||||
};
|
|
||||||
|
|
||||||
class byte_patch
|
class byte_patch
|
||||||
{
|
{
|
||||||
@ -27,7 +24,7 @@ namespace memory
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename TAddr, typename T>
|
template<typename TAddr, typename T>
|
||||||
requires SpanCompatibleType<T>
|
requires SpanCompatibleType<T>
|
||||||
static const std::unique_ptr<byte_patch>& make(TAddr address, T span_compatible)
|
static const std::unique_ptr<byte_patch>& make(TAddr address, T span_compatible)
|
||||||
{
|
{
|
||||||
return m_patches.emplace_back(std::unique_ptr<byte_patch>(new byte_patch(address, std::span{span_compatible})));
|
return m_patches.emplace_back(std::unique_ptr<byte_patch>(new byte_patch(address, std::span{span_compatible})));
|
||||||
@ -71,6 +68,7 @@ namespace memory
|
|||||||
std::unique_ptr<byte[]> m_value;
|
std::unique_ptr<byte[]> m_value;
|
||||||
std::unique_ptr<byte[]> m_original_bytes;
|
std::unique_ptr<byte[]> m_original_bytes;
|
||||||
std::size_t m_size;
|
std::size_t m_size;
|
||||||
|
DWORD m_old_protect;
|
||||||
|
|
||||||
friend bool operator==(const std::unique_ptr<byte_patch>& a, const byte_patch* b);
|
friend bool operator==(const std::unique_ptr<byte_patch>& a, const byte_patch* b);
|
||||||
};
|
};
|
||||||
|
@ -26,7 +26,7 @@ namespace big
|
|||||||
|
|
||||||
void NETWORK_SET_THIS_SCRIPT_IS_NETWORK_SCRIPT(rage::scrNativeCallContext* src)
|
void NETWORK_SET_THIS_SCRIPT_IS_NETWORK_SCRIPT(rage::scrNativeCallContext* src)
|
||||||
{
|
{
|
||||||
if (src->get_arg<int>(2) >= 0x100)
|
if (src->get_arg<int>(2) != -1 && src->get_arg<uint32_t>(2) >= 0x100)
|
||||||
{
|
{
|
||||||
notify::crash_blocked(nullptr, "out of bounds instance id");
|
notify::crash_blocked(nullptr, "out of bounds instance id");
|
||||||
return;
|
return;
|
||||||
@ -37,7 +37,7 @@ namespace big
|
|||||||
|
|
||||||
void NETWORK_TRY_TO_SET_THIS_SCRIPT_IS_NETWORK_SCRIPT(rage::scrNativeCallContext* src)
|
void NETWORK_TRY_TO_SET_THIS_SCRIPT_IS_NETWORK_SCRIPT(rage::scrNativeCallContext* src)
|
||||||
{
|
{
|
||||||
if (src->get_arg<int>(2) >= 0x100)
|
if (src->get_arg<int>(2) != -1 && src->get_arg<uint32_t>(2) >= 0x100)
|
||||||
{
|
{
|
||||||
notify::crash_blocked(nullptr, "out of bounds instance id");
|
notify::crash_blocked(nullptr, "out of bounds instance id");
|
||||||
src->set_return_value<BOOL>(FALSE);
|
src->set_return_value<BOOL>(FALSE);
|
||||||
|
@ -1217,6 +1217,15 @@ namespace big
|
|||||||
g_pointers->m_gta.m_send_non_physical_player_data = ptr.add(1).rip().as<PVOID>();
|
g_pointers->m_gta.m_send_non_physical_player_data = ptr.add(1).rip().as<PVOID>();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 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<void**>();
|
||||||
|
}
|
||||||
|
},
|
||||||
// Max Wanted Level
|
// Max Wanted Level
|
||||||
{
|
{
|
||||||
"MWL",
|
"MWL",
|
||||||
@ -1382,8 +1391,8 @@ namespace big
|
|||||||
[](memory::handle ptr)
|
[](memory::handle ptr)
|
||||||
{
|
{
|
||||||
auto presence_data_vft = ptr.add(3).rip().as<PVOID*>();
|
auto presence_data_vft = ptr.add(3).rip().as<PVOID*>();
|
||||||
g_pointers->m_sc.m_update_presence_attribute_int = presence_data_vft[1];
|
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 = presence_data_vft[3];
|
g_pointers->m_sc.m_update_presence_attribute_string = (functions::update_presence_attribute_string)presence_data_vft[3];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Start Get Presence Attributes
|
// Start Get Presence Attributes
|
||||||
@ -1394,6 +1403,16 @@ namespace big
|
|||||||
{
|
{
|
||||||
g_pointers->m_sc.m_start_get_presence_attributes = ptr.as<functions::start_get_presence_attributes>();
|
g_pointers->m_sc.m_start_get_presence_attributes = ptr.as<functions::start_get_presence_attributes>();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Read Attribute Patch
|
||||||
|
{
|
||||||
|
"RAP",
|
||||||
|
"75 72 EB 23 80 F9 03",
|
||||||
|
[](memory::handle ptr)
|
||||||
|
{
|
||||||
|
g_pointers->m_sc.m_read_attribute_patch = ptr.as<PVOID>();
|
||||||
|
g_pointers->m_sc.m_read_attribute_patch_2 = ptr.add(0x74).as<PVOID>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
@ -6,10 +6,12 @@ namespace big
|
|||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct socialclub_pointers
|
struct socialclub_pointers
|
||||||
{
|
{
|
||||||
PVOID m_update_presence_attribute_int;
|
functions::update_presence_attribute_int m_update_presence_attribute_int;
|
||||||
PVOID m_update_presence_attribute_string;
|
functions::update_presence_attribute_string m_update_presence_attribute_string;
|
||||||
|
|
||||||
functions::start_get_presence_attributes m_start_get_presence_attributes;
|
functions::start_get_presence_attributes m_start_get_presence_attributes;
|
||||||
|
PVOID m_read_attribute_patch;
|
||||||
|
PVOID m_read_attribute_patch_2;
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ namespace nlohmann
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class GSType : int32_t;
|
enum class GSType : int32_t;
|
||||||
|
enum class GameMode : int32_t;
|
||||||
|
|
||||||
namespace big
|
namespace big
|
||||||
{
|
{
|
||||||
@ -50,10 +51,22 @@ namespace big
|
|||||||
std::unordered_set<int> infractions;
|
std::unordered_set<int> infractions;
|
||||||
std::string notes = "";
|
std::string notes = "";
|
||||||
std::optional<CommandAccessLevel> command_access_level = std::nullopt;
|
std::optional<CommandAccessLevel> command_access_level = std::nullopt;
|
||||||
GSType session_type = GSType(-2);
|
bool join_redirect = false;
|
||||||
int64_t session_id = -1;
|
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)
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -3,14 +3,31 @@
|
|||||||
#include "backend/bool_command.hpp"
|
#include "backend/bool_command.hpp"
|
||||||
#include "file_manager.hpp"
|
#include "file_manager.hpp"
|
||||||
#include "gta/enums.hpp"
|
#include "gta/enums.hpp"
|
||||||
|
#include "hooking.hpp"
|
||||||
#include "pointers.hpp"
|
#include "pointers.hpp"
|
||||||
#include "util/session.hpp"
|
#include "util/session.hpp"
|
||||||
|
|
||||||
namespace big
|
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);
|
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)
|
void player_database_service::handle_session_type_change(persistent_player& player, GSType new_session_type)
|
||||||
{
|
{
|
||||||
if (!player.notify_online)
|
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<hooks::update_presence_attribute_string>()(*g_pointers->m_gta.m_presence_data, 0, (char*)"gsinfo", buf);
|
||||||
|
g_hooking->get_original<hooks::update_presence_attribute_int>()(*g_pointers->m_gta.m_presence_data,
|
||||||
|
0,
|
||||||
|
(char*)"gstok",
|
||||||
|
preferred_session.m_session_token);
|
||||||
|
g_hooking->get_original<hooks::update_presence_attribute_int>()(*g_pointers->m_gta.m_presence_data,
|
||||||
|
0,
|
||||||
|
(char*)"gsid",
|
||||||
|
preferred_session.m_unk);
|
||||||
|
g_hooking->get_original<hooks::update_presence_attribute_int>()(*g_pointers->m_gta.m_presence_data, 0, (char*)"gstype", 5);
|
||||||
|
g_hooking->get_original<hooks::update_presence_attribute_int>()(*g_pointers->m_gta.m_presence_data, 0, (char*)"gshost", 0);
|
||||||
|
g_hooking->get_original<hooks::update_presence_attribute_int>()(*g_pointers->m_gta.m_presence_data, 0, (char*)"gsjoin", 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
join_being_redirected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
player_database_service::player_database_service() :
|
player_database_service::player_database_service() :
|
||||||
m_file_path(g_file_manager.get_project_file("./players.json").get_path())
|
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)
|
while (g_running && g.player_db.update_player_online_states)
|
||||||
{
|
{
|
||||||
const auto cur = std::chrono::high_resolution_clock::now();
|
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] {
|
g_fiber_pool->queue_job([this] {
|
||||||
update_player_states(true);
|
update_player_states(true);
|
||||||
|
updating = false;
|
||||||
|
last_update = std::chrono::high_resolution_clock::now();
|
||||||
});
|
});
|
||||||
last_update = cur;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::this_thread::sleep_for(1s);
|
std::this_thread::sleep_for(1s);
|
||||||
@ -198,17 +295,21 @@ namespace big
|
|||||||
|
|
||||||
void player_database_service::update_player_states(bool tracked_only)
|
void player_database_service::update_player_states(bool tracked_only)
|
||||||
{
|
{
|
||||||
const auto player_count = m_players.size();
|
|
||||||
constexpr auto bucket_size = 100;
|
constexpr auto bucket_size = 100;
|
||||||
|
|
||||||
std::vector<std::vector<rage::rlScHandle>> gamer_handle_buckets;
|
std::vector<std::vector<rage::rlScHandle>> gamer_handle_buckets{};
|
||||||
gamer_handle_buckets.resize(std::ceil(player_count / (float)bucket_size));
|
|
||||||
|
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (auto& player : m_players)
|
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);
|
gamer_handle_buckets[i / bucket_size].push_back(player.second->rockstar_id);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
@ -220,20 +321,35 @@ namespace big
|
|||||||
for (auto& bucket : gamer_handle_buckets)
|
for (auto& bucket : gamer_handle_buckets)
|
||||||
{
|
{
|
||||||
rage::rlTaskStatus status{};
|
rage::rlTaskStatus status{};
|
||||||
rage::rlQueryPresenceAttributesContext contexts[bucket_size][2]{};
|
|
||||||
|
rage::rlQueryPresenceAttributesContext contexts[bucket_size][9]{};
|
||||||
rage::rlQueryPresenceAttributesContext* contexts_per_player[bucket_size]{};
|
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_key, "gstype");
|
||||||
strcpy(contexts[i][0].m_presence_attribute_value, "-1");
|
contexts[i][0].m_presence_attribute_int_value = -1;
|
||||||
contexts[i][1].m_presence_attibute_type = 3;
|
contexts[i][1].m_presence_attibute_type = 3;
|
||||||
strcpy(contexts[i][1].m_presence_attribute_key, "gsinfo");
|
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];
|
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)
|
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())
|
if (const auto& it = m_players.find(bucket[i].m_rockstar_id); it != m_players.end())
|
||||||
{
|
{
|
||||||
rage::rlSessionInfo info{};
|
rage::rlSessionInfo info{};
|
||||||
info.m_session_token = -1;
|
rage::rlSessionInfo transition_info{};
|
||||||
GSType gstype = (GSType)atoi(contexts[i][0].m_presence_attribute_value);
|
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;
|
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)
|
if (it->second->session_type != gstype)
|
||||||
{
|
{
|
||||||
handle_session_type_change(*it->second, 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",
|
g_notification_service->push("Player DB",
|
||||||
std::format("{} has joined a new session", it->second->name));
|
std::format("{} has joined a new session", it->second->name));
|
||||||
}
|
}
|
||||||
|
|
||||||
it->second->session_type = gstype;
|
if (gstype != GSType::Invalid)
|
||||||
it->second->session_id = info.m_session_token;
|
{
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
@ -297,4 +497,35 @@ namespace big
|
|||||||
|
|
||||||
return "Unknown";
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -29,6 +29,10 @@ namespace big
|
|||||||
std::shared_ptr<persistent_player> m_selected = nullptr;
|
std::shared_ptr<persistent_player> m_selected = nullptr;
|
||||||
|
|
||||||
void handle_session_type_change(persistent_player& player, GSType new_session_type);
|
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:
|
public:
|
||||||
std::filesystem::path m_file_path;
|
std::filesystem::path m_file_path;
|
||||||
@ -54,6 +58,13 @@ namespace big
|
|||||||
|
|
||||||
static bool is_joinable_session(GSType type);
|
static bool is_joinable_session(GSType type);
|
||||||
static const char* get_session_type_str(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;
|
inline player_database_service* g_player_database_service;
|
||||||
|
@ -58,6 +58,10 @@ namespace big::session
|
|||||||
{
|
{
|
||||||
*scr_globals::transition_state.as<eTransitionState*>() = eTransitionState::TRANSITION_STATE_LOOK_TO_JOIN_ANOTHER_SESSION_FM;
|
*scr_globals::transition_state.as<eTransitionState*>() = eTransitionState::TRANSITION_STATE_LOOK_TO_JOIN_ANOTHER_SESSION_FM;
|
||||||
}
|
}
|
||||||
|
else if (session == eSessionType::LEAVE_ONLINE)
|
||||||
|
{
|
||||||
|
*scr_globals::transition_state.as<eTransitionState*>() = eTransitionState::TRANSITION_STATE_RETURN_TO_SINGLEPLAYER;
|
||||||
|
}
|
||||||
|
|
||||||
scr_functions::reset_session_data({true, true});
|
scr_functions::reset_session_data({true, true});
|
||||||
*script_global(32284).as<int*>() = 0;
|
*script_global(32284).as<int*>() = 0;
|
||||||
|
@ -190,6 +190,15 @@ namespace big
|
|||||||
notes_dirty = true;
|
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, [] {
|
components::button("JOIN_SESSION"_T, [] {
|
||||||
session::join_by_rockstar_id(current_player->rockstar_id);
|
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 (ImGui::Button("SAVE"_T.data()))
|
||||||
{
|
{
|
||||||
if (current_player->rockstar_id != selected->rockstar_id)
|
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 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 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 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();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user