diff --git a/.clang-format b/.clang-format index a0ec550c..0c7c0a9e 100644 --- a/.clang-format +++ b/.clang-format @@ -33,6 +33,7 @@ IncludeBlocks: Regroup IndentCaseLabels: 'false' IndentPPDirectives: BeforeHash IndentWidth: '4' +InsertBraces: true TabWidth: 4 IndentWrappedFunctionNames: 'true' KeepEmptyLinesAtTheStartOfBlocks: 'false' diff --git a/cmake/gtav-classes.cmake b/cmake/gtav-classes.cmake index d1d028ad..27bd4c1a 100644 --- a/cmake/gtav-classes.cmake +++ b/cmake/gtav-classes.cmake @@ -3,7 +3,7 @@ include(FetchContent) FetchContent_Declare( gtav_classes GIT_REPOSITORY https://github.com/Yimura/GTAV-Classes.git - GIT_TAG e45f7f505ac2394c9213f1577831ab100cc74fa2 + GIT_TAG b1a2ef201bdbd8b4171e932d5c8de68cc9df0283 GIT_PROGRESS TRUE CONFIGURE_COMMAND "" BUILD_COMMAND "" diff --git a/src/backend/commands/player/kick/battleye.cpp b/src/backend/commands/player/kick/battleye.cpp index 025af9ef..40ea4b4b 100644 --- a/src/backend/commands/player/kick/battleye.cpp +++ b/src/backend/commands/player/kick/battleye.cpp @@ -1,6 +1,7 @@ #include "backend/player_command.hpp" #include "gta_util.hpp" #include "pointers.hpp" +#include "services/battleye/battleye_service.hpp" #include @@ -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 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_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); } \ No newline at end of file diff --git a/src/core/data/packet_types.hpp b/src/core/data/packet_types.hpp index c7dad731..e90269ba 100644 --- a/src/core/data/packet_types.hpp +++ b/src/core/data/packet_types.hpp @@ -114,8 +114,9 @@ inline const static constexpr std::pair packet_types[] = {"Msg_0x89", 0x89}, {"Msg_0x86", 0x86}, {"MsgDtlsCxnCommand", 0x8A}, - {"MsgSetKickVote", 0x8E}, - {"MsgTransitionHandshake", 0x8D}, + {"MsgSetKickVote", 0x8E}, + {"MsgTransitionHandshake", 0x8D}, {"MsgDidInvitePlayerRequest", 0x8B}, {"MsgDidInvitePlayerResponse", 0x8C}, + {"MsgBattlEyeCmd", 0x8F}, }; \ No newline at end of file diff --git a/src/core/settings.hpp b/src/core/settings.hpp index 7c609f83..087dfa9d 100644 --- a/src/core/settings.hpp +++ b/src/core/settings.hpp @@ -150,8 +150,9 @@ namespace big bool external_console = true; bool window_hook = 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{}; struct tunables diff --git a/src/gta/net_game_event.hpp b/src/gta/net_game_event.hpp index 89802763..6b175231 100644 --- a/src/gta/net_game_event.hpp +++ b/src/gta/net_game_event.hpp @@ -443,6 +443,7 @@ namespace rage Msg_0x45 = 0x45, Msg_0x89 = 0x89, Msg_0x86 = 0x86, + MsgBattlEyeCmd = 0x8F, }; enum class eEventNetworkType : int64_t diff --git a/src/hooks/player_management/assign_physical_index.cpp b/src/hooks/player_management/assign_physical_index.cpp index d3e04b2a..29b05c24 100644 --- a/src/hooks/player_management/assign_physical_index.cpp +++ b/src/hooks/player_management/assign_physical_index.cpp @@ -5,10 +5,10 @@ #include "lua/lua_manager.hpp" #include "services/player_database/player_database_service.hpp" #include "services/players/player_service.hpp" +#include "services/battleye/battleye_service.hpp" #include "util/notify.hpp" #include "util/session.hpp" - namespace big { inline bool is_spoofed_host_token(rage::rlGamerInfo* info) @@ -25,6 +25,7 @@ namespace big if (new_index == static_cast(-1)) { + g_battleye_service.remove_player(player->get_host_token()); g_player_service->player_leave(player); if (net_player_data) @@ -50,6 +51,8 @@ namespace big g_player_service->player_join(player); 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 (admin_rids.contains(net_player_data->m_gamer_handle.m_rockstar_id)) diff --git a/src/hooks/player_management/network_player_mgr.cpp b/src/hooks/player_management/network_player_mgr.cpp index 34f1d657..eafeeb88 100644 --- a/src/hooks/player_management/network_player_mgr.cpp +++ b/src/hooks/player_management/network_player_mgr.cpp @@ -2,6 +2,7 @@ #include "hooking/hooking.hpp" #include "lua/lua_manager.hpp" #include "services/players/player_service.hpp" +#include "services/battleye/battleye_service.hpp" #include @@ -25,6 +26,14 @@ namespace big 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(); self::spawned_vehicles.clear(); diff --git a/src/hooks/protections/receive_net_message.cpp b/src/hooks/protections/receive_net_message.cpp index 76739ae1..cb42cf61 100644 --- a/src/hooks/protections/receive_net_message.cpp +++ b/src/hooks/protections/receive_net_message.cpp @@ -8,6 +8,7 @@ #include "lua/lua_manager.hpp" #include "natives.hpp" #include "services/players/player_service.hpp" +#include "services/battleye/battleye_service.hpp" #include "util/chat.hpp" #include "util/session.hpp" #include "gta/net_object_mgr.hpp" @@ -739,6 +740,22 @@ namespace big return true; } + case rage::eNetMessage::MsgBattlEyeCmd: + { + char data[1028]{}; + int size = buffer.Read(11); + bool client = buffer.Read(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: { if ((int)msgType > 0x91) [[unlikely]] diff --git a/src/packet.cpp b/src/packet.cpp index ea45a917..1045367e 100644 --- a/src/packet.cpp +++ b/src/packet.cpp @@ -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 - .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); + 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); } 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 peer = g_pointers->m_gta.m_get_connection_peer(mgr, peer_id); diff --git a/src/packet.hpp b/src/packet.hpp index 1599be7e..be7c57b6 100644 --- a/src/packet.hpp +++ b/src/packet.hpp @@ -8,13 +8,13 @@ namespace big class packet { public: - char m_data[0x400]{}; + char m_data[0x480]{}; rage::datBitBuffer m_buffer; 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(int peer_id, int connection_id); + void send_direct(int peer_id, int connection_id); inline operator rage::datBitBuffer&() { diff --git a/src/services/battleye/battleye_service.cpp b/src/services/battleye/battleye_service.cpp new file mode 100644 index 00000000..0ed4e301 --- /dev/null +++ b/src/services/battleye/battleye_service.cpp @@ -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 + +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(size, 11); + header.Write(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(pkt_data), size); + }; + if (reinterpret_cast(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([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(message), size); + } +} \ No newline at end of file diff --git a/src/services/battleye/battleye_service.hpp b/src/services/battleye/battleye_service.hpp new file mode 100644 index 00000000..eedc9907 --- /dev/null +++ b/src/services/battleye/battleye_service.hpp @@ -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; +} \ No newline at end of file diff --git a/src/services/players/player.hpp b/src/services/players/player.hpp index 78dc4dec..bd0fd5ad 100644 --- a/src/services/players/player.hpp +++ b/src/services/players/player.hpp @@ -103,6 +103,8 @@ namespace big int spectating_player = -1; + bool tampered_with_be = false; + menu_settings::script_block_opts script_block_opts{}; protected: bool equals(const CNetGamePlayer* net_game_player) const; diff --git a/src/views/debug/view_debug_misc.cpp b/src/views/debug/view_debug_misc.cpp index fed05ed4..0b45fa75 100644 --- a/src/views/debug/view_debug_misc.cpp +++ b/src/views/debug/view_debug_misc.cpp @@ -5,6 +5,12 @@ #include "util/system.hpp" #include "view_debug.hpp" #include "network/CNetworkPlayerMgr.hpp" +#include "thread_pool.hpp" +#include "packet.hpp" +#include "script_mgr.hpp" +#include "util/session.hpp" + +#include namespace big { @@ -12,6 +18,7 @@ namespace big { if (ImGui::BeginTabItem("DEBUG_TAB_MISC"_T.data())) { + components::command_checkbox<"battleyeserver">(); components::command_checkbox<"external_console">(); components::command_checkbox<"windowhook">("VIEW_DEBUG_MISC_DISABLE_GTA_WINDOW_HOOK"_T); diff --git a/src/views/players/player/player_kick.cpp b/src/views/players/player/player_kick.cpp index 692ef64d..47dee73a 100644 --- a/src/views/players/player/player_kick.cpp +++ b/src/views/players/player/player_kick.cpp @@ -28,5 +28,7 @@ namespace big components::player_command_button<"endkick">(g_player_service->get_selected()); ImGui::SameLine(); components::player_command_button<"desync">(g_player_service->get_selected()); + ImGui::SameLine(); + components::player_command_button<"battleupdate">(g_player_service->get_selected()); } }