Add BattlEye server (#3707)
Some checks failed
Nightly Build / Check Recent Commit (push) Successful in 19s
Nightly Build / Build Nightly (push) Has been cancelled
Nightly Build / Recreate Release (push) Has been cancelled

* feat(anticheat): add BE server and a kick
* fix(clang-format): enforce braces
This commit is contained in:
maybegreat48 2024-09-24 06:12:15 +00:00 committed by GitHub
parent 899c116eca
commit 5db512287c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 401 additions and 13 deletions

View File

@ -33,6 +33,7 @@ IncludeBlocks: Regroup
IndentCaseLabels: 'false' IndentCaseLabels: 'false'
IndentPPDirectives: BeforeHash IndentPPDirectives: BeforeHash
IndentWidth: '4' IndentWidth: '4'
InsertBraces: true
TabWidth: 4 TabWidth: 4
IndentWrappedFunctionNames: 'true' IndentWrappedFunctionNames: 'true'
KeepEmptyLinesAtTheStartOfBlocks: 'false' KeepEmptyLinesAtTheStartOfBlocks: 'false'

View File

@ -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 e45f7f505ac2394c9213f1577831ab100cc74fa2 GIT_TAG b1a2ef201bdbd8b4171e932d5c8de68cc9df0283
GIT_PROGRESS TRUE GIT_PROGRESS TRUE
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND "" BUILD_COMMAND ""

View File

@ -1,6 +1,7 @@
#include "backend/player_command.hpp" #include "backend/player_command.hpp"
#include "gta_util.hpp" #include "gta_util.hpp"
#include "pointers.hpp" #include "pointers.hpp"
#include "services/battleye/battleye_service.hpp"
#include <network/Network.hpp> #include <network/Network.hpp>
@ -55,6 +56,28 @@ namespace big
} }
}; };
class battleye_update_kick : player_command
{
using player_command::player_command;
virtual CommandAccessLevel get_access_level() override
{
return CommandAccessLevel::TOXIC;
}
virtual void execute(player_ptr player, const command_arguments& _args, const std::shared_ptr<command_context> ctx) override
{
unsigned char data[] = {0x00, 0x50, 0x31, 0x4A, 0xC0, 0x1A, 0x13, 0xFF, 0xFF, 0xFF};
player->tampered_with_be = true;
for (int i = 0; i < 20; i++)
{
data[0] = i;
g_battleye_service.send_message(player->get_net_game_player()->get_host_token(), &data, sizeof(data));
}
}
};
battleye_kick g_battleye_kick("battlekick", "BATTLEYE_KICK", "BATTLEYE_KICK_DESC", 0); battleye_kick g_battleye_kick("battlekick", "BATTLEYE_KICK", "BATTLEYE_KICK_DESC", 0);
battleye_ban g_battleye_ban("battleban", "BATTLEYE_FAKE_BAN", "BATTLEYE_FAKE_BAN_DESC", 0); battleye_ban g_battleye_ban("battleban", "BATTLEYE_FAKE_BAN", "BATTLEYE_FAKE_BAN_DESC", 0);
battleye_update_kick g_battleye_update_kick("battleupdate", "BATTLEYE_UPDATE_KICK", "BATTLEYE_UPDATE_KICK_DESC", 0);
} }

View File

@ -118,4 +118,5 @@ inline const static constexpr std::pair<const char*, uint32_t> packet_types[] =
{"MsgTransitionHandshake", 0x8D}, {"MsgTransitionHandshake", 0x8D},
{"MsgDidInvitePlayerRequest", 0x8B}, {"MsgDidInvitePlayerRequest", 0x8B},
{"MsgDidInvitePlayerResponse", 0x8C}, {"MsgDidInvitePlayerResponse", 0x8C},
{"MsgBattlEyeCmd", 0x8F},
}; };

View File

