From 9ddce4bd96de85c1d96fac0a751059f8ebe85d6a Mon Sep 17 00:00:00 2001 From: SkyCopeland Date: Thu, 21 Nov 2024 18:56:31 -0500 Subject: [PATCH] SQL --- CHANGELOG.md | 3 +- Commands/BanCommands.cs | 4 +- Commands/UnbanCommands.cs | 3 +- Core.cs | 1 - CrimsonBanned.csproj | 28 +++++- FodyWeavers.xml | 4 + FodyWeavers.xsd | 141 ++++++++++++++++++++++++++++ Patches/ChatMessagePatch.cs | 1 - Patches/OnUserConnectedPatch.cs | 66 ------------- Patches/VivoxPatch.cs | 1 - Services/JSONBinService.cs | 82 ---------------- Services/SQLService.cs | 160 ++++++++++++++++++++++++++++++++ Structs/Database.cs | 110 +++++++++++++++++----- Structs/Settings.cs | 40 +++++--- 14 files changed, 445 insertions(+), 199 deletions(-) create mode 100644 FodyWeavers.xml create mode 100644 FodyWeavers.xsd delete mode 100644 Patches/OnUserConnectedPatch.cs delete mode 100644 Services/JSONBinService.cs create mode 100644 Services/SQLService.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f28270..9aa4b18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - \ No newline at end of file +`0.1.6` +- initial release \ No newline at end of file diff --git a/Commands/BanCommands.cs b/Commands/BanCommands.cs index 355b5a9..0ba18ad 100644 --- a/Commands/BanCommands.cs +++ b/Commands/BanCommands.cs @@ -58,8 +58,8 @@ private static (bool Success, PlayerInfo PlayerInfo) HandleBanOperation(ChatComm return (false, null); } - banList.Add(new Ban(playerInfo.User.CharacterName.ToString(), playerInfo.User.PlatformId, bannedTime, reason)); - Database.SaveDatabases(); + Ban ban = new Ban(playerInfo.User.CharacterName.ToString(), playerInfo.User.PlatformId, bannedTime, reason); + Database.AddBan(ban, banList); ctx.Reply($"{name} has been {banType} for {timeSpan}"); diff --git a/Commands/UnbanCommands.cs b/Commands/UnbanCommands.cs index c73b29f..30efc16 100644 --- a/Commands/UnbanCommands.cs +++ b/Commands/UnbanCommands.cs @@ -50,8 +50,7 @@ private static void HandleUnbanOperation(ChatCommandContext ctx, string name, Li } var ban = banList.First(x => x.PlayerID == playerInfo.User.PlatformId); - banList.Remove(ban); - Database.SaveDatabases(); + Database.DeleteBan(ban, banList); ctx.Reply($"{name} has been {unbanType}"); diff --git a/Core.cs b/Core.cs index 55dd057..51e9dff 100644 --- a/Core.cs +++ b/Core.cs @@ -31,7 +31,6 @@ public static void Initialize() if (hasInitialized) return; PlayerService = new PlayerService(); - _ = new JSONBinService(); Database = new Database(); diff --git a/CrimsonBanned.csproj b/CrimsonBanned.csproj index 25ec74c..0e2bd9e 100644 --- a/CrimsonBanned.csproj +++ b/CrimsonBanned.csproj @@ -11,21 +11,41 @@ 0.1.10.0 0.1.10+1.Branch.main.Sha.c33d0879382e1a0da34861ec55914ba7e637a80e - - - + + + + + all + + + all + + + - + + + + + + + + \ No newline at end of file diff --git a/FodyWeavers.xml b/FodyWeavers.xml new file mode 100644 index 0000000..a5dcf04 --- /dev/null +++ b/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/FodyWeavers.xsd b/FodyWeavers.xsd new file mode 100644 index 0000000..05e92c1 --- /dev/null +++ b/FodyWeavers.xsd @@ -0,0 +1,141 @@ + + + + + + + + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with line breaks. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with line breaks. + + + + + The order of preloaded assemblies, delimited with line breaks. + + + + + + This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. + + + + + Controls if .pdbs for reference assemblies are also embedded. + + + + + Controls if runtime assemblies are also embedded. + + + + + Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. + + + + + Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. + + + + + As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. + + + + + Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. + + + + + Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with |. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with |. + + + + + The order of preloaded assemblies, delimited with |. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/Patches/ChatMessagePatch.cs b/Patches/ChatMessagePatch.cs index ed032ec..65f78e3 100644 --- a/Patches/ChatMessagePatch.cs +++ b/Patches/ChatMessagePatch.cs @@ -35,7 +35,6 @@ public static bool Prefix(ChatMessageSystem __instance) if (DateTime.Now > ban.TimeUntil) { Database.ChatBans.Remove(ban); - Database.SaveDatabases(); ServerChatUtils.SendSystemMessageToClient(Core.EntityManager, userData, "Your chat ban has ended."); } diff --git a/Patches/OnUserConnectedPatch.cs b/Patches/OnUserConnectedPatch.cs deleted file mode 100644 index 876b3c3..0000000 --- a/Patches/OnUserConnectedPatch.cs +++ /dev/null @@ -1,66 +0,0 @@ -using CrimsonBanned.Structs; -using HarmonyLib; -using ProjectM; -using ProjectM.Network; -using Stunlock.Network; -using System; -using System.Linq; -using Unity.Entities; - -namespace CrimsonBanned.Patches; - -[HarmonyPatch(typeof(ServerBootstrapSystem), nameof(ServerBootstrapSystem.OnUserConnected))] -public static class OnUserConnected_Patch -{ - public static void Postfix(ServerBootstrapSystem __instance, NetConnectionId netConnectionId) - { - try - { - var em = __instance.EntityManager; - var userIndex = __instance._NetEndPointToApprovedUserIndex[netConnectionId]; - var serverClient = __instance._ApprovedUsersLookup[userIndex]; - var userEntity = serverClient.UserEntity; - var userData = __instance.EntityManager.GetComponentData(userEntity); - bool isNewVampire = userData.CharacterName.IsEmpty; - - if(Database.Banned.Exists(x => x.PlayerID == userData.PlatformId)) - { - var ban = Database.Banned.First(x => x.PlayerID == userData.PlatformId); - - if (DateTime.Now > ban.TimeUntil) - { - Database.Banned.Remove(ban); - Database.SaveDatabases(); - } - else - { - Entity entity = Core.EntityManager.CreateEntity(new ComponentType[3] - { - ComponentType.ReadOnly(), - ComponentType.ReadOnly(), - ComponentType.ReadOnly() - }); - - entity.Write(new KickEvent() - { - PlatformId = userData.PlatformId - }); - entity.Write(new SendEventToUser() - { - UserIndex = userData.Index - }); - entity.Write(new NetworkEventType() - { - EventId = NetworkEvents.EventId_KickEvent, - IsAdminEvent = false, - IsDebugEvent = false - }); - } - } - } - catch (Exception e) - { - Core.Log.LogError($"Failure in {nameof(ServerBootstrapSystem.OnUserConnected)}\nMessage: {e.Message} Inner:{e.InnerException?.Message}\n\nStack: {e.StackTrace}\nInner Stack: {e.InnerException?.StackTrace}"); - } - } -} diff --git a/Patches/VivoxPatch.cs b/Patches/VivoxPatch.cs index d22f981..3cee21b 100644 --- a/Patches/VivoxPatch.cs +++ b/Patches/VivoxPatch.cs @@ -44,7 +44,6 @@ public static void Prefix(VivoxConnectionSystem __instance) if (DateTime.Now > ban.TimeUntil) { Database.VoiceBans.Remove(ban); - Database.SaveDatabases(); ServerChatUtils.SendSystemMessageToClient(Core.EntityManager, user, "Your voice ban has expired. Please verify in your social settings that Voice Proximity is re-enabled."); diff --git a/Services/JSONBinService.cs b/Services/JSONBinService.cs deleted file mode 100644 index b655449..0000000 --- a/Services/JSONBinService.cs +++ /dev/null @@ -1,82 +0,0 @@ -using CrimsonBanned.Structs; -using System; -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; - -namespace CrimsonBanned.Services; - -internal class JSONBinService -{ - private static HttpClient HttpClient; - - public JSONBinService() - { - if (!Settings.JSONBinConfigured) return; - if (HttpClient != null) return; - - HttpClient = new HttpClient(); - - var masterKey = Settings.JSONBinAPIKey.Value.Trim().Replace(" ", ""); - HttpClient.DefaultRequestHeaders.Add("X-Master-Key", masterKey); - HttpClient.DefaultRequestHeaders.Add("X-Bin-Versioning", "false"); - HttpClient.BaseAddress = new Uri("https://api.jsonbin.io/v3"); - - Plugin.LogInstance.LogInfo("JSONBin API initialized."); - } - - public static async Task GetBans() - { - if (HttpClient == null) new JSONBinService(); - - var request = new HttpRequestMessage(HttpMethod.Get, $"/b/{Settings.JSONBinID.Value.Trim()}"); - request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); - - var result = await HttpClient.SendAsync(request); - if (result.IsSuccessStatusCode) - { - var content = await result.Content.ReadAsStringAsync(); - var json = JsonSerializer.Deserialize(content); - return json.record; - } - - Plugin.LogInstance.LogError($"Failed to get bans: {result.StatusCode} - {result.ReasonPhrase}"); - return null; - } - - public static async Task UpdateBans(BansContainer container) - { - string json = JsonSerializer.Serialize(container, Database.prettyJsonOptions); - var content = new StringContent(json, Encoding.UTF8, "application/json"); - var result = await HttpClient.PutAsync($"b/{Settings.JSONBinID}", content); - - if (result.IsSuccessStatusCode) - { - return true; - } - else - { - Plugin.LogInstance.LogError($"Failed to update bans: {result.StatusCode} - {result.ReasonPhrase}"); - return false; - } - } -} - -public class JsonErrorResponse -{ - public string Message { get; set; } -} - -public class JSONBinResponse -{ - public BansContainer record { get; set; } - public JsonMetadata metadata { get; set; } -} - -public class JsonMetadata -{ - public string id { get; set; } - public bool @private { get; set; } - public string createdAt { get; set; } -} diff --git a/Services/SQLService.cs b/Services/SQLService.cs new file mode 100644 index 0000000..81f7b5f --- /dev/null +++ b/Services/SQLService.cs @@ -0,0 +1,160 @@ +using System; +using System.Data; +using System.Reflection; +using CrimsonBanned.Structs; +using MySql.Data.MySqlClient; + +namespace CrimsonBanned.Services; +internal class SQLService +{ + private static string connectionString; + + public SQLService() + { + var assembly = Assembly.GetExecutingAssembly(); + AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => + { + string resourceName = args.Name switch + { + string name when name.StartsWith("MySql.Data") => "CrimsonBanned._9._1._0.lib.net6._0.MySql.Data.dll", + string name when name.StartsWith("System.Diagnostics.DiagnosticSource") => "CrimsonBanned._8._0._1.lib.net6._0.System.Diagnostics.DiagnosticSource.dll", + string name when name.StartsWith("System.Security.Permissions") => "CrimsonBanned._8._0._0.lib.net6._0.System.Security.Permissions.dll", + string name when name.StartsWith("System.Configuration.ConfigurationManager") => "CrimsonBanned._8._0._0.lib.net6._0.System.Configuration.ConfigurationManager.dll", + string name when name.StartsWith("System.Text.Encoding.CodePages") => "CrimsonBanned._8._0._0.lib.net6._0.System.Text.Encoding.CodePages.dll", + _ => null + }; + + if (resourceName != null) + { + using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream != null) + { + var assemblyData = new byte[stream.Length]; + stream.Read(assemblyData, 0, assemblyData.Length); + return Assembly.Load(assemblyData); + } + } + return null; + }; + + connectionString = $"Server={Settings.Host.Value};Database={Settings.MySQLDbName.Value};User ID={Settings.UserName.Value};Password={Settings.Password.Value};"; + } + + public void Connect() + { + using (var connection = new MySqlConnection(connectionString)) + { + connection.Open(); + // Connection successful + } + } + + public void InsertBan(string tableName, string playerName, ulong playerID, string reason, DateTime timeUntil) + { + if(!Settings.MySQLConfigured) return; + + string query = $@" + INSERT INTO {tableName} (PlayerName, PlayerID, Reason, TimeUntil) + VALUES (@PlayerName, @PlayerID, @Reason, @TimeUntil);"; + + using (var connection = new MySqlConnection(connectionString)) + using (var command = new MySqlCommand(query, connection)) + { + command.Parameters.AddWithValue("@PlayerName", playerName); + command.Parameters.AddWithValue("@PlayerID", playerID); + command.Parameters.AddWithValue("@Reason", reason); + command.Parameters.AddWithValue("@TimeUntil", timeUntil); + + connection.Open(); + command.ExecuteNonQuery(); + } + } + + public DataTable GetBans(string tableName) + { + string query = $"SELECT * FROM {tableName};"; + + using (var connection = new MySqlConnection(connectionString)) + using (var command = new MySqlCommand(query, connection)) + { + connection.Open(); + using (var reader = command.ExecuteReader()) + { + DataTable results = new DataTable(); + results.Load(reader); + return results; + } + } + } + + public void DeleteBan(string tableName, ulong playerId) + { + string query = $@" + DELETE FROM {tableName} + WHERE PlayerID = @PlayerID;"; + + using (var connection = new MySqlConnection(connectionString)) + using (var command = new MySqlCommand(query, connection)) + { + command.Parameters.AddWithValue("@PlayerID", playerId); + + connection.Open(); + command.ExecuteNonQuery(); + } + } + + public void ExecuteQuery(string query) + { + using (var connection = new MySqlConnection(connectionString)) + { + connection.Open(); + using (var command = new MySqlCommand(query, connection)) + { + command.ExecuteNonQuery(); + } + } + } + + public MySqlDataReader ExecuteReader(string query) + { + var connection = new MySqlConnection(connectionString); + connection.Open(); + var command = new MySqlCommand(query, connection); + return command.ExecuteReader(CommandBehavior.CloseConnection); + } + + public void InitializeTables() + { + string[] tableCreationQueries = { + @" + CREATE TABLE IF NOT EXISTS Banned ( + Id INT AUTO_INCREMENT PRIMARY KEY, + PlayerName VARCHAR(255) NOT NULL, + PlayerID BIGINT UNSIGNED NOT NULL, + TimeUntil DATETIME NOT NULL, + Reason TEXT + );", + @" + CREATE TABLE IF NOT EXISTS Chat ( + Id INT AUTO_INCREMENT PRIMARY KEY, + PlayerName VARCHAR(255) NOT NULL, + PlayerID BIGINT UNSIGNED NOT NULL, + TimeUntil DATETIME NOT NULL, + Reason TEXT + );", + @" + CREATE TABLE IF NOT EXISTS Voice ( + Id INT AUTO_INCREMENT PRIMARY KEY, + PlayerName VARCHAR(255) NOT NULL, + PlayerID BIGINT UNSIGNED NOT NULL, + TimeUntil DATETIME NOT NULL, + Reason TEXT + );" + }; + + foreach (var query in tableCreationQueries) + { + ExecuteQuery(query); + } + } +} diff --git a/Structs/Database.cs b/Structs/Database.cs index 544cbe0..65e30e1 100644 --- a/Structs/Database.cs +++ b/Structs/Database.cs @@ -1,5 +1,7 @@ using CrimsonBanned.Services; +using System; using System.Collections.Generic; +using System.Data; using System.IO; using System.Text.Json; @@ -21,6 +23,8 @@ internal class Database public static List ChatBans; public static List VoiceBans; + public static SQLService SQL; + public Database() { LoadDatabases(); @@ -28,23 +32,6 @@ public Database() private static async void LoadDatabases() { - if (Settings.JSONBinConfigured) - { - BansContainer container = await JSONBinService.GetBans(); - - if (container != null) - { - ChatBans = container.ChatBans; - VoiceBans = container.VoiceBans; - return; - } - else - { - Plugin.LogInstance.LogError("Failed to load database. Defaulting to local files."); - Settings.JSONBinConfigured = false; - } - } - if (!Directory.Exists(Plugin.ConfigFiles)) { Directory.CreateDirectory(Plugin.ConfigFiles); } if (File.Exists(ChatBanFile)) @@ -63,7 +50,7 @@ private static async void LoadDatabases() VoiceBans = JsonSerializer.Deserialize>(json, prettyJsonOptions); } else - { + { VoiceBans = new List(); } @@ -76,18 +63,63 @@ private static async void LoadDatabases() { Banned = new List(); } + + StartSQLConnection(); + } + + private static void StartSQLConnection() + { + if (!Settings.MySQLConfigured) return; + + SQL = new(); + SQL.Connect(); + SQL.InitializeTables(); } - public static async void SaveDatabases() + public static void AddBan(Ban ban, List list) { - if (Settings.JSONBinConfigured) + if (list == ChatBans) { - if (await JSONBinService.UpdateBans(new BansContainer(ChatBans, VoiceBans))) - { - return; - } + ChatBans.Add(ban); + SQL.InsertBan("Chat", ban.PlayerName, ban.PlayerID, ban.Reason, ban.TimeUntil); + } + else if (list == VoiceBans) + { + VoiceBans.Add(ban); + SQL.InsertBan("Voice", ban.PlayerName, ban.PlayerID, ban.Reason, ban.TimeUntil); + } + else + { + Banned.Add(ban); + SQL.InsertBan("Banned", ban.PlayerName, ban.PlayerID, ban.Reason, ban.TimeUntil); + } + + SaveDatabases(); + } + + public static void DeleteBan(Ban ban, List list) + { + if (list == ChatBans) + { + ChatBans.Remove(ban); + SQL.DeleteBan("Chat", ban.PlayerID); + } + else if (list == VoiceBans) + { + VoiceBans.Remove(ban); + SQL.DeleteBan("Voice", ban.PlayerID); + } + else + { + Banned.Remove(ban); + SQL.DeleteBan("Banned", ban.PlayerID); } + SaveDatabases(); + } + + private static void SaveDatabases() + { if (ChatBans.Count > 0) { string json = JsonSerializer.Serialize(ChatBans, prettyJsonOptions); @@ -106,4 +138,32 @@ public static async void SaveDatabases() File.WriteAllText(BannedFile, json); } } -} + + public static void SyncDB() + { + DataTable ChatDB = SQL.GetBans("Chat"); + + SyncTable(ChatBans, "Chat"); + SyncTable(VoiceBans, "Voice"); + SyncTable(Banned, "Banned"); + } + + private static void SyncTable(List list, string tableName) + { + DataTable table = SQL.GetBans(tableName); + foreach (DataRow row in table.Rows) + { + Ban ban = new Ban( + row["PlayerName"].ToString(), + Convert.ToUInt64(row["PlayerID"]), + Convert.ToDateTime(row["TimeUntil"]), + row["Reason"].ToString() + ); + + if (!list.Contains(ban)) + { + list.Add(ban); + } + } + } +} \ No newline at end of file diff --git a/Structs/Settings.cs b/Structs/Settings.cs index af7dc41..9d29af1 100644 --- a/Structs/Settings.cs +++ b/Structs/Settings.cs @@ -1,5 +1,6 @@ using BepInEx; using BepInEx.Configuration; +using ProjectM.UI; using System.Collections.Generic; using System.IO; @@ -8,10 +9,14 @@ namespace CrimsonBanned.Structs; public readonly struct Settings { public static ConfigEntry ShadowBan { get; private set; } - public static ConfigEntry JSONBinAPIKey { get; private set; } - public static ConfigEntry JSONBinID { get; private set; } - public static bool JSONBinConfigured { get; set; } = false; + public static ConfigEntry MySQLDbName { get; private set; } + public static ConfigEntry Host { get; private set; } + public static ConfigEntry Port { get; private set; } + public static ConfigEntry UserName { get; private set; } + public static ConfigEntry Password { get; private set; } + + public static bool MySQLConfigured { get; set; } = false; public static void InitConfig() { @@ -23,18 +28,25 @@ public static void InitConfig() ShadowBan = InitConfigEntry("_Config", "ShadowBan", true, "If this is set to true, the player will never be notified that they are banned."); - /* - JSONBinAPIKey = InitConfigEntry("_Config", "JSONBinAPIKey", string.Empty, - "Utilizing a JSONBin.io account, you can sync bans between servers. Consult the Thunderstore wiki on setup."); - - JSONBinID = InitConfigEntry("_Config", "JSONBinID", string.Empty, - "The Bin ID to acess for ban information. Consult the Thunderstore wiki on setup."); + // MySQL DB + MySQLDbName = InitConfigEntry("ServerConnection", "MySQLDbName", "", + "The name of your MySQL database."); + Host = InitConfigEntry("ServerConnection", "Host", "", + "The host address of your MySQL database."); + Port = InitConfigEntry("ServerConnection", "Port", 0, + "The port of your database server."); + UserName = InitConfigEntry("ServerConnection", "Username", "", + "The login username for your database."); + Password = InitConfigEntry("ServerConnection", "Password", "", + "The login password for your database."); - if (!string.IsNullOrEmpty(JSONBinAPIKey.Value) && !string.IsNullOrEmpty(JSONBinID.Value)) - { - JSONBinConfigured = true; - } - */ + if ( + !string.IsNullOrEmpty(MySQLDbName.Value) + && !string.IsNullOrEmpty(Host.Value) + && Port.Value != 0 + && !string.IsNullOrEmpty(UserName.Value) + && !string.IsNullOrEmpty(Password.Value) + ) MySQLConfigured = true; } static ConfigEntry InitConfigEntry(string section, string key, T defaultValue, string description)