From c5a6fc711f4e4395d18a7f62aa2798dc160d4808 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Sat, 2 Mar 2024 17:10:21 +0100 Subject: [PATCH] add client network --- CMakeLists.txt | 2 + src/ClientNetwork.cpp | 183 +++++++++++++ src/ClientNetwork.h | 44 +++ src/Identity.cpp | 2 +- src/Launcher.cpp | 612 ------------------------------------------ src/Launcher.h | 40 --- src/ServerNetwork.cpp | 11 +- src/ServerNetwork.h | 4 +- src/main.cpp | 23 +- 9 files changed, 249 insertions(+), 672 deletions(-) create mode 100644 src/ClientNetwork.cpp create mode 100644 src/ClientNetwork.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d65c6b..d7971d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ set(PRJ_HEADERS src/Compression.h src/Config.h src/ServerNetwork.h + src/ClientNetwork.h ) # add all source files (.cpp) to this, except the one with main() set(PRJ_SOURCES @@ -49,6 +50,7 @@ set(PRJ_SOURCES src/Hashing.cpp src/Config.cpp src/ServerNetwork.cpp + src/ClientNetwork.cpp ) # set the source file containing main() set(PRJ_MAIN src/main.cpp) diff --git a/src/ClientNetwork.cpp b/src/ClientNetwork.cpp new file mode 100644 index 0000000..754d5b4 --- /dev/null +++ b/src/ClientNetwork.cpp @@ -0,0 +1,183 @@ +#include "ClientNetwork.h" +#include "ClientPacket.h" + +#include +#include + +using json = nlohmann::json; + +void ClientNetwork::run() { + ip::tcp::endpoint listen_ep(ip::address::from_string("0.0.0.0"), static_cast(m_listen_port)); + ip::tcp::socket listener(m_io); + boost::system::error_code ec; + listener.open(listen_ep.protocol(), ec); + if (ec) { + spdlog::error("Failed to open socket: {}", ec.message()); + return; + } + socket_base::linger linger_opt {}; + linger_opt.enabled(false); + listener.set_option(linger_opt, ec); + if (ec) { + spdlog::error("Failed to set up listening socket to not linger / reuse address. " + "This may cause the socket to refuse to bind(). spdlog::error: {}", + ec.message()); + } + + ip::tcp::acceptor acceptor(m_io, listen_ep); + acceptor.listen(socket_base::max_listen_connections, ec); + if (ec) { + spdlog::error("listen() failed, which is needed for the launcher to operate. " + "Shutting down. spdlog::error: {}", + ec.message()); + std::this_thread::sleep_for(std::chrono::seconds(3)); + std::exit(1); + } + do { + try { + spdlog::info("Waiting for the game to connect"); + ip::tcp::endpoint game_ep; + auto game = acceptor.accept(game_ep, ec); + if (ec) { + spdlog::error("Failed to accept: {}", ec.message()); + } + spdlog::info("Game connected!"); + spdlog::debug("Game: [{}]:{}", game_ep.address().to_string(), game_ep.port()); + handle_connection(std::move(game)); + spdlog::warn("Game disconnected!"); + } catch (const std::exception& e) { + spdlog::error("Fatal error in core network: {}", e.what()); + } + } while (!*m_shutdown); +} + +ClientNetwork::ClientNetwork(uint16_t port) + : m_listen_port(port) { + spdlog::debug("Client network created"); +} + +ClientNetwork::~ClientNetwork() { + spdlog::debug("Client network destroyed"); +} + +void ClientNetwork::handle_connection(ip::tcp::socket&& socket) { + // immediately send launcher info (first step of client identification) + m_client_state = bmp::ClientState::ClientIdentification; + + bmp::ClientPacket info_packet {}; + info_packet.purpose = bmp::ClientPurpose::LauncherInfo; + info_packet.raw_data = json_to_vec( + { + { "implementation", "Official BeamMP Launcher" }, + { "version", { PRJ_VERSION_MAJOR, PRJ_VERSION_MINOR, PRJ_VERSION_PATCH } }, + { "mod_cache_path", "/idk sorry/" }, // TODO: mod_cache_path in LauncherInfo + }); + client_tcp_write(socket, info_packet); + + // packet read and respond loop + while (!*m_shutdown) { + auto packet = client_tcp_read(socket); + handle_packet(socket, packet); + } +} + +void ClientNetwork::handle_packet(ip::tcp::socket& socket, bmp::ClientPacket& packet) { + spdlog::trace("Got client packet: purpose: 0x{:x}, flags: 0x{:x}, pid: {}, vid: {}, size: {}", + uint16_t(packet.purpose), + uint8_t(packet.flags), + packet.pid, packet.vid, + packet.get_readable_data().size()); + spdlog::trace("Client State: 0x{:x}", int(m_client_state)); + + switch (m_client_state) { + case bmp::ClientIdentification: + handle_client_identification(socket, packet); + break; + case bmp::Login: + handle_login(socket, packet); + break; + case bmp::QuickJoin: + handle_quick_join(socket, packet); + break; + case bmp::Browsing: + handle_browsing(socket, packet); + break; + case bmp::ServerIdentification: + handle_server_identification(socket, packet); + break; + case bmp::ServerAuthentication: + handle_server_authentication(socket, packet); + break; + case bmp::ServerModDownload: + handle_server_mod_download(socket, packet); + break; + case bmp::ServerSessionSetup: + handle_server_session_setup(socket, packet); + break; + case bmp::ServerPlaying: + handle_server_playing(socket, packet); + break; + case bmp::ServerLeaving: + handle_server_leaving(socket, packet); + break; + } +} + +void ClientNetwork::handle_client_identification(ip::tcp::socket& socket, bmp::ClientPacket& packet) { +} + +void ClientNetwork::handle_login(ip::tcp::socket& socket, bmp::ClientPacket& packet) { +} + +void ClientNetwork::handle_quick_join(ip::tcp::socket& socket, bmp::ClientPacket& packet) { +} + +void ClientNetwork::handle_browsing(ip::tcp::socket& socket, bmp::ClientPacket& packet) { +} + +void ClientNetwork::handle_server_identification(ip::tcp::socket& socket, bmp::ClientPacket& packet) { +} + +void ClientNetwork::handle_server_authentication(ip::tcp::socket& socket, bmp::ClientPacket& packet) { +} + +void ClientNetwork::handle_server_mod_download(ip::tcp::socket& socket, bmp::ClientPacket& packet) { +} + +void ClientNetwork::handle_server_session_setup(ip::tcp::socket& socket, bmp::ClientPacket& packet) { +} + +void ClientNetwork::handle_server_playing(ip::tcp::socket& socket, bmp::ClientPacket& packet) { +} + +void ClientNetwork::handle_server_leaving(ip::tcp::socket& socket, bmp::ClientPacket& packet) { +} + +bmp::ClientPacket ClientNetwork::client_tcp_read(ip::tcp::socket& socket) { + bmp::ClientPacket packet {}; + std::vector header_buffer(bmp::ClientHeader::SERIALIZED_SIZE); + read(socket, buffer(header_buffer)); + bmp::ClientHeader hdr {}; + hdr.deserialize_from(header_buffer); + // vector eaten up by now, recv again + packet.raw_data.resize(hdr.data_size); + read(socket, buffer(packet.raw_data)); + packet.purpose = hdr.purpose; + packet.flags = hdr.flags; + return packet; +} + +void ClientNetwork::client_tcp_write(ip::tcp::socket& socket, bmp::ClientPacket& packet) { + // finalize the packet (compress etc) and produce header + auto header = packet.finalize(); + // serialize header + std::vector header_data(bmp::ClientHeader::SERIALIZED_SIZE); + header.serialize_to(header_data); + // write header and packet data + write(socket, buffer(header_data)); + write(socket, buffer(packet.raw_data)); +} +std::vector ClientNetwork::json_to_vec(const nlohmann::json& value) { + auto str = value.dump(); + return std::vector(str.begin(), str.end()); +} diff --git a/src/ClientNetwork.h b/src/ClientNetwork.h new file mode 100644 index 0000000..82cacc8 --- /dev/null +++ b/src/ClientNetwork.h @@ -0,0 +1,44 @@ +#pragma once + +#include "ClientPacket.h" +#include "ClientState.h" +#include "Launcher.h" +#include "Sync.h" + +#include +#include + +using namespace boost::asio; + +class ClientNetwork { +public: + ClientNetwork(uint16_t port); + + ~ClientNetwork(); + + void run(); + +private: + void handle_connection(ip::tcp::socket&& socket); + bmp::ClientPacket client_tcp_read(ip::tcp::socket& socket); + void client_tcp_write(ip::tcp::socket& socket, bmp::ClientPacket& packet); + + void handle_packet(ip::tcp::socket& socket, bmp::ClientPacket& packet); + void handle_client_identification(ip::tcp::socket& socket, bmp::ClientPacket& packet); + void handle_login(ip::tcp::socket& socket, bmp::ClientPacket& packet); + void handle_quick_join(ip::tcp::socket& socket, bmp::ClientPacket& packet); + void handle_browsing(ip::tcp::socket& socket, bmp::ClientPacket& packet); + void handle_server_identification(ip::tcp::socket& socket, bmp::ClientPacket& packet); + void handle_server_authentication(ip::tcp::socket& socket, bmp::ClientPacket& packet); + void handle_server_mod_download(ip::tcp::socket& socket, bmp::ClientPacket& packet); + void handle_server_session_setup(ip::tcp::socket& socket, bmp::ClientPacket& packet); + void handle_server_playing(ip::tcp::socket& socket, bmp::ClientPacket& packet); + void handle_server_leaving(ip::tcp::socket& socket, bmp::ClientPacket& packet); + + static std::vector json_to_vec(const nlohmann::json& json); + + uint16_t m_listen_port {}; + io_context m_io {}; + Sync m_shutdown { false }; + bmp::ClientState m_client_state; +}; diff --git a/src/Identity.cpp b/src/Identity.cpp index a4fa969..5da27b0 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -33,7 +33,7 @@ void Identity::check_local_key() { if (Buffer == "-1" || Buffer.at(0) != '{' || d.is_discarded()) { spdlog::error(Buffer); - spdlog::info("Invalid answer from authentication servers."); + spdlog::info("Invalid answer from authentication servers. Check your internet connection and see if you can reach https://beammp.com."); update_key(nullptr); } if (d["success"].get()) { diff --git a/src/Launcher.cpp b/src/Launcher.cpp index f493cab..8488e7a 100644 --- a/src/Launcher.cpp +++ b/src/Launcher.cpp @@ -83,8 +83,6 @@ void Launcher::proxy_main() { }); pattern += "/:any" + std::to_string(i); } - m_proxy_port = HTTPProxy.bind_to_any_port("0.0.0.0"); - spdlog::debug("http proxy started on port {}", m_proxy_port.get()); HTTPProxy.listen_after_bind(); } @@ -96,11 +94,6 @@ Launcher::~Launcher() { void Launcher::parse_config() { } -void Launcher::set_port(int p) { - spdlog::warn("Using custom port {}", p); - m_config->port = p; -} - void Launcher::check_for_updates(int argc, char** argv) { std::string LatestHash = HTTP::Get(fmt::format("https://backend.beammp.com/sha/launcher?branch={}&pk={}", m_config->branch, m_identity->PublicKey)); std::string LatestVersion = HTTP::Get(fmt::format("https://backend.beammp.com/version/launcher?branch={}&pk={}", m_config->branch, m_identity->PublicKey)); @@ -284,610 +277,5 @@ void Launcher::game_main() { void Launcher::start_game() { m_game_thread = boost::scoped_thread<>(&Launcher::game_main, this); } -void Launcher::start_network() { - while (true) { - try { - net_core_main(); - } catch (const std::exception& e) { - spdlog::error("Error: {}", e.what()); - } - std::this_thread::sleep_for(std::chrono::seconds(1)); - } -} -void Launcher::reset_status() { - *m_m_status = " "; - *m_ul_status = "Ulstart"; - m_conf_list->clear(); - *m_ping = -1; -} - -void Launcher::net_core_main() { - ip::tcp::endpoint listen_ep(ip::address::from_string("0.0.0.0"), static_cast(m_config->port)); - ip::tcp::socket listener(m_io); - boost::system::error_code ec; - listener.open(listen_ep.protocol(), ec); - if (ec) { - spdlog::error("Failed to open socket: {}", ec.message()); - return; - } - socket_base::linger linger_opt {}; - linger_opt.enabled(false); - listener.set_option(linger_opt, ec); - if (ec) { - spdlog::error("Failed to set up listening socket to not linger / reuse address. " - "This may cause the socket to refuse to bind(). spdlog::error: {}", - ec.message()); - } - - ip::tcp::acceptor acceptor(m_io, listen_ep); - acceptor.listen(socket_base::max_listen_connections, ec); - if (ec) { - spdlog::error("listen() failed, which is needed for the launcher to operate. " - "Shutting down. spdlog::error: {}", - ec.message()); - std::this_thread::sleep_for(std::chrono::seconds(3)); - std::exit(1); - } - do { - try { - spdlog::info("Waiting for the game to connect"); - ip::tcp::endpoint game_ep; - m_game_socket = acceptor.accept(game_ep, ec); - if (ec) { - spdlog::error("Failed to accept: {}", ec.message()); - } - reset_status(); - spdlog::info("Game connected!"); - spdlog::debug("Game: [{}]:{}", game_ep.address().to_string(), game_ep.port()); - game_loop(); - spdlog::warn("Game disconnected!"); - } catch (const std::exception& e) { - spdlog::error("Fatal error in core network: {}", e.what()); - } - } while (!*m_shutdown); -} - -void Launcher::game_loop() { - size_t Size; - size_t Temp; - size_t Rcv; - char Header[10] = { 0 }; - boost::system::error_code ec; - do { - Rcv = 0; - do { - Temp = boost::asio::read(m_game_socket, buffer(&Header[Rcv], 1), ec); - if (ec) { - spdlog::error("(Core) Failed to receive from game: {}", ec.message()); - break; - } - if (Temp < 1) - break; - if (!isdigit(Header[Rcv]) && Header[Rcv] != '>') { - spdlog::error("(Core) Invalid lua communication: '{}'", std::string(Header, Rcv)); - break; - } - } while (Header[Rcv++] != '>'); - if (Temp < 1) - break; - if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') { - spdlog::debug("(Core) Invalid lua Header: '{}'", std::string(Header, Rcv)); - break; - } - std::vector Ret(Size, 0); - Rcv = 0; - - Temp = boost::asio::read(m_game_socket, buffer(Ret, Size), ec); - if (ec) { - spdlog::error("(Core) Failed to receive from game: {}", ec.message()); - break; - } - handle_core_packet(Ret); - } while (Temp > 0); - if (Temp == 0) { - spdlog::debug("(Core) Connection closing"); - } - // TODO: NetReset -} - -static bool IsAllowedLink(const std::string& Link) { - std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|discord\.gg))"); - std::smatch link_match; - return std::regex_search(Link, link_match, link_pattern) && link_match.position() == 0; -} - -void Launcher::handle_core_packet(const std::vector& RawData) { - std::string Data(RawData.begin(), RawData.end()); - - char Code = Data.at(0), SubCode = 0; - if (Data.length() > 1) - SubCode = Data.at(1); - switch (Code) { - case 'A': - Data = Data.substr(0, 1); - break; - case 'B': - Data = Code + HTTP::Get("https://backend.beammp.com/servers-info"); - break; - case 'C': - m_list_of_mods->clear(); - if (!start_sync(Data)) { - // TODO: Handle - spdlog::error("start_sync failed, spdlog::error case not implemented"); - } - while (m_list_of_mods->empty() && !*m_shutdown) { - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - if (m_list_of_mods.get() == "-") - Data = "L"; - else - Data = "L" + m_list_of_mods.get(); - break; - case 'O': // open default browser with URL - if (IsAllowedLink(Data.substr(1))) { - spdlog::info("Opening Link \"" + Data.substr(1) + "\""); - boost::process::child c(std::string("open '") + Data.substr(1) + "'", boost::process::std_out > boost::process::null); - c.detach(); - } - Data.clear(); - break; - case 'P': - Data = Code + std::to_string(*m_proxy_port); - break; - case 'U': - if (SubCode == 'l') - Data = *m_ul_status; - if (SubCode == 'p') { - if (*m_ping > 800) { - Data = "Up-2"; - } else - Data = "Up" + std::to_string(*m_ping); - } - if (!SubCode) { - std::string Ping; - if (*m_ping > 800) - Ping = "-2"; - else - Ping = std::to_string(*m_ping); - Data = std::string(*m_ul_status) + "\n" + "Up" + Ping; - } - break; - case 'M': - Data = *m_m_status; - break; - case 'Q': - if (SubCode == 'S') { - spdlog::debug("Shutting down via QS"); - *m_shutdown = true; - *m_ping = -1; - } - if (SubCode == 'G') { - spdlog::debug("Shutting down via QG"); - *m_shutdown = true; - } - Data.clear(); - break; - case 'R': // will send mod name - if (m_conf_list->find(Data) == m_conf_list->end()) { - m_conf_list->insert(Data); - m_mod_loaded = true; - } - Data.clear(); - break; - case 'Z': - Data = fmt::format("Z{}.{}", PRJ_VERSION_MAJOR, PRJ_VERSION_MINOR); - break; - case 'N': - if (SubCode == 'c') { - Data = "N{\"Auth\":" + std::to_string(m_identity->LoginAuth) + "}"; - } else { - Data = "N" + m_identity->login(Data.substr(Data.find(':') + 1)); - } - break; - default: - Data.clear(); - break; - } - if (!Data.empty() && m_game_socket.is_open()) { - boost::system::error_code ec; - boost::asio::write(m_game_socket, buffer((Data + "\n").c_str(), Data.size() + 1), ec); - if (ec) { - spdlog::error("(Core) send failed with error: {}", ec.message()); - } - } -} - -static void compress_properly(std::vector& Data) { - constexpr std::string_view ABG = "ABG:"; - auto CombinedData = std::vector(ABG.begin(), ABG.end()); - auto CompData = Comp(Data); - CombinedData.resize(ABG.size() + CompData.size()); - std::copy(CompData.begin(), CompData.end(), CombinedData.begin() + ABG.size()); - Data = CombinedData; -} - -void Launcher::send_large(const std::string& Data) { - std::vector vec(Data.data(), Data.data() + Data.size()); - compress_properly(vec); - tcp_send(vec); -} - -void Launcher::server_send(const std::string& Data, bool Rel) { - if (Data.empty()) - return; - if (Data.find("Zp") != std::string::npos && Data.size() > 500) { - abort(); - } - char C = 0; - bool Ack = false; - int DLen = int(Data.length()); - if (DLen > 3) { - C = Data.at(0); - } - if (C == 'O' || C == 'T') { - Ack = true; - } - if (C == 'N' || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || C == 'C') { - Rel = true; - } - if (Ack || Rel) { - if (Ack || DLen > 1000) { - send_large(Data); - } else { - tcp_send(Data); - } - } else { - udp_send(Data); - } - - if (DLen > 1000) { - spdlog::debug("(Launcher->Server) Bytes sent: " + std::to_string(Data.length()) + " : " - + Data.substr(0, 10) - + Data.substr(Data.length() - 10)); - } else if (C == 'Z') { - // spdlog::debug("(Game->Launcher) : " + Data); - } -} - -void Launcher::tcp_game_main() { - spdlog::debug("Game server starting on port {}", m_config->port + 1); - ip::tcp::endpoint listen_ep(ip::address::from_string("0.0.0.0"), static_cast(m_config->port + 1)); - ip::tcp::socket g_listener(m_io); - g_listener.open(listen_ep.protocol()); - socket_base::linger linger_opt {}; - linger_opt.enabled(false); - g_listener.set_option(linger_opt); - ip::tcp::acceptor g_acceptor(m_io, listen_ep); - g_acceptor.listen(socket_base::max_listen_connections); - spdlog::debug("Game server listening"); - - while (!*m_shutdown) { - spdlog::debug("Main loop"); - - // socket is connected at this point, spawn thread - m_client_thread = boost::scoped_thread<>(&Launcher::tcp_client_main, this); - spdlog::debug("Client thread spawned"); - - m_core_socket = g_acceptor.accept(); - spdlog::debug("Game connected (tcp game)!"); - - spdlog::info("Successfully connected"); - - m_ping_thread = boost::scoped_thread<>(&Launcher::auto_ping, this); - m_udp_thread = boost::scoped_thread<>(&Launcher::udp_main, this); - - int32_t Temp = 0; - do { - boost::system::error_code ec; - int32_t Rcv = 0; - int32_t Size = 0; - char Header[10] = { 0 }; - do { - Temp = boost::asio::read(m_core_socket, buffer(&Header[Rcv], 1), ec); - if (ec) { - spdlog::error("(Game) Failed to receive from game: {}", ec.message()); - break; - } - if (Temp < 1) - break; - if (!isdigit(Header[Rcv]) && Header[Rcv] != '>') { - spdlog::error("(Game) Invalid lua communication"); - break; - } - } while (Header[Rcv++] != '>'); - if (Temp < 1) - break; - if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') { - spdlog::debug("(Game) Invalid lua Header -> " + std::string(Header, Rcv)); - break; - } - std::vector Ret(Size, 0); - Rcv = 0; - - Temp = boost::asio::read(m_core_socket, buffer(Ret, Size), ec); - if (ec) { - spdlog::error("(Game) Failed to receive from game: {}", ec.message()); - break; - } - spdlog::debug("Got {} from the game, sending to server", Ret[0]); - server_send(std::string(Ret.begin(), Ret.end()), false); - } while (Temp > 0); - if (Temp == 0) { - spdlog::debug("(Proxy) Connection closing"); - } else { - spdlog::debug("(Proxy) Recv failed"); - } - } - spdlog::debug("Game server exiting"); -} - -bool Launcher::start_sync(const std::string& Data) { - ip::tcp::resolver resolver(m_io); - const auto sep = Data.find_last_of(':'); - if (sep == std::string::npos || sep == Data.length() - 1) { - spdlog::error("Invalid host:port string: '{}'", Data); - return false; - } - const auto host = Data.substr(1, sep - 1); - const auto service = Data.substr(sep + 1); - boost::system::error_code ec; - auto resolved = resolver.resolve(host, service, ec); - if (ec) { - spdlog::error("Failed to resolve '{}': {}", Data.substr(1), ec.message()); - return false; - } - bool connected = false; - for (const auto& addr : resolved) { - m_tcp_socket.connect(addr.endpoint(), ec); - if (!ec) { - spdlog::info("Resolved and connected to '[{}]:{}'", - addr.endpoint().address().to_string(), - addr.endpoint().port()); - connected = true; - if (addr.endpoint().address().is_v4()) { - m_udp_socket.open(ip::udp::v4()); - } else { - m_udp_socket.open(ip::udp::v6()); - } - m_udp_endpoint = ip::udp::endpoint(m_tcp_socket.remote_endpoint().address(), m_tcp_socket.remote_endpoint().port()); - break; - } - } - if (!connected) { - spdlog::error("Failed to connect to '{}'", Data); - return false; - } - reset_status(); - m_identity->check_local_key(); - *m_ul_status = "UlLoading..."; - - auto thread = boost::scoped_thread<>(&Launcher::tcp_game_main, this); - m_tcp_game_thread.swap(thread); - - return true; -} - -void Launcher::tcp_send(const std::vector& data) { - const auto Size = int32_t(data.size()); - std::vector ToSend; - ToSend.resize(data.size() + sizeof(Size)); - std::memcpy(ToSend.data(), &Size, sizeof(Size)); - std::memcpy(ToSend.data() + sizeof(Size), data.data(), data.size()); - boost::system::error_code ec; - spdlog::debug("tcp sending {} bytes to the server", data.size()); - boost::asio::write(m_tcp_socket, buffer(ToSend), ec); - if (ec) { - spdlog::debug("write(): {}", ec.message()); - throw std::runtime_error(fmt::format("write() failed: {}", ec.message())); - } - spdlog::debug("tcp sent {} bytes to the server", data.size()); -} -void Launcher::tcp_send(const std::string& data) { - tcp_send(std::vector(data.begin(), data.end())); -} - -std::string Launcher::tcp_recv() { - int32_t Header {}; - - boost::system::error_code ec; - std::array HeaderData {}; - boost::asio::read(m_tcp_socket, buffer(HeaderData), ec); - if (ec) { - throw std::runtime_error(fmt::format("read() failed: {}", ec.message())); - } - Header = *reinterpret_cast(HeaderData.data()); - - if (Header < 0) { - throw std::runtime_error("read() failed: Negative TCP header"); - } - - std::vector Data; - // 100 MiB is super arbitrary but what can you do. - if (Header < int32_t(100 * (1024 * 1024))) { - Data.resize(Header); - } else { - throw std::runtime_error("Header size limit exceeded"); - } - auto N = boost::asio::read(m_tcp_socket, buffer(Data), ec); - if (ec) { - throw std::runtime_error(fmt::format("read() failed: {}", ec.message())); - } - - if (N != Header) { - throw std::runtime_error(fmt::format("read() failed: Expected {} byte(s), got {} byte(s) instead", Header, N)); - } - - constexpr std::string_view ABG = "ABG:"; - if (Data.size() >= ABG.size() && std::equal(Data.begin(), Data.begin() + ABG.size(), ABG.begin(), ABG.end())) { - Data.erase(Data.begin(), Data.begin() + ABG.size()); - Data = DeComp(Data); - } - return { reinterpret_cast(Data.data()), Data.size() }; -} - -void Launcher::game_send(const std::string& data) { - auto to_send = data + "\n"; - boost::asio::write(m_core_socket, buffer(reinterpret_cast(to_send.data()), to_send.size())); -} - -void Launcher::tcp_client_main() { - spdlog::debug("Client starting"); - - boost::system::error_code ec; - - try { - { - // send C to say hi - boost::asio::write(m_tcp_socket, buffer("C", 1), ec); - - // client version - tcp_send(fmt::format("VC{}.{}", PRJ_VERSION_MAJOR, PRJ_VERSION_MINOR)); - - // auth - auto res = tcp_recv(); - if (res.empty() || res[0] == 'E') { - throw std::runtime_error("Kicked!"); - } else if (res[0] == 'K') { - if (res.size() > 1) { - throw std::runtime_error(fmt::format("Kicked: {}", res.substr(1))); - } else { - throw std::runtime_error("Kicked!"); - } - } - - tcp_send(m_identity->PublicKey); - - res = tcp_recv(); - if (res.empty() || res[0] != 'P') { - throw std::runtime_error("Expected 'P'"); - } - - if (res.size() > 1 && std::all_of(res.begin() + 1, res.end(), [](char c) { return std::isdigit(c); })) { - *m_client_id = std::stoi(res.substr(1)); - } else { - // auth failed - throw std::runtime_error("Authentication failed"); - } - - tcp_send("SR"); - - res = tcp_recv(); - if (res.empty() || res[0] == 'E') { - throw std::runtime_error("Kicked!"); - } else if (res[0] == 'K') { - if (res.size() > 1) { - throw std::runtime_error(fmt::format("Kicked: {}", res.substr(1))); - } else { - throw std::runtime_error("Kicked!"); - } - } - - if (res == "-") { - spdlog::info("Didn't receive any mods"); - *m_list_of_mods = "-"; - tcp_send("Done"); - spdlog::info("Done!"); - } - // auth done! - } - - if (m_list_of_mods.get() != "-") { - spdlog::info("Checking resources"); - if (!std::filesystem::exists("Resources")) { - std::filesystem::create_directories("Resources"); - } - throw std::runtime_error("Mod loading not yet implemented"); - } - - std::string res; - while (!*m_shutdown) { - res = tcp_recv(); - server_parse(res); - } - - game_send("T"); - - } catch (const std::exception& e) { - spdlog::error("Fatal error: {}", e.what()); - *m_shutdown = true; - } - spdlog::debug("Client stopping"); -} - -void Launcher::server_parse(const std::string& data) { - if (data.empty()) { - return; - } - switch (data[0]) { - case 'p': - *m_ping_end = std::chrono::high_resolution_clock::now(); - if (m_ping_start.get() > m_ping_end.get()) { - *m_ping = 0; - } else { - *m_ping = int(std::chrono::duration_cast(m_ping_end.get() - m_ping_start.get()).count()); - } - break; - case 'M': - *m_m_status = data; - *m_ul_status = "Uldone"; - break; - default: - game_send(data); - } -} - -std::string Launcher::udp_recv() { - // the theoretical maximum udp message is 64 KiB, so we save one buffer per thread for it - static thread_local std::vector s_local_buf(size_t(64u * 1024u)); - auto n = m_udp_socket.receive_from(buffer(s_local_buf), m_udp_endpoint); - return { s_local_buf.data(), n }; -} - -void Launcher::udp_main() { - spdlog::info("UDP starting"); - - try { - game_send(std::string("P") + std::to_string(*m_client_id)); - tcp_send("H"); - udp_send("p"); - - while (!*m_shutdown) { - auto msg = udp_recv(); - if (!msg.empty() && msg.length() > 4 && msg.substr(0, 4) == "ABG:") { - server_parse(std::string(DeComp(msg))); - } else { - server_parse(std::string(msg)); - } - } - - } catch (const std::exception& e) { - spdlog::error("Error in udp_main: {}", e.what()); - } - - spdlog::info("UDP stopping"); -} - -void Launcher::auto_ping() { - spdlog::debug("Ping thread started"); - while (!*m_shutdown) { - udp_send("p"); - *m_ping_start = std::chrono::high_resolution_clock::now(); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - spdlog::debug("Ping thread stopped"); -} - -void Launcher::udp_send(const std::string& data) { - auto vec = std::vector(data.begin(), data.end()); - if (data.length() > 400) { - compress_properly(vec); - } - std::vector to_send = { uint8_t(*m_client_id), uint8_t(':') }; - to_send.insert(to_send.end(), vec.begin(), vec.end()); - m_udp_socket.send_to(buffer(to_send.data(), to_send.size()), m_udp_endpoint); -} -std::string Launcher::get_public_key() { - return m_identity->PublicKey; -} diff --git a/src/Launcher.h b/src/Launcher.h index ed36cf4..8463ccc 100644 --- a/src/Launcher.h +++ b/src/Launcher.h @@ -16,8 +16,6 @@ public: Launcher(); ~Launcher(); - void set_port(int p); - void check_for_updates(int argc, char** argv); void set_exe_name(const std::string& name) { m_exe_name = name; } @@ -29,8 +27,6 @@ public: void start_game(); - void start_network(); - std::string get_public_key(); private: @@ -40,48 +36,12 @@ private: /// Thread main function for the game thread. void game_main(); - /// Thread main function for the clien thread (the one that talks to the server) - void tcp_client_main(); - - void tcp_game_main(); - - void udp_main(); - - void auto_ping(); - void parse_config(); static void check_mp(const std::string& path); void enable_mp(); - void net_core_main(); - - void reset_status(); - - void game_loop(); - - void handle_core_packet(const std::vector& RawData); - - bool start_sync(const std::string& Data); - - void server_parse(const std::string& data); - - void udp_send(const std::string& data); - void server_send(const std::string& data, bool Res); - void tcp_send(const std::string& data); - void send_large(const std::string& data); - void tcp_send(const std::vector& data); - std::string tcp_recv(); - std::string udp_recv(); - void game_send(const std::string& data); - - Sync m_proxy_port; - - Sync m_ping; - - Sync m_client_id; - Sync m_mod_loaded { false }; Sync m_config; diff --git a/src/ServerNetwork.cpp b/src/ServerNetwork.cpp index 0521f9e..144d704 100644 --- a/src/ServerNetwork.cpp +++ b/src/ServerNetwork.cpp @@ -2,7 +2,6 @@ #include "ClientInfo.h" #include "Identity.h" #include "ImplementationInfo.h" -#include "Launcher.h" #include "ProtocolVersion.h" #include "ServerInfo.h" #include "Transport.h" @@ -10,9 +9,8 @@ #include #include -ServerNetwork::ServerNetwork(Launcher& launcher, const ip::tcp::endpoint& ep) - : m_launcher(launcher) - , m_tcp_ep(ep) { +ServerNetwork::ServerNetwork(const ip::tcp::endpoint& ep) + : m_tcp_ep(ep) { spdlog::debug("Server network created"); } @@ -203,11 +201,10 @@ void ServerNetwork::handle_identification(const bmp::Packet& packet) { case bmp::Purpose::StateChangeAuthentication: { spdlog::debug("Starting authentication"); m_state = bmp::State::Authentication; - // TODO: make the launcher provide login properly! - auto pubkey = m_launcher.get_public_key(); + Identity ident{}; bmp::Packet pubkey_packet { .purpose = bmp::Purpose::PlayerPublicKey, - .raw_data = std::vector(pubkey.begin(), pubkey.end()) + .raw_data = std::vector(ident.PublicKey.begin(), ident.PublicKey.end()) }; tcp_write(pubkey_packet); break; diff --git a/src/ServerNetwork.h b/src/ServerNetwork.h index 1a1816d..89647f5 100644 --- a/src/ServerNetwork.h +++ b/src/ServerNetwork.h @@ -10,7 +10,7 @@ class Launcher; class ServerNetwork { public: - ServerNetwork(Launcher& launcher, const ip::tcp::endpoint& ep); + ServerNetwork(const ip::tcp::endpoint& ep); ~ServerNetwork(); /// Starts and runs the connection to the server. @@ -44,8 +44,6 @@ private: uint64_t m_udp_magic {}; - Launcher& m_launcher; - ip::tcp::endpoint m_tcp_ep; ip::udp::endpoint m_udp_ep; }; diff --git a/src/main.cpp b/src/main.cpp index 849bdfd..b55de12 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,16 @@ -#include "Launcher.h" -#include "Platform.h" -#include "ServerNetwork.h" #include #include -#include -#include - #include #include #include +#include "ClientNetwork.h" +#include "Launcher.h" +#include "Platform.h" +#include "ServerNetwork.h" +#include +#include + /// Sets up a file- and console logger and makes it the default spdlog logger. static void setup_logger(bool debug = false); static std::shared_ptr default_logger {}; @@ -65,7 +66,6 @@ int main(int argc, char** argv) { spdlog::info("BeamMP Launcher v{}.{}.{} is a PRE-RELEASE build. Please report any errors immediately at https://github.com/BeamMP/BeamMP-Launcher.", PRJ_VERSION_MAJOR, PRJ_VERSION_MINOR, PRJ_VERSION_PATCH); - Launcher launcher {}; std::filesystem::path arg0(argv[0]); @@ -73,7 +73,7 @@ int main(int argc, char** argv) { launcher.set_exe_path(arg0.parent_path()); if (custom_port > 0) { - launcher.set_port(custom_port); + // launcher.set_port(custom_port); } if (!enable_dev) { @@ -89,7 +89,12 @@ int main(int argc, char** argv) { launcher.start_game(); } - launcher.start_network(); + ClientNetwork cn(4444); + + cn.run(); + + // old: launcher.start_network(); + /* Launcher launcher {};