diff --git a/src/core/hooking/Hooking.cpp b/src/core/hooking/Hooking.cpp index f1967fd5..6f0b5709 100644 --- a/src/core/hooking/Hooking.cpp +++ b/src/core/hooking/Hooking.cpp @@ -47,6 +47,8 @@ namespace YimMenu BaseHook::Add(new DetourHook("MatchmakingSessionDetailSendResponse", Pointers.MatchmakingSessionDetailSendResponse, Hooks::Matchmaking::MatchmakingSessionDetailSendResponse)); BaseHook::Add(new DetourHook("MatchmakingUnadvertise", Pointers.MatchmakingUnadvertise, Hooks::Matchmaking::MatchmakingUnadvertise)); BaseHook::Add(new DetourHook("MatchmakingUpdate", Pointers.MatchmakingUpdate, Hooks::Matchmaking::MatchmakingUpdate)); + BaseHook::Add(new DetourHook("MatchmakingFindSessions", Pointers.MatchmakingFindSessions, Hooks::Matchmaking::MatchmakingFindSessions)); + BaseHook::Add(new DetourHook("MatchmakingFindSessionsResponse", Pointers.MatchmakingFindSessionsResponse, Hooks::Matchmaking::MatchmakingFindSessionsResponse)); BaseHook::Add(new DetourHook("AssistedAimShouldReleaseEntity", Pointers.AssistedAimShouldReleaseEntity, Hooks::Misc::AssistedAimShouldReleaseEntity)); } diff --git a/src/game/backend/CustomMatchmaking.cpp b/src/game/backend/CustomMatchmaking.cpp index f8cd0292..3329e4cf 100644 --- a/src/game/backend/CustomMatchmaking.cpp +++ b/src/game/backend/CustomMatchmaking.cpp @@ -2,6 +2,7 @@ #include "core/util/Joaat.hpp" #include "types/network/MatchmakingId.hpp" #include "types/network/rlSessionDetail.hpp" +#include "types/network/NetworkGameFilterMatchmakingComponent.hpp" #include "core/commands/BoolCommand.hpp" #include "core/commands/IntCommand.hpp" #include "core/commands/ListCommand.hpp" @@ -12,32 +13,13 @@ namespace YimMenu::Features { - static std::vector> g_RegionCodes = { - {0, "CIS"}, - {1, "South America"}, - {2, "US East"}, - {3, "Europe"}, - {4, "China"}, - {5, "Australia"}, - {6, "US West"}, - {7, "Japan"}, - {8, "Unknown"}, + static std::vector> g_SortMethods = { + {0, "Off"}, + {1, "Player Count"}, }; - - static std::vector> g_LanguageTypes = { - {0, "English"}, - {1, "French"}, - {2, "German"}, - {3, "Italian"}, - {4, "Spanish (Spain)"}, - {5, "Portuguese (Brazil)"}, - {6, "Polish"}, - {7, "Russian"}, - {8, "Korean"}, - {9, "Chinese (Traditional)"}, - {10, "Japanese"}, - {11, "Spanish (Mexico)"}, - {12, "Chinese (Simplified)"}, + static std::vector> g_SortDirections = { + {0, "Ascending"}, + {1, "Descending"}, }; BoolCommand _SpoofRegionType{ @@ -48,7 +30,7 @@ namespace YimMenu::Features "mmregiontype", "Region Type", "The region to spoof the session to", - g_RegionCodes}; + g_RegionCodes}; BoolCommand _SpoofLanguage{ "mmspooflanguage", @@ -83,6 +65,50 @@ namespace YimMenu::Features 2, 7, 5}; + + BoolCommand _LanguageFilterEnabled{ + "mmlanguagefilterenabled", + "Filter By Language", + "Filter sessions by language"}; + ListCommand _LanguageFilter{ + "mmlanguagefilter", + "Language", + "The language to filter", + g_LanguageTypes}; + BoolCommand _FilterMultiplexedSessions{ + "mmfiltermultiplexedsessions", + "Filter Multiplexed Sessions", + "Filter out multiplexed sessions"}; + + BoolCommand _PlayerCountFilterEnabled{ + "mmplayercountfilterenabled", + "Filter By Player Count", + "Filter by player count"}; + IntCommand _PlayerCountFilterMin{ + "mmplayercountfiltermin", + "Player Count Minimum", + "Minimum players filter", + 1, + 32, + 25}; + IntCommand _PlayerCountFilterMax{ + "mmplayercountfiltermax", + "Player Count Maximum", + "Maximum players filter", + 1, + 32, + 25}; + + ListCommand _SortMethod{ + "mmsortmethod", + "Sort By", + "", + g_SortMethods}; + ListCommand _SortDirection{ + "mmsortdirection", + "Sort Direction", + "", + g_SortDirections}; } namespace YimMenu @@ -112,7 +138,114 @@ namespace YimMenu CustomMatchmaking::CustomMatchmaking() { + } + bool CustomMatchmaking::MatchmakeImpl(std::optional constraint, std::optional enforce_player_limit) + { + for (auto& session : m_FoundSessions) + { + session.m_IsValid = true; + } + + NetworkGameFilterMatchmakingComponent component{}; + strcpy(component.m_FilterName, "Group"); + component.m_FilterType = 1; + component.m_GameMode = 0; + component.m_NumParameters = 0; + component.m_SessionType = 25600; + + if (constraint) + { + component.SetParameter("MMATTR_DISCRIMINATOR", 0, constraint.value()); + } + + component.SetParameter("MMATTR_MM_GROUP_2", 2, 30); + + rage::rlTaskStatus state{}; + static rage::rlSessionInfo result_sessions[MAX_SESSIONS_TO_FIND]; + + m_Active = true; + m_NumValidSessions = 0; + + if (BaseHook::Get>()->Original()(0, 1, &component, MAX_SESSIONS_TO_FIND, result_sessions, &m_NumSessionsFound, &state)) + { + while (state.m_Status == 1) + ScriptMgr::Yield(); + + if (state.m_Status == 3) + { + std::unordered_map stok_map = {}; + + for (int i = 0; i < m_NumSessionsFound; i++) + { + m_FoundSessions[i].m_Info = result_sessions[i]; + + if (auto it = stok_map.find(m_FoundSessions[i].m_Info.m_SessionToken); it != stok_map.end()) + { + if (Features::_FilterMultiplexedSessions.GetState()) + { + it->second->m_IsValid = false; + } + + it->second->m_Attributes.m_MultiplexCount++; + m_FoundSessions[i].m_IsValid = false; + continue; + } + + if (enforce_player_limit.has_value() && enforce_player_limit.value() + && m_FoundSessions[i].m_Attributes.m_PlayerCount >= 30) + m_FoundSessions[i].m_IsValid = false; + + if (Features::_LanguageFilterEnabled.GetState() + && m_FoundSessions[i].m_Attributes.m_Language != Features::_LanguageFilter.GetState()) + m_FoundSessions[i].m_IsValid = false; + + if (Features::_PlayerCountFilterEnabled.GetState() + && (m_FoundSessions[i].m_Attributes.m_PlayerCount < Features::_PlayerCountFilterMin.GetState() + || m_FoundSessions[i].m_Attributes.m_PlayerCount > Features::_PlayerCountFilterMax.GetState())) + { + m_FoundSessions[i].m_IsValid = false; + } + + stok_map.emplace(m_FoundSessions[i].m_Info.m_SessionToken, &m_FoundSessions[i]); + } + + if (Features::_SortMethod.GetState() != 0) + { + std::qsort(m_FoundSessions, m_NumSessionsFound, sizeof(Session), [](const void* a1, const void* a2) -> int { + std::strong_ordering result = std::strong_ordering::equal; + + if (Features::_SortMethod.GetState() == 1) + { + result = (((Session*)(a1))->m_Attributes.m_PlayerCount <=> ((Session*)(a2))->m_Attributes.m_PlayerCount); + } + + if (result == 0) + return 0; + + if (result > 0) + return Features::_SortDirection.GetState() ? -1 : 1; + + if (result < 0) + return Features::_SortDirection.GetState() ? 1 : -1; + + + std::unreachable(); + }); + } + + m_Active = false; + return true; + } + } + else + { + m_Active = false; + return false; + } + + m_Active = false; + return false; } bool CustomMatchmaking::OnAdvertiseImpl(int& num_slots, int& available_slots, rage::rlSessionInfo* info, MatchmakingAttributes* attrs, MatchmakingId* id, rage::rlTaskStatus* status) @@ -151,7 +284,7 @@ namespace YimMenu auto id_hash = GetIdHash(id); m_MultiplexedSessions.emplace(id_hash, std::vector{}); - + // create the multiplexed sessions for (int i = 0; i < Features::_MultiplexCount.GetState() - 1; i++) { @@ -198,7 +331,7 @@ namespace YimMenu auto num_slots_copy = num_slots; auto available_slots_copy = available_slots; FiberPool::Push([session, num_slots_copy, available_slots_copy, info, attrs]() { - auto session_copy = session; // the compiler doesn't like it if I use session directly + auto session_copy = session; // the compiler doesn't like it if I use session directly BaseHook::Get>()->Original()(0, &session_copy, num_slots_copy, available_slots_copy, info, attrs, nullptr); // life's too short to check the task result }); } @@ -235,7 +368,7 @@ namespace YimMenu } } - return true; + return true; } void CustomMatchmaking::OnSendSessionDetailResponseImpl(rage::rlSessionDetailMsg* message) diff --git a/src/game/backend/CustomMatchmaking.hpp b/src/game/backend/CustomMatchmaking.hpp index 999910f0..81f8a4a1 100644 --- a/src/game/backend/CustomMatchmaking.hpp +++ b/src/game/backend/CustomMatchmaking.hpp @@ -1,4 +1,5 @@ #pragma once +#include "types/network/rlSessionInfo.hpp" #include "types/network/rlTaskStatus.hpp" #include "types/network/MatchmakingId.hpp" @@ -13,6 +14,37 @@ class MatchmakingId; namespace YimMenu { + namespace Features + { + static std::vector> g_RegionCodes = { + {0, "CIS"}, + {1, "South America"}, + {2, "US East"}, + {3, "Europe"}, + {4, "China"}, + {5, "Australia"}, + {6, "US West"}, + {7, "Japan"}, + {8, "Unknown"}, + }; + + static std::vector> g_LanguageTypes = { + {0, "English"}, + {1, "French"}, + {2, "German"}, + {3, "Italian"}, + {4, "Spanish (Spain)"}, + {5, "Portuguese (Brazil)"}, + {6, "Polish"}, + {7, "Russian"}, + {8, "Korean"}, + {9, "Chinese (Traditional)"}, + {10, "Japanese"}, + {11, "Spanish (Mexico)"}, + {12, "Chinese (Simplified)"}, + }; + } + class CustomMatchmaking { bool OnAdvertiseImpl(int& num_slots, int& available_slots, rage::rlSessionInfo* info, MatchmakingAttributes* attrs, MatchmakingId* id, rage::rlTaskStatus* status); @@ -20,6 +52,9 @@ namespace YimMenu bool OnUnadvertiseImpl(MatchmakingId* id); void OnSendSessionDetailResponseImpl(rage::rlSessionDetailMsg* message); + bool MatchmakeImpl(std::optional constraint = std::nullopt, std::optional enforce_player_limit = std::nullopt); + + CustomMatchmaking(); static CustomMatchmaking& GetInstance() @@ -28,9 +63,40 @@ namespace YimMenu return instance; } + public: + constexpr static int MAX_SESSIONS_TO_FIND = 1000; + + struct SessionAttributes + { + int m_Discriminator; + int m_PlayerCount; + int m_Region; + int m_Language; + int m_MultiplexCount = 1; + }; + + struct Session + { + rage::rlSessionInfo m_Info; + SessionAttributes m_Attributes; + bool m_IsValid; + }; + + private: + int m_NumSessionsFound = 0; + int m_NumValidSessions = 0; + bool m_Active = false; + Session m_FoundSessions[MAX_SESSIONS_TO_FIND]; + std::unordered_map> m_MultiplexedSessions; + public: + static bool Matchmake(std::optional constraint = std::nullopt, std::optional enforce_player_limit = std::nullopt) + { + return GetInstance().MatchmakeImpl(constraint, enforce_player_limit); + } + static bool OnAdvertise(int& num_slots, int& available_slots, rage::rlSessionInfo* info, MatchmakingAttributes* attrs, MatchmakingId* id, rage::rlTaskStatus* status) { return GetInstance().OnAdvertiseImpl(num_slots, available_slots, info, attrs, id, status); @@ -50,5 +116,25 @@ namespace YimMenu { return GetInstance().OnSendSessionDetailResponseImpl(message); } + + static int GetNumFoundSessions() + { + return GetInstance().m_NumSessionsFound; + } + + static int GetNumValidSessions() + { + return GetInstance().m_NumValidSessions; + } + + static Session* GetFoundSessions() + { + return GetInstance().m_FoundSessions; + } + + static bool IsActive() + { + return GetInstance().m_Active; + } }; } \ No newline at end of file diff --git a/src/game/frontend/submenus/Network.cpp b/src/game/frontend/submenus/Network.cpp index 57ff38b0..513643d3 100644 --- a/src/game/frontend/submenus/Network.cpp +++ b/src/game/frontend/submenus/Network.cpp @@ -4,6 +4,7 @@ #include "game/frontend/items/Items.hpp" #include "game/frontend/submenus/Network/SavedPlayers.hpp" #include "game/frontend/submenus/Network/RandomEvents.hpp" +#include "game/frontend/submenus/Network/SessionBrowser.hpp" #include "game/gta/Network.hpp" namespace YimMenu::Submenus @@ -136,6 +137,7 @@ namespace YimMenu::Submenus AddCategory(std::move(session)); AddCategory(std::move(spoofing)); AddCategory(std::move(BuildSavedPlayersMenu())); + AddCategory(std::move(BuildSessionBrowser())); AddCategory(BuildRandomEventsMenu()); } } \ No newline at end of file diff --git a/src/game/frontend/submenus/Network/SessionBrowser.cpp b/src/game/frontend/submenus/Network/SessionBrowser.cpp new file mode 100644 index 00000000..79276955 --- /dev/null +++ b/src/game/frontend/submenus/Network/SessionBrowser.cpp @@ -0,0 +1,150 @@ +#include "SavedPlayers.hpp" +#include "core/backend/FiberPool.hpp" +#include "core/frontend/widgets/imgui_colors.h" +#include "core/frontend/Notifications.hpp" +#include "game/backend/CustomMatchmaking.hpp" +#include "game/backend/SavedPlayers.hpp" +#include "game/frontend/items/Items.hpp" +#include "game/gta/Network.hpp" +#include "game/pointers/Pointers.hpp" +#include "imgui.h" + +namespace YimMenu::Submenus +{ + static int selected_session_idx = -1; + + std::string GetSessionName(const CustomMatchmaking::Session& session) + { + auto host_rid = session.m_Info.m_HostInfo.m_GamerHandle.m_RockstarId; + + const auto player = SavedPlayers::GetPlayerData(host_rid); + if(player) + return player->m_Name; + + return std::format("{:X}", session.m_Info.m_SessionToken); + } + + void RenderSessionBrowser() + { + static char name_buf[32]; + static char search[64]; + static char session_info[0x100]{}; + ImGui::Text("Total sessions found: %i", CustomMatchmaking::GetNumFoundSessions()); + + if (ImGui::BeginListBox("###sessions", ImVec2(300, -ImGui::GetFrameHeight()))) + { + if (CustomMatchmaking::GetNumFoundSessions()) + { + for (int i = 0; i < CustomMatchmaking::GetNumFoundSessions(); i++) + { + auto& session = CustomMatchmaking::GetFoundSessions()[i]; + + if (!session.m_IsValid) + continue; + + auto host_rid = session.m_Info.m_HostInfo.m_GamerHandle.m_RockstarId; + auto player = SavedPlayers::GetPlayerData(host_rid); + + std::string session_str; + if (session.m_Attributes.m_MultiplexCount > 1) + session_str = std::format("{} (x{})", GetSessionName(session), session.m_Attributes.m_MultiplexCount); + else + session_str = GetSessionName(session); + + if (ImGui::Selectable(session_str.c_str(), i == selected_session_idx)) + { + selected_session_idx = i; + Pointers.EncodeSessionInfo(&session.m_Info, session_info, 0xA9, nullptr); + } + + if (ImGui::IsItemHovered()) + { + auto tool_tip = std::format("Number of Players: {}\nRegion: {}\nLanguage: {}\nHost Rockstar ID: {}\nDiscriminator: {:X}", + session.m_Attributes.m_PlayerCount, + Features::g_RegionCodes.at(session.m_Attributes.m_Region).second, + Features::g_LanguageTypes.at(session.m_Attributes.m_Language).second, + session.m_Info.m_HostInfo.m_GamerHandle.m_RockstarId, // TODO: this is not accurate + session.m_Attributes.m_Discriminator); + ImGui::SetTooltip("%s", tool_tip.c_str()); + } + } + } + else + { + ImGui::TextUnformatted("No sessions"); + } + + ImGui::EndListBox(); + } + + if (selected_session_idx != -1) + { + ImGui::SameLine(); + if (ImGui::BeginChild("###selected_session", ImVec2(300, -ImGui::GetFrameHeight()), false, ImGuiWindowFlags_NoBackground)) + { + auto& session = CustomMatchmaking::GetFoundSessions()[selected_session_idx]; + + ImGui::Text("Num Players: %d", session.m_Attributes.m_PlayerCount); + ImGui::Text("Discriminator: 0x%X", session.m_Attributes.m_Discriminator); + ImGui::Text("Region: %s", Features::g_RegionCodes.at(session.m_Attributes.m_Region).second); + ImGui::Text("Language: %s", Features::g_LanguageTypes.at(session.m_Attributes.m_Language).second); + + auto& data = session.m_Info.m_HostInfo; + ImGui::Text("Host Rockstar ID: %llu", data.m_GamerHandle.m_RockstarId); + + if(ImGui::Button("Copy Session Info")) + { + FiberPool::Push([] { + ImGui::SetClipboardText(session_info); + }); + } + + if(ImGui::Button("Join")) + { + FiberPool::Push([session] { + Network::JoinSessionInfo(&session.m_Info); + }); + } + } + ImGui::EndChild(); + } + + static uint32_t discriminator = 730776930; // 0xA9A8562 for non_cheater pool + + ImGui::InputScalar("Discriminator", ImGuiDataType_U32, &discriminator, nullptr, nullptr, "%08X"); + + if(ImGui::Button("Refresh")) + { + FiberPool::Push( [] { + selected_session_idx = -1; + + if (!CustomMatchmaking::Matchmake(discriminator)) + Notifications::Show("Matchmaking", "Matchmaking failed", NotificationType::Error); + }); + } + } + + std::shared_ptr BuildSessionBrowser() + { + auto menu = std::make_shared("Session Browser"); + menu->AddItem(std::make_unique(RenderSessionBrowser)); + + auto filters = std::make_shared("Filters"); + + filters->AddItem(std::make_shared("mmlanguagefilterenabled"_J)); + filters->AddItem(std::make_shared("mmlanguagefilter"_J)); + + filters->AddItem(std::make_shared("mmfiltermultiplexedsessions"_J)); + + filters->AddItem(std::make_shared("mmplayercountfilterenabled"_J)); + filters->AddItem(std::make_shared("mmplayercountfiltermin"_J)); + filters->AddItem(std::make_shared("mmplayercountfiltermax"_J)); + + filters->AddItem(std::make_shared("mmsortmethod"_J)); + filters->AddItem(std::make_shared("mmsortdirection"_J)); + + menu->AddItem(filters); + + return menu; + } +} \ No newline at end of file diff --git a/src/game/frontend/submenus/Network/SessionBrowser.hpp b/src/game/frontend/submenus/Network/SessionBrowser.hpp new file mode 100644 index 00000000..356f0c69 --- /dev/null +++ b/src/game/frontend/submenus/Network/SessionBrowser.hpp @@ -0,0 +1,8 @@ +#pragma once +#include "core/frontend/manager/Category.hpp" +#include "game/frontend/items/Items.hpp" + +namespace YimMenu::Submenus +{ + std::shared_ptr BuildSessionBrowser(); +} \ No newline at end of file diff --git a/src/game/gta/Network.cpp b/src/game/gta/Network.cpp index e5c4de28..9ba1714b 100644 --- a/src/game/gta/Network.cpp +++ b/src/game/gta/Network.cpp @@ -25,7 +25,7 @@ namespace YimMenu::Network *join_type_global.As() = to_launch; } - void JoinSessionInfo(rage::rlSessionInfo* info) + void JoinSessionInfo(const rage::rlSessionInfo* info) { static std::optional session_to_join; static bool ensure_native_hook_initialized = ([] { diff --git a/src/game/gta/Network.hpp b/src/game/gta/Network.hpp index 3b497cd7..bf989419 100644 --- a/src/game/gta/Network.hpp +++ b/src/game/gta/Network.hpp @@ -23,7 +23,7 @@ namespace YimMenu::Network }; void LaunchJoinType(JoinType to_launch); - void JoinSessionInfo(rage::rlSessionInfo* info); + void JoinSessionInfo(const rage::rlSessionInfo* info); void JoinRockstarId(std::uint64_t id); std::optional ResolveRockstarId(std::string_view name); } \ No newline at end of file diff --git a/src/game/hooks/Hooks.hpp b/src/game/hooks/Hooks.hpp index 00a31305..61e0d578 100644 --- a/src/game/hooks/Hooks.hpp +++ b/src/game/hooks/Hooks.hpp @@ -10,6 +10,7 @@ class CBattlEyePlayerModifyContext; namespace rage { + class JSONNode; class netConnectionManager; class netArrayHandler; class netEvent; @@ -26,6 +27,7 @@ namespace rage class MatchmakingAttributes; class MatchmakingId; +class NetworkGameFilterMatchmakingComponent; namespace YimMenu { @@ -60,6 +62,8 @@ namespace YimMenu::Hooks extern bool MatchmakingUpdate(int profile_index, MatchmakingId* id, int num_slots, int available_slots, rage::rlSessionInfo* info, MatchmakingAttributes* data, rage::rlTaskStatus* status); extern bool MatchmakingUnadvertise(int profile_index, MatchmakingId* id, rage::rlTaskStatus* status); extern bool MatchmakingSessionDetailSendResponse(rage::netConnectionManager* mgr, void* request_frame, rage::rlSessionDetailMsg* msg); + extern bool MatchmakingFindSessions(int profile_index, int available_slots, NetworkGameFilterMatchmakingComponent* m_filter, unsigned int max_sessions, rage::rlSessionInfo* result_sessions, int* result_session_count, rage::rlTaskStatus* state); + extern bool MatchmakingFindSessionsResponse(void* _this, void* unused, rage::JSONNode* node, int* unk); } namespace Info diff --git a/src/game/hooks/Matchmaking/MatchmakingFindSessions.cpp b/src/game/hooks/Matchmaking/MatchmakingFindSessions.cpp new file mode 100644 index 00000000..09f6b00b --- /dev/null +++ b/src/game/hooks/Matchmaking/MatchmakingFindSessions.cpp @@ -0,0 +1,83 @@ +#include "game/hooks/Hooks.hpp" +#include "core/hooking/DetourHook.hpp" +#include "game/backend/CustomMatchmaking.hpp" +#include "types/network/NetworkGameFilterMatchmakingComponent.hpp" + +namespace rage +{ + class JSONNode + { + public: + char* m_key; //0x0000 + char pad_0008[40]; //0x0008 + class rage::JSONNode* m_sibling;//0x0030 + class rage::JSONNode* m_child; //0x0038 + char* m_value; //0x0040 + char pad_0040[8]; //0x0048 + + inline JSONNode* get_child_node(const char* name) + { + for (auto node = m_child; node; node = node->m_sibling) + { + if (strcmp(name, node->m_key) == 0) + return node; + } + + return nullptr; + } + };//Size: 0x0048 + static_assert(sizeof(rage::JSONNode) == 0x50); +} + +namespace +{ + // https://stackoverflow.com/a/5167641 + static std::vector split(const std::string& s, char seperator) + { + std::vector output; + + std::string::size_type prev_pos = 0, pos = 0; + + while ((pos = s.find(seperator, pos)) != std::string::npos) + { + std::string substring(s.substr(prev_pos, pos - prev_pos)); + + output.push_back(substring); + + prev_pos = ++pos; + } + + output.push_back(s.substr(prev_pos, pos - prev_pos));// Last word + + return output; + } +} + +namespace YimMenu::Hooks +{ + bool Matchmaking::MatchmakingFindSessions(int profile_index, int available_slots, NetworkGameFilterMatchmakingComponent* m_filter, unsigned int max_sessions, rage::rlSessionInfo* result_sessions, int* result_session_count, rage::rlTaskStatus* state) + { + return BaseHook::Get>()->Original()(profile_index, available_slots, m_filter, max_sessions, result_sessions, result_session_count, state); + } + + bool Matchmaking::MatchmakingFindSessionsResponse(void* _this, void* unused, rage::JSONNode* node, int* unk) + { + bool ret = BaseHook::Get>()->Original()(_this, unused, node, unk); + + if (CustomMatchmaking::IsActive()) + { + int i = 0; + for (auto result = node->get_child_node("Results")->m_child; result; result = result->m_sibling) + { + const auto& values = split(result->get_child_node("Attributes")->m_value, ','); + CustomMatchmaking::GetFoundSessions()[i].m_Attributes.m_Discriminator = std::stoi(values[2]); + CustomMatchmaking::GetFoundSessions()[i].m_Attributes.m_PlayerCount = std::stoi(values[4]); + CustomMatchmaking::GetFoundSessions()[i].m_Attributes.m_Language = std::stoi(values[5]); + CustomMatchmaking::GetFoundSessions()[i].m_Attributes.m_Region = std::stoi(values[6]); + i++; + } + } + + return ret; + } +} diff --git a/src/game/pointers/Pointers.cpp b/src/game/pointers/Pointers.cpp index d97ab9fe..3f76e22f 100644 --- a/src/game/pointers/Pointers.cpp +++ b/src/game/pointers/Pointers.cpp @@ -447,11 +447,26 @@ namespace YimMenu MatchmakingUnadvertise = addr.Sub(0xC).Rip().As(); }); + static constexpr auto doMatchmakingFindSessions = Pattern<"4C 89 5C 24 20 E8 ? ? ? ? 84 C0 74 ? C7 47">("MatchmakingFindSessions"); + scanner.Add(doMatchmakingFindSessions, [this](PointerCalculator addr) { + MatchmakingFindSessions = addr.Add(6).Rip().As(); + }); + + static constexpr auto matchmakingFindSessionsResponse = Pattern<"4C 89 CE 49 89 CE">("MatchmakingFindSessionsResponse"); + scanner.Add(matchmakingFindSessionsResponse, [this](PointerCalculator addr) { + MatchmakingFindSessionsResponse = addr.Sub(0x1B).As(); + }); + static constexpr auto matchmakingSessionDetailSendResponsePtrn = Pattern<"48 B8 01 00 00 00 0D 00 00 00">("SessionDetailSendResponse"); scanner.Add(matchmakingSessionDetailSendResponsePtrn, [this](PointerCalculator addr) { MatchmakingSessionDetailSendResponse = addr.Add(0x2F).Rip().As(); }); + static constexpr auto encodeSessionInfoPtrn = Pattern<"E8 ? ? ? ? 48 85 C0 74 ? 48 89 BC">("EncodeSessionInfo"); + scanner.Add(encodeSessionInfoPtrn, [this](PointerCalculator addr) { + EncodeSessionInfo = addr.Add(1).Rip().As(); + }); + static constexpr auto gameSkeletonUpdatePtrn = Pattern<"56 48 83 EC 20 48 8B 81 40 01 00 00 48 85 C0">("GameSkeletonUpdate"); scanner.Add(gameSkeletonUpdatePtrn, [this](PointerCalculator addr) { GameSkeletonUpdate = addr.As(); diff --git a/src/game/pointers/Pointers.hpp b/src/game/pointers/Pointers.hpp index ac5553c6..30c8de1c 100644 --- a/src/game/pointers/Pointers.hpp +++ b/src/game/pointers/Pointers.hpp @@ -45,6 +45,7 @@ class CNetworkSession; class CGameDataHash; class CStatsMpCharacterMappingData; class CAnticheatContext; +class NetworkGameFilterMatchmakingComponent; namespace YimMenu { @@ -70,6 +71,8 @@ namespace YimMenu using GetPresenceAttributes = bool (*)(int profile_index, rage::rlScGamerHandle* handles, int num_handles, rage::rlQueryPresenceAttributesContext** contexts, int count, rage::rlScTaskStatus* state); using GetAvatars = bool (*)(rage::rlGetAvatarsContext* context, rage::rlGetAvatarsPlayerList* players); using AssistedAimFindNewTarget = bool (*)(__int64 a1); + using MatchmakingFindSessions = bool (*)(int profile_index, int available_slots, NetworkGameFilterMatchmakingComponent* m_filter, unsigned int max_sessions, rage::rlSessionInfo* result_sessions, int* result_session_count, rage::rlTaskStatus* state); + using EncodeSessionInfo = bool (*)(rage::rlSessionInfo* info, char* buffer, int buffer_size, int* bytes_written); } struct PointerData @@ -170,6 +173,9 @@ namespace YimMenu PVOID MatchmakingUpdate; PVOID MatchmakingUnadvertise; PVOID MatchmakingSessionDetailSendResponse; + PVOID MatchmakingFindSessions; + PVOID MatchmakingFindSessionsResponse; + Functions::EncodeSessionInfo EncodeSessionInfo; PVOID GameSkeletonUpdate; }; diff --git a/src/types/network/NetworkGameFilterMatchmakingComponent.hpp b/src/types/network/NetworkGameFilterMatchmakingComponent.hpp new file mode 100644 index 00000000..4a9165fa --- /dev/null +++ b/src/types/network/NetworkGameFilterMatchmakingComponent.hpp @@ -0,0 +1,33 @@ +#pragma once + +class NetworkGameFilterMatchmakingComponent +{ +public: + // do not use for actual network filters, this will break things + inline void SetParameter(const char* name, int index, int value) + { + std::strcpy(m_ParamNames[index], name); + m_ParamMappings[index] = index; + m_ParamValues[index] = value; + m_EnabledParamsBitset |= (1 << index); + + if (m_NumParameters <= (uint32_t)index) + m_NumParameters++; + } + + uint32_t m_FilterType; //0x0000 + char m_FilterName[24]; //0x0004 + uint32_t m_NumParameters; //0x001C + uint16_t m_GameMode; //0x0020 + uint16_t m_SessionType; //0x0022 + uint32_t m_ParamUnk[8]; //0x0024 + char m_ParamNames[8][24]; //0x0044 + char pad_0104[4]; //0x0104 + uint32_t m_ParamMappings[8]; //0x0108 + char pad_0128[352]; //0x0128 + uint32_t m_ParamValues[8]; //0x0288 + char pad_02A8[96]; //0x02A8 + uint32_t m_EnabledParamsBitset; //0x0308 + char pad_030C[8]; //0x030C +}; //Size: 0x0314 +static_assert(sizeof(NetworkGameFilterMatchmakingComponent) == 0x314);