diff --git a/CHANGELOG b/CHANGELOG index 56b1be8..e37862e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +-- 2024. 05. 18 - v1.3.0 + +- feat: Add configuration json file to allow players to bypass specific restrictions +- feat: Add database to store allowed players for a while and dont validate them all the time + -- 2024. 04. 26 - v1.2.1 - update: Updated from NET7 to NET8 diff --git a/src/KitsuneSteamRestrict.cs b/src/KitsuneSteamRestrict.cs index 53b6d6c..bbf20e7 100644 --- a/src/KitsuneSteamRestrict.cs +++ b/src/KitsuneSteamRestrict.cs @@ -2,10 +2,10 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; using CounterStrikeSharp.API.Modules.Timers; +using CounterStrikeSharp.API.Modules.Admin; using System.Text.Json.Serialization; using Microsoft.Extensions.Logging; -using CounterStrikeSharp.API.Modules.Admin; namespace KitsuneSteamRestrict; @@ -49,13 +49,46 @@ public class PluginConfig : BasePluginConfig [JsonPropertyName("BlockGameBanned")] public bool BlockGameBanned { get; set; } = false; + + [JsonPropertyName("DatabaseSettings")] + public DatabaseSettings DatabaseSettings = new DatabaseSettings(); + + [JsonPropertyName("ConfigVersion")] + public override int Version { get; set; } = 2; +} + +public sealed class DatabaseSettings +{ + [JsonPropertyName("host")] + public string Host { get; set; } = "localhost"; + + [JsonPropertyName("username")] + public string Username { get; set; } = "root"; + + [JsonPropertyName("database")] + public string Database { get; set; } = "database"; + + [JsonPropertyName("password")] + public string Password { get; set; } = "password"; + + [JsonPropertyName("port")] + public int Port { get; set; } = 3306; + + [JsonPropertyName("sslmode")] + public string Sslmode { get; set; } = "none"; + + [JsonPropertyName("table-prefix")] + public string TablePrefix { get; set; } = ""; + + [JsonPropertyName("table-purge-days")] + public int TablePurgeDays { get; set; } = 30; } -[MinimumApiVersion(198)] +[MinimumApiVersion(227)] public class SteamRestrictPlugin : BasePlugin, IPluginConfig { public override string ModuleName => "Steam Restrict"; - public override string ModuleVersion => "1.2.1"; + public override string ModuleVersion => "1.3.0"; public override string ModuleAuthor => "K4ryuu, Cruze @ KitsuneLab"; public override string ModuleDescription => "Restrict certain players from connecting to your server."; @@ -64,20 +97,29 @@ public class SteamRestrictPlugin : BasePlugin, IPluginConfig private CounterStrikeSharp.API.Modules.Timers.Timer?[] g_hAuthorize = new CounterStrikeSharp.API.Modules.Timers.Timer?[65]; + private BypassConfig? _bypassConfig; public PluginConfig Config { get; set; } = new(); public void OnConfigParsed(PluginConfig config) { - Config = config; + if (config.Version < Config.Version) + base.Logger.LogWarning("Configuration version mismatch (Expected: {0} | Current: {1})", this.Config.Version, config.Version); if (string.IsNullOrEmpty(config.SteamWebAPI)) - { - Logger.LogError("This plugin won't work because Web API is empty."); - } + base.Logger.LogError("This plugin won't work because Web API is empty."); + + Config = config; } public override void Load(bool hotReload) { + string bypassConfigFilePath = "bypass_config.json"; + var bypassConfigService = new BypassConfigService(bypassConfigFilePath); + _bypassConfig = bypassConfigService.LoadConfig(); + + var databaseService = new DatabaseService(Config.DatabaseSettings); + _ = databaseService.EnsureTablesExistAsync(); + RegisterListener(() => { g_bSteamAPIActivated = true; }); RegisterListener((int slot, string name, string ipAddress) => { g_hAuthorize[slot]?.Kill(); }); RegisterListener((int slot) => { g_hAuthorize[slot]?.Kill(); }); @@ -96,8 +138,7 @@ public override void Load(bool hotReload) public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventInfo info) { - CCSPlayerController player = @event.Userid; - + CCSPlayerController? player = @event.Userid; if (player == null) return HookResult.Continue; @@ -131,10 +172,21 @@ private void OnPlayerConnectFull(CCSPlayerController player) if (!g_bSteamAPIActivated) return; - nint handle = player.Handle; ulong authorizedSteamID = player.AuthorizedSteamID.SteamId64; + nint handle = player.Handle; + + var databaseService = new DatabaseService(Config.DatabaseSettings); - _ = CheckUserViolations(handle, authorizedSteamID); + Task.Run(async () => + { + if (await databaseService.IsSteamIdAllowedAsync(authorizedSteamID)) + { + Server.NextWorldUpdate(() => Logger.LogInformation($"{player.PlayerName} ({authorizedSteamID}) was allowed to join without validations because they were found in the database.")); + return; + } + + await CheckUserViolations(handle, authorizedSteamID); + }); } private async Task CheckUserViolations(nint handle, ulong authorizedSteamID) @@ -168,6 +220,16 @@ private async Task CheckUserViolations(nint handle, ulong authorizedSteamID) { Server.ExecuteCommand($"kickid {player.UserId} \"You have been kicked for not meeting the minimum requirements.\""); } + else + { + ulong steamID = player.AuthorizedSteamID?.SteamId64 ?? 0; + + if (steamID != 0) + { + var databaseService = new DatabaseService(Config.DatabaseSettings); + Task.Run(async () => await databaseService.AddAllowedUserAsync(steamID, Config.DatabaseSettings.TablePurgeDays)); + } + } } }); } @@ -177,21 +239,23 @@ private bool IsRestrictionViolated(CCSPlayerController player, SteamUserInfo use if (AdminManager.PlayerHasPermissions(player, "@css/bypasspremiumcheck")) return false; + BypassConfig bypassConfig = _bypassConfig ?? new BypassConfig(); + bool isPrime = userInfo.HasPrime; var configChecks = new[] { - (isPrime, Config.MinimumHourPrime, userInfo.CS2Playtime), - (isPrime, Config.MinimumLevelPrime, userInfo.SteamLevel), - (isPrime, Config.MinimumCS2LevelPrime, userInfo.CS2Level), - (!isPrime, Config.MinimumHourNonPrime, userInfo.CS2Playtime), - (!isPrime, Config.MinimumLevelNonPrime, userInfo.SteamLevel), - (!isPrime, Config.MinimumCS2LevelNonPrime, userInfo.CS2Level), - (true, Config.MinimumSteamAccountAgeInDays, (DateTime.Now - userInfo.SteamAccountAge).TotalDays), - (Config.BlockPrivateProfile, 1, userInfo.IsPrivate ? 0 : 1), - (Config.BlockTradeBanned, 1, userInfo.IsTradeBanned ? 0 : 1), - (Config.BlockGameBanned, 1, userInfo.IsGameBanned ? 0 : 1), - (!string.IsNullOrEmpty(Config.SteamGroupID), 1, userInfo.IsInSteamGroup ? 1 : 0), - (Config.BlockVACBanned, 1, userInfo.IsVACBanned ? 0 : 1), + (isPrime && !bypassConfig.BypassMinimumCS2Level, Config.MinimumCS2LevelPrime, userInfo.CS2Level), + (!isPrime && !bypassConfig.BypassMinimumCS2Level, Config.MinimumCS2LevelNonPrime, userInfo.CS2Level), + (isPrime && !bypassConfig.BypassMinimumHours, Config.MinimumHourPrime, userInfo.CS2Playtime), + (!isPrime && !bypassConfig.BypassMinimumHours, Config.MinimumHourNonPrime, userInfo.CS2Playtime), + (isPrime && !bypassConfig.BypassMinimumLevel, Config.MinimumLevelPrime, userInfo.SteamLevel), + (!isPrime && !bypassConfig.BypassMinimumLevel, Config.MinimumLevelNonPrime, userInfo.SteamLevel), + (!bypassConfig.BypassMinimumSteamAccountAge, Config.MinimumSteamAccountAgeInDays, (DateTime.Now - userInfo.SteamAccountAge).TotalDays), + (Config.BlockPrivateProfile && !bypassConfig.BypassPrivateProfile, 1, userInfo.IsPrivate ? 0 : 1), + (Config.BlockTradeBanned && !bypassConfig.BypassTradeBanned, 1, userInfo.IsTradeBanned ? 0 : 1), + (Config.BlockGameBanned && !bypassConfig.BypassGameBanned, 1, userInfo.IsGameBanned ? 0 : 1), + (!string.IsNullOrEmpty(Config.SteamGroupID) && !bypassConfig.BypassSteamGroupCheck, 1, userInfo.IsInSteamGroup ? 1 : 0), + (Config.BlockVACBanned && !bypassConfig.BypassVACBanned, 1, userInfo.IsVACBanned ? 0 : 1), }; return configChecks.Any(check => check.Item1 && check.Item2 != -1 && check.Item3 < check.Item2); diff --git a/src/KitsuneSteamRestrict.csproj b/src/KitsuneSteamRestrict.csproj index 0be3018..b902ef3 100644 --- a/src/KitsuneSteamRestrict.csproj +++ b/src/KitsuneSteamRestrict.csproj @@ -15,6 +15,8 @@ runtime compile; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/src/Models/BypassConfigService.cs b/src/Models/BypassConfigService.cs new file mode 100644 index 0000000..6c2c479 --- /dev/null +++ b/src/Models/BypassConfigService.cs @@ -0,0 +1,58 @@ +using System.Text.Json; + +namespace KitsuneSteamRestrict +{ + public class BypassConfig + { + public ulong SteamID { get; set; } + public bool BypassMinimumCS2Level { get; set; } = false; + public bool BypassMinimumHours { get; set; } = false; + public bool BypassMinimumLevel { get; set; } = false; + public bool BypassMinimumSteamAccountAge { get; set; } = false; + public bool BypassPrivateProfile { get; set; } = false; + public bool BypassTradeBanned { get; set; } = false; + public bool BypassVACBanned { get; set; } = false; + public bool BypassSteamGroupCheck { get; set; } = false; + public bool BypassGameBanned { get; set; } = false; + } + + public class BypassConfigService + { + private readonly string _configFilePath; + + public BypassConfigService(string configFilePath) + { + _configFilePath = configFilePath; + } + + public BypassConfig LoadConfig() + { + if (File.Exists(_configFilePath)) + { + string json = File.ReadAllText(_configFilePath); + return JsonSerializer.Deserialize(json)!; + } + else + { + BypassConfig defaultConfig = new BypassConfig + { + SteamID = 76561198345583467, + BypassMinimumCS2Level = true, + BypassMinimumHours = false, + BypassMinimumLevel = true, + BypassMinimumSteamAccountAge = false, + BypassPrivateProfile = true, + BypassTradeBanned = false, + BypassVACBanned = true, + BypassSteamGroupCheck = false, + BypassGameBanned = true + }; + + string json = JsonSerializer.Serialize(defaultConfig, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(_configFilePath, json); + + return defaultConfig; + } + } + } +} diff --git a/src/Models/DatabaseService.cs b/src/Models/DatabaseService.cs new file mode 100644 index 0000000..b30f974 --- /dev/null +++ b/src/Models/DatabaseService.cs @@ -0,0 +1,49 @@ +using Dapper; +using KitsuneSteamRestrict; +using MySqlConnector; + +public class DatabaseService +{ + private readonly string _tablePrefix; + private readonly string _connectionString; + + public DatabaseService(DatabaseSettings settings) + { + _tablePrefix = settings.TablePrefix; + _connectionString = $"Server={settings.Host};Port={settings.Port};Database={settings.Database};Uid={settings.Username};Pwd={settings.Password};SslMode={Enum.Parse(settings.Sslmode, true)};"; + } + + public async Task IsSteamIdAllowedAsync(ulong steamId) + { + using var connection = new MySqlConnection(_connectionString); + var result = await connection.QueryFirstOrDefaultAsync( + $"SELECT `expiration_date` FROM `{_tablePrefix}allowed_users` WHERE `steam_id` = @steamId AND `expiration_date` > NOW()", + new { steamId }); + + return result.HasValue; + } + + public async Task AddAllowedUserAsync(ulong steamId, int daysValid) + { + using var connection = new MySqlConnection(_connectionString); + await connection.ExecuteAsync( + $"INSERT INTO `{_tablePrefix}allowed_users` (`steam_id`, `expiration_date`) VALUES (@steamId, DATE_ADD(NOW(), INTERVAL @daysValid DAY))", + new { steamId, daysValid }); + } + + public async Task EnsureTablesExistAsync() + { + using var connection = new MySqlConnection(_connectionString); + await connection.ExecuteAsync($@" + CREATE TABLE IF NOT EXISTS `{_tablePrefix}allowed_users` ( + `steam_id` BIGINT UNSIGNED PRIMARY KEY, + `expiration_date` DATETIME + )"); + } + + public async Task PurgeExpiredSavesAsync() + { + using var connection = new MySqlConnection(_connectionString); + await connection.ExecuteAsync($"DELETE FROM `{_tablePrefix}allowed_users` WHERE `expiration_date` < NOW()"); + } +}