feat(network): join by username

This commit is contained in:
maybegreat48
2025-04-09 13:12:31 -04:00
parent 1e14449720
commit 5cd7d1c8f0
11 changed files with 209 additions and 7 deletions
+13
View File
@@ -270,6 +270,19 @@ namespace YimMenu
return res;
}
void SavedPlayers::AddPlayerDataImpl(std::uint64_t id, std::string_view username)
{
if (auto data = GetPlayerDataImpl(id))
return;
SavedPlayerData data;
data.m_Name = username;
m_SavedPlayers.emplace(id, data);
Save();
}
void SavedPlayers::UpdateRockstarIdImpl(std::uint64_t id, std::uint64_t new_id)
{
if (auto player = GetPlayerDataImpl(id))
+6
View File
@@ -23,6 +23,7 @@ namespace YimMenu
void FetchPlayerInfoImpl(bool tracked_only = false);
SavedPlayerData* GetPlayerDataImpl(std::uint64_t id);
SavedPlayerData* GetPlayerDataImpl(Player player, bool create = true);
void AddPlayerDataImpl(std::uint64_t id, std::string_view username);
void UpdateRockstarIdImpl(std::uint64_t id, std::uint64_t new_id);
void RemovePlayerDataImpl(std::uint64_t id);
void LoadImpl();
@@ -56,6 +57,11 @@ namespace YimMenu
return GetInstance().GetPlayerDataImpl(player, create);
}
static void AddPlayerData(std::uint64_t id, std::string_view username)
{
GetInstance().AddPlayerDataImpl(id, username);
}
// TODO: this cannot be safely called outside the saved players frontend menu. Invalidates all SavedPlayerData pointers due to potential map reallocation
static void UpdateRockstarId(std::uint64_t old_id, std::uint64_t new_id)
{
+19
View File
@@ -1,5 +1,6 @@
#include "Network.hpp"
#include "core/backend/FiberPool.hpp"
#include "core/frontend/Notifications.hpp"
#include "game/frontend/items/Items.hpp"
#include "game/frontend/submenus/Network/SavedPlayers.hpp"
#include "game/gta/Network.hpp"
@@ -25,6 +26,24 @@ namespace YimMenu::Submenus
joinGroup->AddItem(joinSession);
joinGroup->AddItem(std::make_shared<ImGuiItem>([] {
static std::uint64_t rockstar_id{};
static char name_buf[24]{};
ImGui::SetNextItemWidth(180.0f);
ImGui::InputText("Username", name_buf, sizeof(name_buf));
ImGui::SameLine();
if (ImGui::Button("Join##username"))
FiberPool::Push([] {
auto rid = YimMenu::Network::ResolveRockstarId(name_buf);
if (rid)
{
YimMenu::Network::JoinRockstarId(*rid);
}
else
{
Notifications::Show("Joiner", "Failed to get RID from username", NotificationType::Error);
}
});
ImGui::SetNextItemWidth(150.0f);
ImGui::InputScalar("Rockstar Id", ImGuiDataType_U64, &rockstar_id);
ImGui::SameLine();
@@ -1,6 +1,7 @@
#include "SavedPlayers.hpp"
#include "core/backend/FiberPool.hpp"
#include "core/frontend/widgets/imgui_colors.h"
#include "core/frontend/Notifications.hpp"
#include "game/backend/SavedPlayers.hpp"
#include "game/gta/Network.hpp"
#include "game/pointers/Pointers.hpp"
@@ -72,7 +73,7 @@ namespace YimMenu::Submenus
ImGui::SetNextItemWidth(200.f);
ImGui::InputTextWithHint("Search", "Search", g_NameToSearch, sizeof(g_NameToSearch));
if (ImGui::BeginListBox("###player-list", {180, static_cast<float>(*Pointers.ScreenResY - 700 - 38 * 4)}))
if (ImGui::BeginListBox("###player-list", {180, -100 /* static_cast<float>(*Pointers.ScreenResY - 700 - 38 * 4) */}))
{
auto& players = SavedPlayers::GetSavedPlayers();
@@ -108,7 +109,7 @@ namespace YimMenu::Submenus
if (!g_SelectedPlayer || g_SelectedRid == 0)
return;
if (ImGui::BeginChild("##player-editor", {500, static_cast<float>(*Pointers.ScreenResY - 688 - 38 * 4)}, 0, ImGuiWindowFlags_NoBackground))
if (ImGui::BeginChild("##player-editor", {500, -100 /* static_cast<float>(*Pointers.ScreenResY - 688 - 38 * 4) */}, 0, ImGuiWindowFlags_NoBackground))
{
ImGui::SetNextItemWidth(180.f);
if (ImGui::InputText("Name", g_SelectedPlayerName, sizeof(g_SelectedPlayerName)))
@@ -178,10 +179,32 @@ namespace YimMenu::Submenus
RenderPlayerEditor();
}
static void RenderAddNewPlayer()
{
static char name_buf[24]{};
ImGui::SetNextItemWidth(180.0f);
ImGui::InputText("Username", name_buf, sizeof(name_buf));
ImGui::SameLine();
if (ImGui::Button("Add"))
FiberPool::Push([] {
auto rid = YimMenu::Network::ResolveRockstarId(name_buf);
if (rid)
{
SavedPlayers::AddPlayerData(*rid, name_buf);
}
else
{
Notifications::Show("Saved Players", "Failed to get RID from username", NotificationType::Error);
}
});
}
std::shared_ptr<Category> BuildSavedPlayersMenu()
{
auto menu = std::make_shared<Category>("Saved Players");
auto players = std::make_shared<Group>("Players");
auto new_player = std::make_shared<Group>("New");
auto tracking = std::make_shared<Group>("Tracking");
auto notifications = std::make_shared<Group>("Notifications");
@@ -189,6 +212,10 @@ namespace YimMenu::Submenus
RenderSavedPlayers();
}));
new_player->AddItem(std::make_shared<ImGuiItem>([] {
RenderAddNewPlayer();
}));
notifications->AddItem(std::make_shared<BoolCommandItem>("playerdbnotifywhenjoinable"_J));
notifications->AddItem(std::make_shared<BoolCommandItem>("playerdbnotifywhenunjoinable"_J));
notifications->AddItem(std::make_shared<BoolCommandItem>("playerdbnotifywhenonline"_J));
@@ -206,6 +233,7 @@ namespace YimMenu::Submenus
tracking->AddItem(std::make_shared<ConditionalItem>("playerdbnotify"_J, std::move(notifications)));
menu->AddItem(players);
menu->AddItem(new_player);
menu->AddItem(tracking);
return menu;
+39
View File
@@ -6,9 +6,13 @@
#include "game/gta/ScriptGlobal.hpp"
#include "game/gta/ScriptFunction.hpp"
#include "game/pointers/Pointers.hpp"
#include "types/rage/atArray.hpp"
#include "types/network/rlSessionInfo.hpp"
#include "types/network/rlSessionByGamerHandleTaskResult.hpp"
#include "types/network/rlTaskStatus.hpp"
#include "types/socialclub/rlQueryAccountsResult.hpp"
#include "types/socialclub/rlGetAvatarsContext.hpp"
#include "types/socialclub/rlScTaskStatus.hpp"
namespace YimMenu::Network
{
@@ -37,6 +41,7 @@ namespace YimMenu::Network
{
ctx->SetReturnValue(TRUE);
}
session_to_join = std::nullopt;
}
else
{
@@ -84,4 +89,38 @@ namespace YimMenu::Network
return;
}
}
std::optional<std::uint64_t> ResolveRockstarId(std::string_view name)
{
if (!Pointers.GetAvatars)
return std::nullopt;
// these are probably too large to keep on the stack
auto results = std::make_unique<rage::rlGetAvatarsResults>();
auto players = std::make_unique<rage::rlGetAvatarsPlayerList>();
rage::rlGetAvatarsContext context;
strncpy(players->m_PlayerNames[0], name.data(), sizeof(players->m_PlayerNames[0]));
players->m_NumEntries = 1;
context.m_Type = rage::rlGetAvatarsContext::Type::BY_NICKNAME;
context.m_AvatarSize = rage::rlGetAvatarsContext::AvatarSize::SMALL; // we don't need the avatars, but you gotta give it something
context.m_Result = results.get();
if (Pointers.GetAvatars(&context, players.get()))
{
while (context.m_Status.m_Status == 1)
{
ScriptMgr::Yield();
}
if (context.m_Status.m_Status == 3 && results->m_NumResults == 1)
{
return results->m_Results[0].m_RockstarId;
}
}
return std::nullopt;
}
}
+1
View File
@@ -25,4 +25,5 @@ namespace YimMenu::Network
void LaunchJoinType(JoinType to_launch);
void JoinSessionInfo(rage::rlSessionInfo* info);
void JoinRockstarId(std::uint64_t id);
std::optional<std::uint64_t> ResolveRockstarId(std::string_view name);
}
+5
View File
@@ -352,6 +352,11 @@ namespace YimMenu
BytePatches::Add(ptr.As<void*>(), std::vector<std::uint8_t>{0xB0, 0x01})->Apply();
});
constexpr auto getAvatarsPtrn = Pattern<"89 4E 7C 48 8B CE E8 ? ? ? ? 84 C0">("GetAvatars");
scanner.Add(getAvatarsPtrn, [this](PointerCalculator ptr) {
GetAvatars = ptr.Add(6).Add(1).Rip().As<Functions::GetAvatars>();
});
if (!scanner.Scan())
{
LOG(WARNING) << "Some socialclub patterns could not be found";
+5
View File
@@ -27,6 +27,9 @@ namespace rage
class rlSessionByGamerTaskResult;
class rlQueryPresenceAttributesContext;
class rlScGamerHandle;
class rlQueryAccountsResult;
class rlGetAvatarsContext;
class rlGetAvatarsPlayerList;
}
class CPedFactory;
class CNetGamePlayer;
@@ -60,6 +63,7 @@ namespace YimMenu
using JoinSessionByInfo = bool (*)(CNetworkSession* network, rage::rlSessionInfo* info, int unk, int flags, rage::rlGamerHandle* handles, int num_handles);
using GetSessionByGamerHandle = bool (*)(int profile_index, rage::rlGamerHandle* handles, int num_handles, rage::rlSessionByGamerTaskResult* results, int num_results, bool* success, rage::rlTaskStatus* state);
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);
}
struct PointerData
@@ -129,6 +133,7 @@ namespace YimMenu
Functions::JoinSessionByInfo JoinSessionByInfo;
Functions::GetSessionByGamerHandle GetSessionByGamerHandle;
Functions::GetPresenceAttributes GetPresenceAttributes;
Functions::GetAvatars GetAvatars;
};
struct Pointers : PointerData
+5 -5
View File
@@ -13,7 +13,7 @@ namespace rage
atArray() :
m_Data(nullptr),
m_Size(0),
m_Count(0)
m_Capacity(0)
{
}
@@ -37,9 +37,9 @@ namespace rage
return m_Size;
}
std::uint16_t count() const
std::uint16_t capacity() const
{
return m_Count;
return m_Capacity;
}
T& operator[](std::uint16_t index) const
@@ -59,10 +59,10 @@ namespace rage
return false;
}
private:
public:
T* m_Data;
std::uint16_t m_Size;
std::uint16_t m_Count;
std::uint16_t m_Capacity; // includes items allocated due to reservations and alignment
};
static_assert(sizeof(rage::atArray<std::uint32_t>) == 0x10, "rage::atArray is not properly sized");
}
@@ -0,0 +1,59 @@
#pragma once
#include "rlScTaskStatus.hpp"
namespace rage
{
struct rlGetAvatarsPlayerList
{
union
{
char m_PlayerNames[51][250];
char m_PlayerHandles[51][250];
};
int m_NumEntries;
};
static_assert(sizeof(rlGetAvatarsPlayerList) == 0x31D4);
struct rlGetAvatarsResult
{
std::uint64_t m_RockstarId; // 0x00
char m_Nickname[51]; // 0x08
char m_AvatarUrl[127]; // 0x3B
};
static_assert(sizeof(rlGetAvatarsResult) == 0xC0);
struct rlGetAvatarsResults
{
rlGetAvatarsResult m_Results[250];
int m_NumResults;
};
static_assert(sizeof(rlGetAvatarsResults) == 0xBB88);
// this is probably something else that LLVM decided to inline into the GetAvatars func
class rlGetAvatarsContext
{
public:
enum class Type : std::uint8_t
{
BY_NICKNAME,
BY_ROCKSTAR_ID
};
enum class AvatarSize
{
SMALL = 1,
MEDIUM = 2,
LARGE = 4
};
virtual ~rlGetAvatarsContext() = default;
char m_Pad[80]; // 0x08
void* m_Result; // 0x58
char m_Pad2[4]; // 0x60
Type m_Type; // 0x64
AvatarSize m_AvatarSize; // 0x68
rage::rlScTaskStatus m_Status{}; // 0x70
};
static_assert(sizeof(rlGetAvatarsContext) == 0x80);
}
@@ -0,0 +1,27 @@
#pragma once
namespace rage
{
class rlQueryAccountsResult
{
public:
enum class Relationship : std::uint32_t
{
NONE = 1,
BLOCKED_BY_ME = 2,
BLOCKED_BY_THEM = 4,
BLOCKED_BY_BOTH = 6,
INVITED_BY_ME = 8,
INVITED_BY_THEM = 16,
FRIEND = 32
};
std::uint64_t m_RockstarId; // 0x00 (gets parsed as a 32 bit value? wtf?)
char m_Nickname[51]; // 0x08
char m_AvatarUrl[2048]; // 0x3B
bool m_IsVisible; // 0x83B
Relationship m_Relationship; // 0x83C
bool m_IsClanmate; // 0x840
};
static_assert(sizeof(rlQueryAccountsResult) == 0x848);
}