mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-10-23 16:41:25 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1bfdde406 | ||
|
|
1b83b4bb3e | ||
|
|
e7a5050e1b | ||
|
|
b50f82166b | ||
|
|
b21d4062c6 | ||
|
|
cd4cc3e630 | ||
|
|
a8dfa4b004 | ||
|
|
69233f4185 |
No files matched your search
@@ -55,7 +55,6 @@ set(PRJ_HEADERS
|
||||
include/ChronoWrapper.h
|
||||
include/TConnectionLimiter.h
|
||||
include/TLuaResult.h
|
||||
include/RegionHandler.h
|
||||
)
|
||||
# add all source files (.cpp) to this, except the one with main()
|
||||
set(PRJ_SOURCES
|
||||
@@ -85,7 +84,6 @@ set(PRJ_SOURCES
|
||||
src/ChronoWrapper.cpp
|
||||
src/TConnectionLimiter.cpp
|
||||
src/TLuaResult.cpp
|
||||
src/RegionHandler.cpp
|
||||
)
|
||||
|
||||
find_package(Lua REQUIRED)
|
||||
|
||||
+9
-5
@@ -80,12 +80,16 @@ public:
|
||||
|
||||
static inline struct Settings Settings { };
|
||||
|
||||
static std::vector<std::string> GetBackendUrlsInOrder();
|
||||
static std::vector<std::string> GetBackendUrlsInOrder() {
|
||||
return {
|
||||
"https://backend.beammp.com",
|
||||
};
|
||||
}
|
||||
|
||||
static std::string GetServerCheckUrl();
|
||||
static std::string GetServerCheckUrl() { return "https://check.beammp.com"; }
|
||||
|
||||
static std::string GetBackendUrlForAuth();
|
||||
static std::string GetBackendUrlForSocketIO();
|
||||
static std::string GetBackendUrlForAuth() { return "https://auth.beammp.com"; }
|
||||
static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; }
|
||||
static void CheckForUpdates();
|
||||
static std::array<uint8_t, 3> VersionStrToInts(const std::string& str);
|
||||
static bool IsOutdated(const Version& Current, const Version& Newest);
|
||||
@@ -125,7 +129,7 @@ private:
|
||||
static inline std::mutex mShutdownHandlersMutex {};
|
||||
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
|
||||
|
||||
static inline Version mVersion { 3, 9, 3 };
|
||||
static inline Version mVersion { 3, 9, 4 };
|
||||
};
|
||||
|
||||
/// Used to static_assert in std::visit
|
||||
|
||||
+2
-2
@@ -39,8 +39,8 @@
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Http {
|
||||
std::string GET(std::string url, unsigned int* status = nullptr, const bool& redirect = true);
|
||||
std::string POST(std::string url, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr, const std::map<std::string, std::string>& headers = {}, const bool& redirect = true);
|
||||
std::string GET(const std::string& url, unsigned int* status = nullptr);
|
||||
std::string POST(const std::string& url, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr, const std::map<std::string, std::string>& headers = {});
|
||||
namespace Status {
|
||||
std::string ToString(int code);
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// BeamMP, the BeamNG.drive multiplayer mod.
|
||||
// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
|
||||
//
|
||||
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
class RegionHandler final {
|
||||
public:
|
||||
RegionHandler() = delete;
|
||||
static void TopLevelDomainFailed();
|
||||
static std::string RegionToTopLevelDomain();
|
||||
static std::string RedirectURL(const std::string &URL);
|
||||
private:
|
||||
static inline unsigned int mRegionIndex { 0 };
|
||||
const static inline std::array<std::string, 2> mValidTLDs {"beammp.com", "beammp.net"};
|
||||
};
|
||||
@@ -87,7 +87,6 @@ struct Settings {
|
||||
General_Debug,
|
||||
General_AllowGuests,
|
||||
General_InformationPacket,
|
||||
General_Region,
|
||||
};
|
||||
|
||||
Sync<std::unordered_map<Key, SettingsTypeVariant>> SettingsMap;
|
||||
|
||||
@@ -53,7 +53,10 @@ public:
|
||||
|
||||
io_context& IoCtx() { return mIoCtxPoller.IoCtx(); }
|
||||
|
||||
uint64_t GetServerTimeMS() const { return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - TimeSyncStart).count();}
|
||||
double GetServerTime() const { return std::chrono::duration<double>(std::chrono::steady_clock::now() - TimeSyncStart).count(); }
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::steady_clock> TimeSyncStart;
|
||||
TIoPollThread mIoCtxPoller;
|
||||
TClientSet mClients;
|
||||
mutable RWMutex mClientsMutex;
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#include "Compat.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "Http.h"
|
||||
#include "RegionHandler.h"
|
||||
|
||||
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
|
||||
std::unique_lock Lock(mShutdownHandlersMutex);
|
||||
@@ -74,27 +73,6 @@ std::string Application::ServerVersionString() {
|
||||
return mVersion.AsString();
|
||||
}
|
||||
|
||||
std::vector<std::string> Application::GetBackendUrlsInOrder() {
|
||||
return {
|
||||
"https://backend.beammp.com",
|
||||
};
|
||||
}
|
||||
|
||||
std::string Application::GetServerCheckUrl()
|
||||
{
|
||||
return "https://check.beammp.com";
|
||||
}
|
||||
|
||||
std::string Application::GetBackendUrlForAuth()
|
||||
{
|
||||
return "https://auth.beammp.com";
|
||||
}
|
||||
|
||||
std::string Application::GetBackendUrlForSocketIO()
|
||||
{
|
||||
return "https://backend.beammp.com";
|
||||
}
|
||||
|
||||
std::array<uint8_t, 3> Application::VersionStrToInts(const std::string& str) {
|
||||
std::array<uint8_t, 3> Version;
|
||||
std::stringstream ss(str);
|
||||
|
||||
+2
-33
@@ -18,7 +18,6 @@
|
||||
|
||||
#include "Http.h"
|
||||
|
||||
#include "RegionHandler.h"
|
||||
#include "Common.h"
|
||||
#include "CustomAssert.h"
|
||||
|
||||
@@ -116,13 +115,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::string Http::GET(std::string url, unsigned int* status, const bool& redirect) {
|
||||
std::string Http::GET(const std::string& url, unsigned int* status) {
|
||||
std::string Ret;
|
||||
CurlLease Lease{};
|
||||
CURL* curl = Lease.GetHandle();
|
||||
if (redirect) {
|
||||
url = RegionHandler::RedirectURL(url);
|
||||
}
|
||||
if (curl) {
|
||||
CURLcode res;
|
||||
char errbuf[CURL_ERROR_SIZE];
|
||||
@@ -146,18 +142,6 @@ std::string Http::GET(std::string url, unsigned int* status, const bool& redirec
|
||||
if (res != CURLE_OK) {
|
||||
beammp_error("GET to " + url + " failed: " + std::string(curl_easy_strerror(res)));
|
||||
beammp_error("Curl error: " + std::string(errbuf));
|
||||
if (!redirect) {
|
||||
return Http::ErrorString;
|
||||
}
|
||||
RegionHandler::TopLevelDomainFailed();
|
||||
url = RegionHandler::RedirectURL(url);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
beammp_error("GET to " + url + " failed: " + std::string(curl_easy_strerror(res)));
|
||||
beammp_error("Curl error: " + std::string(errbuf));
|
||||
return Http::ErrorString;
|
||||
}
|
||||
return Http::ErrorString;
|
||||
}
|
||||
|
||||
@@ -172,13 +156,10 @@ std::string Http::GET(std::string url, unsigned int* status, const bool& redirec
|
||||
return Ret;
|
||||
}
|
||||
|
||||
std::string Http::POST(std::string url, const std::string& body, const std::string& ContentType, unsigned int* status, const std::map<std::string, std::string>& headers, const bool& redirect) {
|
||||
std::string Http::POST(const std::string& url, const std::string& body, const std::string& ContentType, unsigned int* status, const std::map<std::string, std::string>& headers) {
|
||||
std::string Ret;
|
||||
CurlLease Lease{};
|
||||
CURL* curl = Lease.GetHandle();
|
||||
if (redirect) {
|
||||
url = RegionHandler::RedirectURL(url);
|
||||
}
|
||||
if (curl) {
|
||||
CURLcode res;
|
||||
char errbuf[CURL_ERROR_SIZE];
|
||||
@@ -214,18 +195,6 @@ std::string Http::POST(std::string url, const std::string& body, const std::stri
|
||||
if (res != CURLE_OK) {
|
||||
beammp_error("POST to " + url + " failed: " + std::string(curl_easy_strerror(res)));
|
||||
beammp_error("Curl error: " + std::string(errbuf));
|
||||
if (!redirect) {
|
||||
return Http::ErrorString;
|
||||
}
|
||||
RegionHandler::TopLevelDomainFailed();
|
||||
url = RegionHandler::RedirectURL(url);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
beammp_error("GET to " + url + " failed: " + std::string(curl_easy_strerror(res)));
|
||||
beammp_error("Curl error: " + std::string(errbuf));
|
||||
return Http::ErrorString;
|
||||
}
|
||||
return Http::ErrorString;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// BeamMP, the BeamNG.drive multiplayer mod.
|
||||
// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
|
||||
//
|
||||
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "RegionHandler.h"
|
||||
#include "Common.h"
|
||||
|
||||
#include <regex>
|
||||
|
||||
void RegionHandler::TopLevelDomainFailed()
|
||||
{
|
||||
static bool isDeveloperRegion = Application::Settings.getAsString(Settings::Key::General_Region) == "Developer";
|
||||
if (!isDeveloperRegion) {
|
||||
beammp_info("Top level domain of " + mValidTLDs[mRegionIndex % mValidTLDs.size()] + " didn't respond correctly , changing domain to " + mValidTLDs[(mRegionIndex + 1) % mValidTLDs.size()]);
|
||||
}
|
||||
mRegionIndex++;
|
||||
}
|
||||
|
||||
std::string RegionHandler::RegionToTopLevelDomain()
|
||||
{
|
||||
static bool isDeveloperRegion = Application::Settings.getAsString(Settings::Key::General_Region) == "Developer";
|
||||
if (isDeveloperRegion) {
|
||||
return "beammp.dev";
|
||||
}
|
||||
return mValidTLDs[mRegionIndex % mValidTLDs.size()]; // Global
|
||||
}
|
||||
|
||||
std::string RegionHandler::RedirectURL(const std::string &URL)
|
||||
{
|
||||
std::regex link_pattern(R"(^(https:\/\/.*)beammp\.com(\/.*)?$)");
|
||||
std::smatch link_match;
|
||||
if (std::regex_search(URL, link_match, link_pattern) && link_match.position() == 0) {
|
||||
//TLD matched beammp.com
|
||||
std::string before = link_match[1].str(); // "https://..." up to beammp
|
||||
std::string after = link_match[2].matched ? link_match[2].str() : ""; // "/path" or ""
|
||||
return before + RegionToTopLevelDomain() + after;
|
||||
}
|
||||
return URL; //if it didn't match, just return the unmodified URL
|
||||
}
|
||||
@@ -36,7 +36,6 @@ Settings::Settings() {
|
||||
{ General_Debug, false },
|
||||
{ General_AllowGuests, true },
|
||||
{ General_InformationPacket, true },
|
||||
{ General_Region, std::string("")},
|
||||
{ Misc_ImScaredOfUpdates, true },
|
||||
{ Misc_UpdateReminderTime, "30s" }
|
||||
};
|
||||
@@ -57,7 +56,6 @@ Settings::Settings() {
|
||||
{ { "General", "Debug" }, { General_Debug, READ_WRITE } },
|
||||
{ { "General", "AllowGuests" }, { General_AllowGuests, READ_WRITE } },
|
||||
{ { "General", "InformationPacket" }, { General_InformationPacket, READ_WRITE } },
|
||||
{ { "General", "Region" }, { General_Region, READ_WRITE } },
|
||||
{ { "Misc", "ImScaredOfUpdates" }, { Misc_ImScaredOfUpdates, READ_WRITE } },
|
||||
{ { "Misc", "UpdateReminderTime" }, { Misc_UpdateReminderTime, READ_WRITE } }
|
||||
};
|
||||
|
||||
@@ -52,8 +52,6 @@ static constexpr std::string_view StrTags = "Tags";
|
||||
static constexpr std::string_view EnvStrTags = "BEAMMP_TAGS";
|
||||
static constexpr std::string_view StrResourceFolder = "ResourceFolder";
|
||||
static constexpr std::string_view EnvStrResourceFolder = "BEAMMP_RESOURCE_FOLDER";
|
||||
static constexpr std::string_view StrRegion = "BackendRegion";
|
||||
static constexpr std::string_view EnvStrRegion = "BEAMMP_BACKEND_REGION";
|
||||
static constexpr std::string_view StrAuthKey = "AuthKey";
|
||||
static constexpr std::string_view EnvStrAuthKey = "BEAMMP_AUTH_KEY";
|
||||
static constexpr std::string_view StrLogChat = "LogChat";
|
||||
@@ -153,9 +151,6 @@ void TConfig::FlushToFile() {
|
||||
data["General"][StrMap.data()] = Application::Settings.getAsString(Settings::Key::General_Map);
|
||||
data["General"][StrDescription.data()] = Application::Settings.getAsString(Settings::Key::General_Description);
|
||||
data["General"][StrResourceFolder.data()] = Application::Settings.getAsString(Settings::Key::General_ResourceFolder);
|
||||
if (Application::Settings.getAsString(Settings::Key::General_Region) != "") {
|
||||
data["General"][StrRegion.data()] = Application::Settings.getAsString(Settings::Key::General_Region);
|
||||
}
|
||||
// data["General"][StrPassword.data()] = Application::Settings.Password;
|
||||
// SetComment(data["General"][StrPassword.data()].comments(), " Sets a password on this server, which restricts people from joining. To join, a player must enter this exact password. Leave empty ("") to disable the password.");
|
||||
// Misc
|
||||
@@ -226,22 +221,16 @@ void TConfig::TryReadValue(toml::value& Table, const std::string& Category, cons
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
if (Table[Category.c_str()][Key.data()].is_string())
|
||||
Application::Settings.set(key, Table[Category.c_str()][Key.data()].as_string());
|
||||
else if (Table[Category.c_str()][Key.data()].is_empty())
|
||||
beammp_debugf("Value '{}.{}' is empty", Category, Key);
|
||||
else
|
||||
beammp_warnf("Value '{}.{}' has unexpected type, expected type 'string'", Category, Key);
|
||||
} else if constexpr (std::is_same_v<T, int>) {
|
||||
if (Table[Category.c_str()][Key.data()].is_integer())
|
||||
Application::Settings.set(key, int(Table[Category.c_str()][Key.data()].as_integer()));
|
||||
else if (Table[Category.c_str()][Key.data()].is_empty())
|
||||
beammp_debugf("Value '{}.{}' is empty", Category, Key);
|
||||
else
|
||||
beammp_warnf("Value '{}.{}' has unexpected type, expected type 'integer'", Category, Key);
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
if (Table[Category.c_str()][Key.data()].is_boolean())
|
||||
Application::Settings.set(key, Table[Category.c_str()][Key.data()].as_boolean());
|
||||
else if (Table[Category.c_str()][Key.data()].is_empty())
|
||||
beammp_debugf("Value '{}.{}' is empty", Category, Key);
|
||||
else
|
||||
beammp_warnf("Value '{}.{}' has unexpected type, expected type 'boolean'", Category, Key);
|
||||
} else {
|
||||
@@ -281,7 +270,6 @@ void TConfig::ParseFromFile(std::string_view name) {
|
||||
TryReadValue(data, "General", StrDescription, EnvStrDescription, Settings::Key::General_Description);
|
||||
TryReadValue(data, "General", StrTags, EnvStrTags, Settings::Key::General_Tags);
|
||||
TryReadValue(data, "General", StrResourceFolder, EnvStrResourceFolder, Settings::Key::General_ResourceFolder);
|
||||
TryReadValue(data, "General", StrRegion, EnvStrRegion, Settings::Key::General_Region);
|
||||
TryReadValue(data, "General", StrAuthKey, EnvStrAuthKey, Settings::Key::General_AuthKey);
|
||||
TryReadValue(data, "General", StrLogChat, EnvStrLogChat, Settings::Key::General_LogChat);
|
||||
TryReadValue(data, "General", StrAllowGuests, EnvStrAllowGuests, Settings::Key::General_AllowGuests);
|
||||
@@ -334,7 +322,6 @@ void TConfig::PrintDebug() {
|
||||
beammp_debug(std::string(StrTags) + ": " + TagsAsPrettyArray());
|
||||
beammp_debug(std::string(StrLogChat) + ": \"" + (Application::Settings.getAsBool(Settings::Key::General_LogChat) ? "true" : "false") + "\"");
|
||||
beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "\"");
|
||||
beammp_debug(std::string(StrRegion) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Region) + "\"");
|
||||
beammp_debug(std::string(StrAllowGuests) + ": \"" + (Application::Settings.getAsBool(Settings::Key::General_AllowGuests) ? "true" : "false") + "\"");
|
||||
// special!
|
||||
beammp_debug("Key Length: " + std::to_string(Application::Settings.getAsString(Settings::Key::General_AuthKey).length()) + "");
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "Http.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "TLuaEngine.h"
|
||||
#include "RegionHandler.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <lua.hpp>
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
#include "Client.h"
|
||||
#include "Common.h"
|
||||
#include "Http.h"
|
||||
#include "RegionHandler.h"
|
||||
// #include "SocketIO.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <sstream>
|
||||
|
||||
void THeartbeatThread::operator()() {
|
||||
RegisterThread("Heartbeat");
|
||||
|
||||
@@ -1012,6 +1012,14 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
||||
}
|
||||
MPTable.set_function("Get", &LuaAPI::MP::Get);
|
||||
|
||||
MPTable.set_function("GetServerTimeMS", [this](const sol::this_state ts) {
|
||||
return make_object(sol::state_view(ts), this->mEngine->Server().GetServerTimeMS());
|
||||
});
|
||||
|
||||
MPTable.set_function("GetServerTime", [this](const sol::this_state ts) {
|
||||
return make_object(sol::state_view(ts), this->mEngine->Server().GetServerTime());
|
||||
});
|
||||
|
||||
auto UtilTable = StateView.create_named_table("Util");
|
||||
UtilTable.set_function("LogDebug", [this](sol::variadic_args args) {
|
||||
std::string ToPrint = "";
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "Common.h"
|
||||
#include "Env.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "RegionHandler.h"
|
||||
#include "TConnectionLimiter.h"
|
||||
#include "THeartbeatThread.h"
|
||||
#include "TLuaEngine.h"
|
||||
|
||||
@@ -123,6 +123,12 @@ TEST_CASE("GetPidVid") {
|
||||
TServer::TServer(const std::vector<std::string_view>& Arguments) {
|
||||
beammp_info("BeamMP Server v" + Application::ServerVersionString());
|
||||
Application::SetSubsystemStatus("Server", Application::Status::Starting);
|
||||
std::mt19937_64 TimeSyncRandomDevice(std::random_device{}());
|
||||
|
||||
TimeSyncStart = std::chrono::steady_clock::now()
|
||||
- std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
||||
std::chrono::duration<double>(std::uniform_real_distribution<double>(0.0, 2592000.0)(TimeSyncRandomDevice))
|
||||
);
|
||||
Application::SetSubsystemStatus("Server", Application::Status::Good);
|
||||
}
|
||||
|
||||
@@ -210,6 +216,24 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
||||
return;
|
||||
}
|
||||
switch (Code) {
|
||||
case 't':
|
||||
if (!udp && Packet.size() == 9) {
|
||||
auto Time = GetServerTimeMS();
|
||||
|
||||
std::vector<uint8_t> ServerTime(sizeof(uint64_t));
|
||||
std::memcpy(ServerTime.data(), &Time, sizeof(uint64_t));
|
||||
|
||||
auto Data = Packet;
|
||||
|
||||
beammp_debugf("Sending server time {} to client '{}' ({}), client time: {}", Time, LockedClient->GetName(), LockedClient->GetID(), reinterpret_cast<uint64_t>(Data.data()));
|
||||
|
||||
Data.insert(Data.end(), ServerTime.begin(), ServerTime.end());
|
||||
|
||||
if (!Network.Respond(*LockedClient, Data, true)) {
|
||||
Network.DisconnectClient(*LockedClient, "Failed to send time");
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 'H': // initial connection
|
||||
if (udp) {
|
||||
beammp_debugf("Received 'H' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
|
||||
|
||||
Reference in new issue
Block a user