@ -150,8 +150,9 @@ namespace big
bool external_console = true; bool external_console = true;
bool window_hook = false; bool window_hook = false;
bool block_all_metrics = false; bool block_all_metrics = false;
bool battleye_server = false;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(debug, logs, external_console, window_hook, block_all_metrics) NLOHMANN_DEFINE_TYPE_INTRUSIVE(debug, logs, external_console, window_hook, block_all_metrics, battleye_server)
} debug{}; } debug{};
struct tunables struct tunables

View File

@ -443,6 +443,7 @@ namespace rage
Msg_0x45 = 0x45, Msg_0x45 = 0x45,
Msg_0x89 = 0x89, Msg_0x89 = 0x89,
Msg_0x86 = 0x86, Msg_0x86 = 0x86,
MsgBattlEyeCmd = 0x8F,
}; };
enum class eEventNetworkType : int64_t enum class eEventNetworkType : int64_t

View File

@ -5,10 +5,10 @@
#include "lua/lua_manager.hpp" #include "lua/lua_manager.hpp"
#include "services/player_database/player_database_service.hpp" #include "services/player_database/player_database_service.hpp"
#include "services/players/player_service.hpp" #include "services/players/player_service.hpp"
#include "services/battleye/battleye_service.hpp"
#include "util/notify.hpp" #include "util/notify.hpp"
#include "util/session.hpp" #include "util/session.hpp"
namespace big namespace big
{ {
inline bool is_spoofed_host_token(rage::rlGamerInfo* info) inline bool is_spoofed_host_token(rage::rlGamerInfo* info)
@ -25,6 +25,7 @@ namespace big
if (new_index == static_cast<uint8_t>(-1)) if (new_index == static_cast<uint8_t>(-1))
{ {
g_battleye_service.remove_player(player->get_host_token());
g_player_service->player_leave(player); g_player_service->player_leave(player);
if (net_player_data) if (net_player_data)
@ -50,6 +51,8 @@ namespace big
g_player_service->player_join(player); g_player_service->player_join(player);
if (net_player_data) if (net_player_data)
{ {
g_battleye_service.add_player(player->get_host_token(), net_player_data->m_gamer_handle.m_rockstar_id, net_player_data->m_name);
if (g.protections.admin_check) if (g.protections.admin_check)
{ {
if (admin_rids.contains(net_player_data->m_gamer_handle.m_rockstar_id)) if (admin_rids.contains(net_player_data->m_gamer_handle.m_rockstar_id))

View File

@ -2,6 +2,7 @@
#include "hooking/hooking.hpp" #include "hooking/hooking.hpp"
#include "lua/lua_manager.hpp" #include "lua/lua_manager.hpp"
#include "services/players/player_service.hpp" #include "services/players/player_service.hpp"
#include "services/battleye/battleye_service.hpp"
#include <network/CNetworkPlayerMgr.hpp> #include <network/CNetworkPlayerMgr.hpp>
@ -25,6 +26,14 @@ namespace big
void hooks::network_player_mgr_shutdown(CNetworkPlayerMgr* _this) void hooks::network_player_mgr_shutdown(CNetworkPlayerMgr* _this)
{ {
for (auto& [_, player] : g_player_service->players())
{
if (player->is_valid())
{
g_battleye_service.remove_player(player->get_net_game_player()->get_host_token());
}
}
g_player_service->do_cleanup(); g_player_service->do_cleanup();
self::spawned_vehicles.clear(); self::spawned_vehicles.clear();

View File

@ -8,6 +8,7 @@
#include "lua/lua_manager.hpp" #include "lua/lua_manager.hpp"
#include "natives.hpp" #include "natives.hpp"
#include "services/players/player_service.hpp" #include "services/players/player_service.hpp"
#include "services/battleye/battleye_service.hpp"
#include "util/chat.hpp" #include "util/chat.hpp"
#include "util/session.hpp" #include "util/session.hpp"
#include "gta/net_object_mgr.hpp" #include "gta/net_object_mgr.hpp"
@ -739,6 +740,22 @@ namespace big
return true; return true;
} }
case rage::eNetMessage::MsgBattlEyeCmd:
{
char data[1028]{};
int size = buffer.Read<int>(11);
bool client = buffer.Read<bool>(1);
buffer.SeekForward(4); // normalize before we read
buffer.ReadArray(&data, size * 8);
if (client && player)
{
g_battleye_service.receive_message(player->get_net_game_player()->get_host_token(), &data, size);
}
break;
}
default: default:
{ {
if ((int)msgType > 0x91) [[unlikely]] if ((int)msgType > 0x91) [[unlikely]]

View File

@ -12,18 +12,17 @@ namespace big
{ {
} }
void packet::send(uint32_t msg_id) void packet::send(uint32_t msg_id, bool unk_flag)
{ {
g_pointers->m_gta g_pointers->m_gta.m_queue_packet(gta_util::get_network()->m_game_session_ptr->m_net_connection_mgr, msg_id, m_data, (m_buffer.m_curBit + 7) >> 3, unk_flag ? 0x4000001 : 1, nullptr);
.m_queue_packet(gta_util::get_network()->m_game_session_ptr->m_net_connection_mgr, msg_id, m_data, (m_buffer.m_curBit + 7) >> 3, 1, nullptr);
} }
void packet::send(player_ptr player, int connection_id) void packet::send(player_ptr player, int connection_id)
{ {
send(player->get_session_player()->m_player_data.m_peer_id_2, connection_id); send_direct(player->get_session_player()->m_player_data.m_peer_id_2, connection_id);
} }
void packet::send(int peer_id, int connection_id) void packet::send_direct(int peer_id, int connection_id)
{ {
auto mgr = gta_util::get_network()->m_game_session_ptr->m_net_connection_mgr; auto mgr = gta_util::get_network()->m_game_session_ptr->m_net_connection_mgr;
auto peer = g_pointers->m_gta.m_get_connection_peer(mgr, peer_id); auto peer = g_pointers->m_gta.m_get_connection_peer(mgr, peer_id);

View File

@ -8,13 +8,13 @@ namespace big
class packet class packet
{ {
public: public:
char m_data[0x400]{}; char m_data[0x480]{};
rage::datBitBuffer m_buffer; rage::datBitBuffer m_buffer;
packet(); packet();
void send(uint32_t msg_id); void send(uint32_t msg_id, bool unk_flag = false);
void send(player_ptr player, int connection_id); void send(player_ptr player, int connection_id);
void send(int peer_id, int connection_id); void send_direct(int peer_id, int connection_id);
inline operator rage::datBitBuffer&() inline operator rage::datBitBuffer&()
{ {

View File

@ -0,0 +1,261 @@
#include "battleye_service.hpp"
#include "services/players/player_service.hpp"
#include "packet.hpp"
#include "script_mgr.hpp"
#include "thread_pool.hpp"
#include "util/session.hpp"
#include "backend/looped_command.hpp"
#include <network/snSession.hpp>
namespace
{
static std::string base64_encode(const std::string& data)
{
static constexpr char sEncodingTable[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
size_t in_len = data.size();
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char* p = ret.data();
for (i = 0; i < in_len - 2; i += 3)
{
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int)(data[i + 2] & 0xC0) >> 6)];
*p++ = sEncodingTable[data[i + 2] & 0x3F];
}
if (i < in_len)
{
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
if (i == (in_len - 1))
{
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else
{
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
}
namespace big
{
class battleye_server : looped_command
{
using looped_command::looped_command;
virtual void on_enable() override
{
g_battleye_service.start();
}
virtual void on_tick() override
{
}
virtual void on_disable() override
{
g_battleye_service.stop();
}
};
battleye_server g_battleye_server("battleyeserver", "BATTLEYE_SERVER", "BATTLEYE_SERVER_DESC", g.debug.battleye_server);
bool battleye_service::is_running()
{
return m_battleye_api.m_shutdown != nullptr;
}
void battleye_service::send_message(std::uint64_t token, void* message, int size)
{
packet pkt;
char header_buf[32];
rage::datBitBuffer header(header_buf, sizeof(header_buf));
header.Write<int>(size, 11);
header.Write<bool>(false, 1); // we are not the client
pkt.write_message(rage::eNetMessage::MsgBattlEyeCmd);
pkt.m_buffer.WriteArray(&header_buf, header.GetDataLength() * 8);
pkt.m_buffer.WriteArray(message, size * 8);
// send to player
if (auto plyr = g_player_service->get_by_host_token(token); plyr && plyr->get_session_player())
{
pkt.send(plyr->get_session_player()->m_msg_id, true);
}
}
void battleye_service::kick_player(std::uint64_t token, const char* reason)
{
if (auto plyr = g_player_service->get_by_host_token(token))
{
LOGF(WARNING, "BattlEye wants us to kick {} for {}", plyr->get_name(), reason);
if (!plyr->tampered_with_be)
{
session::add_infraction(plyr, Infraction::TRIGGERED_ANTICHEAT);
}
}
}
void battleye_service::script_func()
{
bool was_host = false;
while (is_running())
{
if (g_player_service->get_self()->is_valid())
{
bool is_host = g_player_service->get_self()->is_host();
if (is_host != was_host)
{
if (is_host)
{
for (auto& [_, player] : g_player_service->players())
{
add_player(player->get_net_game_player()->get_host_token(), player->get_rockstar_id(), player->get_name());
}
}
was_host = is_host;
}
}
big::script::get_current()->yield();
}
}
void battleye_service::thread_func()
{
while (is_running() && g_running)
{
// we need to run this every frame (lockstep with game or otherwise)
{
std::lock_guard lock(m_mutex);
if (is_running() && !m_battleye_api.m_run())
{
LOG(WARNING) << "BE::Run failed";
}
}
std::this_thread::yield();
}
}
battleye_service::battleye_service()
{
}
battleye_service::~battleye_service()
{
stop();
}
void battleye_service::start()
{
std::lock_guard lock(m_mutex);
if (is_running())
{
return;
}
auto handle = LoadLibraryA("BattlEye\\BEServer_x64.dll");
if (!handle)
{
LOG(WARNING) << "Failed to load BattlEye";
return;
}
m_battleye_user_data.m_game_name = "paradise";
m_battleye_user_data.m_log_func = [](const char* message, int level) {
LOG(INFO) << "BattlEye: " << message;
};
m_battleye_user_data.m_kick_player = [](std::uint64_t player, const char* reason) {
g_battleye_service.kick_player(player, reason);
};
m_battleye_user_data.m_send_message = [](std::uint64_t player, const void* pkt_data, int size) {
g_battleye_service.send_message(player, const_cast<void*>(pkt_data), size);
};
if (reinterpret_cast<init_t>(GetProcAddress(handle, "Init"))(1, &m_battleye_user_data, &m_battleye_api))
{
// background thread (not game thread)
g_thread_pool->push([this] { this->thread_func(); });
// background script (game thread)
g_script_mgr.add_script(std::make_unique<big::script>([this] { this->script_func(); }, "BE Background Script", false));
}
else
{
LOG(WARNING) << "Failed to initialize BattlEye";
}
}
void battleye_service::stop()
{
std::lock_guard lock(m_mutex);
if (!is_running())
{
return;
}
m_battleye_api.m_shutdown();
memset(&m_battleye_api, 0, sizeof(CApi));
}
void battleye_service::add_player(std::uint64_t token, std::uint64_t rockstar_id, const char* name)
{
std::lock_guard lock(m_mutex);
if (!is_running() || !g_player_service->get_self()->is_host())
{
return;
}
char string[32]{};
snprintf(string, sizeof(string), "%I64d", rockstar_id);
auto guid = base64_encode(string);
m_battleye_api.m_add_player(token, -1, 0, name, false);
m_battleye_api.m_assign_guid(token, guid.data(), guid.length());
m_battleye_api.m_assign_guid_verified(token, guid.data(), guid.length());
m_battleye_api.m_set_player_state(token, 1);
}
void battleye_service::remove_player(std::uint64_t token)
{
std::lock_guard lock(m_mutex);
if (!is_running())
{
return;
}
m_battleye_api.m_set_player_state(token, -1);
}
void battleye_service::receive_message(std::uint64_t token, void* message, int size)
{
std::lock_guard lock(m_mutex);
if (!is_running())
{
return;
}
m_battleye_api.m_receive_message(token, const_cast<const void*>(message), size);
}
}

View File

@ -0,0 +1,61 @@
#pragma once
namespace big
{
class battleye_service
{
using log_func_t = void (*)(const char* msg, int level);
using kick_player_t = void (*)(std::uint64_t id, const char* reason);
using send_message_t = void (*)(std::uint64_t id, const void* msg, int size);
using add_player_t = void (*)(std::uint64_t id, u_long ip_addr, u_short port, const char* name, char unused);
using set_player_state_t = void (*)(std::uint64_t id, int reason);
using assign_guid_t = void (*)(std::uint64_t id, const void* guid, int size); // binary
using receive_message_t = void (*)(std::uint64_t id, const void* packet, int size);
using shutdown_t = void (*)();
using run_t = bool (*)();
using run_command_t = void (*)(const char* command);
struct CUserData
{
const char* m_game_name;
log_func_t m_log_func;
kick_player_t m_kick_player;
send_message_t m_send_message;
void* m_unk;
} m_battleye_user_data{};
struct CApi
{
shutdown_t m_shutdown;
run_t m_run;
run_command_t m_run_command;
add_player_t m_add_player;
set_player_state_t m_set_player_state;
assign_guid_t m_assign_guid;
assign_guid_t m_assign_guid_verified;
receive_message_t m_receive_message;
} m_battleye_api{};
using init_t = bool (*)(int api_level, CUserData* data, CApi* api);
std::mutex m_mutex{};
bool is_running();
public:
battleye_service();
~battleye_service();
void start();
void stop();
void add_player(std::uint64_t token, std::uint64_t rockstar_id, const char* name);
void remove_player(std::uint64_t token);
void receive_message(std::uint64_t token, void* message, int size);
void send_message(std::uint64_t token, void* message, int size);
void kick_player(std::uint64_t token, const char* reason);
void script_func();
void thread_func();
};
inline battleye_service g_battleye_service;
}

View File

@ -103,6 +103,8 @@ namespace big
int spectating_player = -1; int spectating_player = -1;
bool tampered_with_be = false;
menu_settings::script_block_opts script_block_opts{}; menu_settings::script_block_opts script_block_opts{};
protected: protected:
bool equals(const CNetGamePlayer* net_game_player) const; bool equals(const CNetGamePlayer* net_game_player) const;

View File

@ -5,6 +5,12 @@
#include "util/system.hpp" #include "util/system.hpp"
#include "view_debug.hpp" #include "view_debug.hpp"
#include "network/CNetworkPlayerMgr.hpp" #include "network/CNetworkPlayerMgr.hpp"
#include "thread_pool.hpp"
#include "packet.hpp"
#include "script_mgr.hpp"
#include "util/session.hpp"
#include <network/snSession.hpp>
namespace big namespace big
{ {
@ -12,6 +18,7 @@ namespace big
{ {
if (ImGui::BeginTabItem("DEBUG_TAB_MISC"_T.data())) if (ImGui::BeginTabItem("DEBUG_TAB_MISC"_T.data()))
{ {
components::command_checkbox<"battleyeserver">();
components::command_checkbox<"external_console">(); components::command_checkbox<"external_console">();
components::command_checkbox<"windowhook">("VIEW_DEBUG_MISC_DISABLE_GTA_WINDOW_HOOK"_T); components::command_checkbox<"windowhook">("VIEW_DEBUG_MISC_DISABLE_GTA_WINDOW_HOOK"_T);

View File

@ -28,5 +28,7 @@ namespace big
components::player_command_button<"endkick">(g_player_service->get_selected()); components::player_command_button<"endkick">(g_player_service->get_selected());
ImGui::SameLine(); ImGui::SameLine();
components::player_command_button<"desync">(g_player_service->get_selected()); components::player_command_button<"desync">(g_player_service->get_selected());
ImGui::SameLine();
components::player_command_button<"battleupdate">(g_player_service->get_selected());
} }
} }