diff --git a/Entities/ApiResponseEntity.cs b/Entities/ApiResponseEntity.cs index dbcfc18..cd29088 100644 --- a/Entities/ApiResponseEntity.cs +++ b/Entities/ApiResponseEntity.cs @@ -36,13 +36,23 @@ public CommandsResponseEntity(int status, List data) public class ServerResponseEntity { - public int id { get; set; } + public int id { get; set; } = 0; - public ServerResponseEntity(int id) + public ServerResponseEntity(int id = 0) { this.id = id; } } + public class ServerVerifyResponseEntity + { + + public string message { get; set; } = ""; + public ServerVerifyResponseEntity(string message) + { + this.message = message; + } + } + } \ No newline at end of file diff --git a/Entities/PlayerUpdateDto.cs b/Entities/PlayerUpdateDto.cs deleted file mode 100644 index 7e7814b..0000000 --- a/Entities/PlayerUpdateDto.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace GameCMS -{ - - public class PlayerUpdateDto - { - public ulong SteamId { get; set; } - public string Username { get; set; } - public int ServerId { get; set; } - public DateTime TodayDate { get; set; } - public long TodayUnixTimestamp { get; set; } - public int? Ct { get; set; } - public int? T { get; set; } - public int? Spec { get; set; } - } -} diff --git a/GameCMSPlugin.cs b/GameCMSPlugin.cs index bae1a49..fdb0911 100644 --- a/GameCMSPlugin.cs +++ b/GameCMSPlugin.cs @@ -14,6 +14,7 @@ using System.Text.RegularExpressions; using MySqlConnector; using Dapper; + using CounterStrikeSharp.API.Modules.Cvars; public sealed partial class GameCMSPlugin : BasePlugin, IPluginConfig { @@ -24,6 +25,7 @@ public sealed partial class GameCMSPlugin : BasePlugin, IPluginConfig", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] + [RequiresPermissions("@css/root")] + public void onCommandServerVerifyAsync(CCSPlayerController? player, CommandInfo command) + { + var moduleFolderName = Path.GetFileName(ModuleDirectory); + var filePath = _helper.GetFilePath($"configs/plugins"); + filePath = _helper.GetFilePath($"{filePath}/{moduleFolderName}"); + filePath = _helper.GetFilePath($"{filePath}/{moduleFolderName}.json"); + + + + if (!_httpServer.IsHttpServerRunning()) + { + command.ReplyToCommand("GameCMS is no listening for requiests, please ensure you have entered free and open ServerHttpPort, and you have give your server a restart."); + return; + } + + if (!dbConnected) + { + command.ReplyToCommand("Database not connected! Please ensure you have connected your database. Before run this command."); + return; + } + + string ServerKey = command.GetArg(1); + + if (ServerKey.Length < 64) + { + command.ReplyToCommand("Invalid Server API Key! Please ensure you have copied the right Server API Key!"); + return; + } + + var url = $"{API_URI_BASE}/server-verify/cs2"; + + HttpRequestMessage request = _helper.GetServerRequestHeaders(ServerKey); + request.RequestUri = new Uri(url); + request.Method = HttpMethod.Post; + + var address = _helper.GetServerIp(); + var port = ConVar.Find("hostport")?.GetPrimitiveValue()!.ToString() ?? "27015"; + var httpPort = Config.ServerHttpPort; + + string encryptionKey = ServerKey.Substring(0, 32); + var formData = new Dictionary + { + { "address", address }, + { "port", port.ToString() }, + { "httpPort", httpPort.ToString() }, + { "dbHost", _helper.EncryptString(Config.database.host, encryptionKey) }, + { "dbPort", _helper.EncryptString(Config.database.port.ToString(), encryptionKey) }, + { "dbName", _helper.EncryptString(Config.database.name, encryptionKey) }, + { "dbUsername", _helper.EncryptString(Config.database.username, encryptionKey) }, + { "dbPassword", _helper.EncryptString(Config.database.password, encryptionKey) } + }; + + request.Content = new FormUrlEncodedContent(formData); + try + { + var responseTask = client.SendAsync(request).ConfigureAwait(false); + var response = responseTask.GetAwaiter().GetResult(); + var readTask = response.Content.ReadAsStringAsync().ConfigureAwait(false); + string responseBody = readTask.GetAwaiter().GetResult(); + if (response.StatusCode == HttpStatusCode.OK) + { + var apiResponse = JsonSerializer.Deserialize(responseBody); + if (apiResponse != null) + { + serverId = apiResponse.id; + Config.ServerApiKey = ServerKey; + string jsonString = JsonSerializer.Serialize(Config, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(filePath, jsonString); + command.ReplyToCommand($"[GameCMS.ORG] Server verified successfully!"); + } + else + { + command.ReplyToCommand("[GameCMS.ORG] Server verification failed: Unexpected response from the server."); + } + } + else + { + + var apiResponse = JsonSerializer.Deserialize(responseBody); + string message = "[GameCMS.ORG] Server verification failed: Unexpected error occurred"; + if (apiResponse != null) + { + message = $"[GameCMS.ORG] {apiResponse.message}"; + } + command.ReplyToCommand(message); + } + } + catch (Exception ex) + { + command.ReplyToCommand($"[GameCMS.ORG] Exception occurred during server verification: {ex.Message}"); + } + + + + + + } [ConsoleCommand("css_gcms_reload_admins")] [CommandHelper(minArgs: 0, whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] [RequiresPermissions("@css/root")] - public void OnCMSReloadAdmins(CCSPlayerController? player, CommandInfo command) + public void onCommandReloadAdmins(CCSPlayerController? player, CommandInfo command) { _adminService.ProgressAdminsData(serverId, Config.DeleteExpiredAdmins); } @@ -199,6 +299,7 @@ public void OnConfigParsed(GameCMSConfig config) try { Database.Initialize(config, Logger); + dbConnected = true; Logger.LogInformation("Connected to the database."); } catch (Exception ex) diff --git a/GameCMSPlugin.csproj b/GameCMSPlugin.csproj index 228cf13..aef09d8 100644 --- a/GameCMSPlugin.csproj +++ b/GameCMSPlugin.csproj @@ -8,7 +8,7 @@ false enable enable - + true @@ -19,10 +19,12 @@ + C:\Users\ovcha\Desktop\gamecms-cs2\shared-apis\VipCoreApi.dll + false \ No newline at end of file diff --git a/Helper.cs b/Helper.cs index edf1dcc..e307368 100644 --- a/Helper.cs +++ b/Helper.cs @@ -1,7 +1,12 @@ namespace GameCMS { + + using System; + using System.Security.Cryptography; + using System.Text; using System.Net.Http.Headers; + using System.Runtime.InteropServices; using System.Text.Json; using System.Text.RegularExpressions; using CounterStrikeSharp.API; @@ -12,8 +17,44 @@ public class Helper { private string _directory = string.Empty; + + public delegate nint CNetworkSystem_UpdatePublicIp(nint a1); + public static CNetworkSystem_UpdatePublicIp? _networkSystemUpdatePublicIp; + + private Dictionary _playersTimeCollection = new Dictionary(); + + // This method is taken from + //https://github.com/daffyyyy/CS2-SimpleAdmin/blob/main/Helper.cs + //thanks to daffyyyy + public string GetServerIp() + { + var networkSystem = NativeAPI.GetValveInterface(0, "NetworkSystemVersion001"); + + unsafe + { + if (_networkSystemUpdatePublicIp == null) + { + var funcPtr = *(nint*)(*(nint*)(networkSystem) + 256); + _networkSystemUpdatePublicIp = Marshal.GetDelegateForFunctionPointer(funcPtr); + } + /* + struct netadr_t + { + uint32_t type + uint8_t ip[4] + uint16_t port + } + */ + // + 4 to skip type, because the size of uint32_t is 4 bytes + var ipBytes = (byte*)(_networkSystemUpdatePublicIp(networkSystem) + 4); + // port is always 0, use the one from convar "hostport" + return $"{ipBytes[0]}.{ipBytes[1]}.{ipBytes[2]}.{ipBytes[3]}"; + } + } + + public void AddPlayerToTimeCollection(ulong steam_id) { long currentUnixTimestampSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); @@ -92,6 +133,33 @@ public HttpRequestMessage GetServerRequestHeaders(string serverApiKey) return request; } + public string EncryptString(string text, string key) + { + // Ensure the key is 32 bytes long for AES-256 + using (Aes aes = Aes.Create()) + { + aes.Key = Encoding.UTF8.GetBytes(key.Substring(0, 32)); // Use first 32 bytes (64 hex characters) + aes.GenerateIV(); + aes.Mode = CipherMode.CBC; + + ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); + + using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) + { + ms.Write(aes.IV, 0, aes.IV.Length); // Prepend IV to the ciphertext + using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + { + using (StreamWriter sw = new StreamWriter(cs)) + { + sw.Write(text); + } + } + + return Convert.ToBase64String(ms.ToArray()); + } + } + } + public string[] DeserializeJsonStringArray(string json) { if (string.IsNullOrWhiteSpace(json) || json == "{}") diff --git a/Services/HttpServerSerice.cs b/Services/HttpServerSerice.cs index d986a8d..5f29bbc 100644 --- a/Services/HttpServerSerice.cs +++ b/Services/HttpServerSerice.cs @@ -197,6 +197,11 @@ private async Task HandleCommandsRoute(HttpListenerContext context) await SendJsonResponse(context, new { message = "Command processed" }); } + private async Task HandlePluginsListRoute(HttpListenerContext context) + { + + } + private async Task HandlePlayersRoute(HttpListenerContext context) { TaskCompletionSource tcs = new TaskCompletionSource(); @@ -450,6 +455,7 @@ private void SetRoutings() {("/check-connection", "GET"), HandleCheckConnection}, {("/command", "POST"), HandleCommandsRoute}, {("/players", "GET"), HandlePlayersRoute}, + {("/plugins", "GET"), HandlePluginsListRoute}, {("/main-configs/admins", "GET"), (context) => HandleGetFile(context, "Admins")}, {("/main-configs/admins", "POST"), (context) => HandleUpdateFile(context, "Admins") }, @@ -471,5 +477,9 @@ private void SetRoutings() }; } + public bool IsHttpServerRunning() + { + return isRunning; + } } } \ No newline at end of file diff --git a/Services/PlayingTimeService.cs b/Services/PlayingTimeService.cs index 27477a6..02b2343 100644 --- a/Services/PlayingTimeService.cs +++ b/Services/PlayingTimeService.cs @@ -39,7 +39,7 @@ public void Start(bool hotReload, int serverId) _logger.LogInformation("Starting playing time service"); _plugin.RegisterEventHandler((EventPlayerConnectFull @event, GameEventInfo info) => { - CCSPlayerController player = @event.Userid; + CCSPlayerController player = @event.Userid!; if (player is null || !player.IsValid || !player.PlayerPawn.IsValid || player.IsBot) return HookResult.Continue; if (Players.Any(p => p._controller == player)) @@ -86,7 +86,7 @@ public void Start(bool hotReload, int serverId) _plugin.RegisterEventHandler((EventPlayerDisconnect @event, GameEventInfo info) => { - PlayerModel? playerModel = GetPlayer(@event.Userid); + PlayerModel? playerModel = GetPlayer(@event.Userid!); if (playerModel is null || !playerModel.IsValid || !playerModel.IsPlayer) return HookResult.Continue; @@ -104,7 +104,7 @@ public void Start(bool hotReload, int serverId) _plugin.RegisterEventHandler((EventPlayerTeam @event, GameEventInfo info) => { - PlayerModel? playerModel = GetPlayer(@event.Userid); + PlayerModel? playerModel = GetPlayer(@event.Userid!); if (playerModel is null || !playerModel.IsValid || !playerModel.IsPlayer) return HookResult.Continue; diff --git a/Services/WebstoreSerivce.cs b/Services/WebstoreSerivce.cs index 6841772..519f491 100644 --- a/Services/WebstoreSerivce.cs +++ b/Services/WebstoreSerivce.cs @@ -14,27 +14,30 @@ public class WebstoreService private readonly ILogger _logger; private readonly Helper _helper; private readonly HttpClient client = new HttpClient(); - private System.Threading.Timer _timer; private readonly TimeSpan _interval = TimeSpan.FromSeconds(60); // Interval of 60 seconds private string _serverApiKey = string.Empty; + private string API_URI_BASE = string.Empty; + public WebstoreService(ILogger logger, Helper helper) { _logger = logger; _helper = helper; } - public void ListenForCommands(string ServerApiKey) + public void ListenForCommands(string ServerApiKey, string API_URI_BASE) { _logger.LogInformation("Start listening for webstore commands"); _serverApiKey = ServerApiKey; + this.API_URI_BASE = $"{API_URI_BASE}/commands"; var startTimeSpan = TimeSpan.Zero; var periodTimeSpan = _interval; - _timer = new System.Threading.Timer((e) => - { - TryToFetchStoreCommands(); - }, null, startTimeSpan, periodTimeSpan); + + Timer _timer = new Timer((e) => + { + TryToFetchStoreCommands(); + }, null, startTimeSpan, periodTimeSpan); } public void TryToFetchStoreCommands(bool manual = false) @@ -44,7 +47,7 @@ public void TryToFetchStoreCommands(bool manual = false) private async Task FetchStoreCommands(bool manual) { - var url = "https://api.gamecms.org/v2/commands/queue/cs2"; + var url = $"{API_URI_BASE}/queue/cs2"; HttpRequestMessage request = _helper.GetServerRequestHeaders(_serverApiKey); request.RequestUri = new Uri(url); request.Method = HttpMethod.Get; @@ -111,19 +114,20 @@ private void ExecuteStoreCommands(CommandDataEntity commandData, List execu } executedCommandIds.Add(commandData.id); } + private async Task MarkCommandsAsCompleted(List commandIds) { if (commandIds == null || !commandIds.Any()) return; - var url = "https://api.gamecms.org/v2/commands/complete"; + var url = $"{API_URI_BASE}/complete"; HttpRequestMessage request = _helper.GetServerRequestHeaders(_serverApiKey); request.RequestUri = new Uri(url); request.Method = HttpMethod.Post; var jsonContent = JsonSerializer.Serialize(commandIds); - + var formData = new Dictionary { { "ids", jsonContent } }; request.Content = new FormUrlEncodedContent(formData); @@ -141,6 +145,7 @@ private async Task MarkCommandsAsCompleted(List commandIds) Console.WriteLine("Exception in MarkCommandsAsCompleted: " + ex.Message); } } + private async Task CheckIfPlayerOnline(ulong steam_id) { TaskCompletionSource tcs = new TaskCompletionSource();