mirror of
https://github.com/BeamMP/BeamMP-Launcher.git
synced 2025-07-01 23:46:59 +00:00
add client network
This commit is contained in:
parent
23cf2483e7
commit
c5a6fc711f
@ -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
183
src/ClientNetwork.cpp
Normal 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
44
src/ClientNetwork.h
Normal 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;
|
||||
};
|
@ -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>()) {
|
||||
|
612
src/Launcher.cpp
612
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<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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
23
src/main.cpp
23
src/main.cpp
@ -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 {};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user