mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-04-04 23:06:08 +00:00
add event 'onPlayerRequestMods', restructure modloading
now every player has their own list of allowed client mods, this can be modified by lua upon joining and is later used as a whitelist to ensure only those files can be sent to each client
This commit is contained in:
@@ -87,6 +87,8 @@ void TConfig::FlushToFile() {
|
||||
SetComment(data["Misc"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here");
|
||||
data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::GetSettingBool(StrSendErrorsMessageEnabled.data());
|
||||
SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`");
|
||||
data["Misc"][StrIncludeSubdirectories.data()] = Application::GetSettingBool(StrIncludeSubdirectories.data());
|
||||
SetComment(data["Misc"][StrIncludeSubdirectories.data()].comments(), " Whether or not to include subdirectories in General.ResourceFolder/Client when searching for `.zip`s");
|
||||
// HTTP
|
||||
data["HTTP"][StrSSLKeyPath.data()] = Application::GetSettingString(StrSSLKeyPath.data());
|
||||
data["HTTP"][StrSSLCertPath.data()] = Application::GetSettingString(StrSSLCertPath.data());
|
||||
|
||||
@@ -145,9 +145,9 @@ std::string THeartbeatThread::GenerateCall() {
|
||||
<< "&version=" << Application::ServerVersionString()
|
||||
<< "&clientversion=" << std::to_string(Application::ClientMajorVersion()) + ".0" // FIXME: Wtf.
|
||||
<< "&name=" << Application::GetSettingString(StrName)
|
||||
<< "&modlist=" << mResourceManager.TrimmedList()
|
||||
<< "&modstotalsize=" << mResourceManager.MaxModSize()
|
||||
<< "&modstotal=" << mResourceManager.ModsLoaded()
|
||||
<< "&modlist=" << TResourceManager::FormatForBackend(mResourceManager.FileMap())
|
||||
<< "&modstotalsize=" << mResourceManager.TotalModsSize()
|
||||
<< "&modstotal=" << mResourceManager.LoadedModCount()
|
||||
<< "&playerslist=" << GetPlayers()
|
||||
<< "&desc=" << Application::GetSettingString(StrDescription);
|
||||
return Ret.str();
|
||||
|
||||
@@ -1054,6 +1054,15 @@ void TLuaEngine::StateThreadData::operator()() {
|
||||
LuaArgs.push_back(sol::make_object(StateView, Table));
|
||||
break;
|
||||
}
|
||||
case TLuaType::StringSizeTMap: {
|
||||
auto Map = std::get<std::unordered_map<std::string, size_t>>(Arg);
|
||||
auto Table = StateView.create_table();
|
||||
for (const auto& [k, v] : Map) {
|
||||
Table[k] = v;
|
||||
}
|
||||
LuaArgs.push_back(sol::make_object(StateView, Table));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
beammp_error("Unknown argument type, passed as nil");
|
||||
break;
|
||||
|
||||
@@ -614,6 +614,34 @@ void TNetwork::SyncResources(TClient& c) {
|
||||
}
|
||||
}
|
||||
|
||||
ModMap TNetwork::GetClientMods(TClient& Client) {
|
||||
auto AllMods = mResourceManager.FileMap();
|
||||
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerRequestMods", "", Client.GetName(), Client.GetRoles(), Client.IsGuest(), Client.GetIdentifiers(), AllMods);
|
||||
TLuaEngine::WaitForAll(Futures);
|
||||
|
||||
ModMap AllowedMods = AllMods;
|
||||
|
||||
for (const std::shared_ptr<TLuaResult>& Future : Futures) {
|
||||
sol::table Result;
|
||||
if (!Future->Error && Future->Result.is<sol::table>()) {
|
||||
Result = Future->Result.as<sol::table>();
|
||||
|
||||
for (const auto& [name, size] : AllMods) {
|
||||
auto val = Result.get<sol::optional<int>>(name);
|
||||
if (!val.has_value()) {
|
||||
AllowedMods.erase(name);
|
||||
beammp_debugf("Not sending mod '{}' to player '{}' (from state '{}')", name, Client.GetName(), Future->StateId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Client.AllowedMods = AllowedMods;
|
||||
|
||||
return AllowedMods;
|
||||
}
|
||||
|
||||
void TNetwork::HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Packet) {
|
||||
if (Packet.empty())
|
||||
return;
|
||||
@@ -627,7 +655,7 @@ void TNetwork::HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Pac
|
||||
case 'S':
|
||||
if (SubCode == 'R') {
|
||||
beammp_debug("Sending Mod Info");
|
||||
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
|
||||
std::string ToSend = TResourceManager::FormatForClient(GetClientMods(c));
|
||||
if (ToSend.empty())
|
||||
ToSend = "-";
|
||||
if (!TCPSend(c, StringToVector(ToSend))) {
|
||||
@@ -641,24 +669,16 @@ void TNetwork::HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Pac
|
||||
}
|
||||
|
||||
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
|
||||
beammp_infof("{} ({}) requesting : '{}'", c.GetName(), c.GetID(), UnsafeName);
|
||||
beammp_infof("{} ({}) requesting mod: '{}'", c.GetName(), c.GetID(), UnsafeName);
|
||||
|
||||
if (!fs::path(UnsafeName).has_filename()) {
|
||||
if (!TCPSend(c, StringToVector("CO"))) {
|
||||
c.Disconnect("TCP send failed in SendFile, when trying to cancel file transfer because the requested file doesn't contain a valid filename");
|
||||
return;
|
||||
}
|
||||
beammp_warn("File " + UnsafeName + " is not a file!");
|
||||
return;
|
||||
}
|
||||
auto FileName = fs::path(UnsafeName).filename().string();
|
||||
FileName = Application::GetSettingString(StrResourceFolder) + "/Client/" + FileName;
|
||||
auto FileName = UnsafeName;
|
||||
|
||||
if (!std::filesystem::exists(FileName)) {
|
||||
auto res = TResourceManager::IsModValid(FileName, c.AllowedMods);
|
||||
|
||||
if (res.has_value()) {
|
||||
if (!TCPSend(c, StringToVector("CO"))) {
|
||||
c.Disconnect("TCP send failed in SendFile, when trying to cancel file transfer because the requested file doesn't exist or couldn't be accessed");
|
||||
c.Disconnect("TCP send failed in SendFile, when trying to cancel file transfer because " + res.value());
|
||||
}
|
||||
beammp_warn("File " + UnsafeName + " could not be accessed!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,31 +5,80 @@
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::string TResourceManager::FormatForBackend(const ModMap& mods) {
|
||||
std::string monkey;
|
||||
for (const auto& [name, size] : mods) {
|
||||
monkey += fs::path(name).filename().string() + ';';
|
||||
}
|
||||
return monkey;
|
||||
}
|
||||
|
||||
std::string TResourceManager::FormatForClient(const ModMap& mods) {
|
||||
std::string monkey;
|
||||
for (const auto& [name, size] : mods) {
|
||||
monkey += '/' + name + ';';
|
||||
}
|
||||
for (const auto& [name, size] : mods) {
|
||||
monkey += std::to_string(size) + ';';
|
||||
}
|
||||
return monkey;
|
||||
}
|
||||
|
||||
/// @brief Sanitizes a requested mod string
|
||||
/// @param pathString Raw mod path string
|
||||
/// @param mods List of allowed mods for this client
|
||||
/// @return Error, if any
|
||||
std::optional<std::string> TResourceManager::IsModValid(std::string& pathString, const ModMap& mods) {
|
||||
auto path = fs::path(pathString);
|
||||
if (!path.has_filename()) {
|
||||
beammp_warn("File " + pathString + " is not a file!");
|
||||
return { "the requested file doesn't contain a valid filename" };
|
||||
}
|
||||
|
||||
auto BasePath = fs::path(Application::GetSettingString(StrResourceFolder) + "/Client");
|
||||
|
||||
auto CombinedPath = fs::path(BasePath.string() + pathString).lexically_normal();
|
||||
|
||||
// beammp_infof("path: {}, base: {}, combined: {}", pathString, BasePath.string(), CombinedPath.string());
|
||||
|
||||
if (!std::filesystem::exists(CombinedPath)) {
|
||||
beammp_warn("File " + pathString + " could not be accessed!");
|
||||
return { "the requested file doesn't exist or couldn't be accessed" };
|
||||
}
|
||||
|
||||
auto relative = fs::relative(CombinedPath, BasePath);
|
||||
|
||||
if (mods.count(relative.string()) == 0) {
|
||||
beammp_warn("File " + pathString + " is disallowed for this player!");
|
||||
return { "the requested file is disallowed for this player" };
|
||||
}
|
||||
|
||||
pathString = CombinedPath.string();
|
||||
return {};
|
||||
}
|
||||
|
||||
TResourceManager::TResourceManager() {
|
||||
Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting);
|
||||
std::string Path = Application::GetSettingString(StrResourceFolder) + "/Client";
|
||||
if (!fs::exists(Path))
|
||||
fs::create_directories(Path);
|
||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||
std::string File(entry.path().string());
|
||||
if (auto pos = File.find(".zip"); pos != std::string::npos) {
|
||||
if (File.length() - pos == 4) {
|
||||
std::replace(File.begin(), File.end(), '\\', '/');
|
||||
mFileList += File + ';';
|
||||
if (auto i = File.find_last_of('/'); i != std::string::npos) {
|
||||
++i;
|
||||
File = File.substr(i, pos - i);
|
||||
}
|
||||
mTrimmedList += "/" + fs::path(File).filename().string() + ';';
|
||||
mFileSizes += std::to_string(size_t(fs::file_size(entry.path()))) + ';';
|
||||
mMaxModSize += size_t(fs::file_size(entry.path()));
|
||||
mModsLoaded++;
|
||||
}
|
||||
std::string BasePath = Application::GetSettingString(StrResourceFolder) + "/Client";
|
||||
if (!fs::exists(BasePath))
|
||||
fs::create_directories(BasePath);
|
||||
std::vector<std::string> modNames;
|
||||
|
||||
auto iterator = fs::recursive_directory_iterator(BasePath, fs::directory_options::follow_directory_symlink | fs::directory_options::skip_permission_denied);
|
||||
for (const auto& entry : iterator) {
|
||||
if (iterator.depth() > 0 && !Application::GetSettingBool(StrIncludeSubdirectories))
|
||||
continue;
|
||||
if ((entry.is_regular_file() || entry.is_symlink()) && entry.path().extension() == ".zip") {
|
||||
auto relativePath = fs::relative(entry.path(), BasePath);
|
||||
beammp_infof("mod entry: {}", relativePath.string());
|
||||
|
||||
mMods[relativePath.string()] = entry.file_size();
|
||||
mTotalModSize += entry.file_size();
|
||||
}
|
||||
}
|
||||
|
||||
if (mModsLoaded) {
|
||||
beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods");
|
||||
if (!mMods.empty()) {
|
||||
beammp_infof("Loaded {} mod{}", mMods.size(), mMods.size() != 1 ? 's' : ' ');
|
||||
}
|
||||
|
||||
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
|
||||
|
||||
Reference in New Issue
Block a user