add client network

This commit is contained in:
Lion Kortlepel 2024-03-02 17:10:21 +01:00
parent 23cf2483e7
commit c5a6fc711f
No known key found for this signature in database
GPG Key ID: 4322FF2B4C71259B
9 changed files with 249 additions and 672 deletions

View File

@ -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)

183
src/ClientNetwork.cpp Normal file
View File

@ -0,0 +1,183 @@
#include "ClientNetwork.h"
#include "ClientPacket.h"
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
using json = nlohmann::json;
void ClientNetwork::run() {
ip::tcp::endpoint listen_ep(ip::address::from_string("0.0.0.0"), static_cast<uint16_t>(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<uint8_t> 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<uint8_t> 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<uint8_t> ClientNetwork::json_to_vec(const nlohmann::json& value) {
auto str = value.dump();
return std::vector<uint8_t>(str.begin(), str.end());
}

44
src/ClientNetwork.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include "ClientPacket.h"
#include "ClientState.h"
#include "Launcher.h"
#include "Sync.h"
#include <boost/asio.hpp>
#include <nlohmann/json.hpp>
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<uint8_t> json_to_vec(const nlohmann::json& json);
uint16_t m_listen_port {};
io_context m_io {};
Sync<bool> m_shutdown { false };
bmp::ClientState m_client_state;
};

View File

@ -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<bool>()) {

View File

@ -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<uint16_t>(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<char> 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<char>& 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<uint8_t>& Data) {
constexpr std::string_view ABG = "ABG:";
auto CombinedData = std::vector<uint8_t>(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<uint8_t> 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<uint16_t>(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<char> 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<uint8_t>& data) {
const auto Size = int32_t(data.size());
std::vector<uint8_t> 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<uint8_t>(data.begin(), data.end()));
}
std::string Launcher::tcp_recv() {
int32_t Header {};
boost::system::error_code ec;
std::array<uint8_t, sizeof(Header)> 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<int32_t*>(HeaderData.data());
if (Header < 0) {
throw std::runtime_error("read() failed: Negative TCP header");
}
std::vector<uint8_t> 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<const char*>(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<const uint8_t*>(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<std::chrono::milliseconds>(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<char> 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<uint8_t>(data.begin(), data.end());
if (data.length() > 400) {
compress_properly(vec);
}
std::vector<uint8_t> 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;
}

View File

@ -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<char>& 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<uint8_t>& data);
std::string tcp_recv();
std::string udp_recv();
void game_send(const std::string& data);
Sync<int> m_proxy_port;
Sync<int> m_ping;
Sync<int> m_client_id;
Sync<bool> m_mod_loaded { false };
Sync<Config> m_config;

View File

@ -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 <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
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<uint8_t>(pubkey.begin(), pubkey.end())
.raw_data = std::vector<uint8_t>(ident.PublicKey.begin(), ident.PublicKey.end())
};
tcp_write(pubkey_packet);
break;

View File

@ -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;
};

View File

@ -1,15 +1,16 @@
#include "Launcher.h"
#include "Platform.h"
#include "ServerNetwork.h"
#include <boost/system/detail/errc.hpp>
#include <boost/system/detail/error_category.hpp>
#include <iostream>
#include <thread>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include "ClientNetwork.h"
#include "Launcher.h"
#include "Platform.h"
#include "ServerNetwork.h"
#include <iostream>
#include <thread>
/// 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<spdlog::logger> 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 {};