namespace Supercell.Laser.Server.SCID { using System; using System.Collections.Generic; using System.Text; using System.Threading; using MySql.Data.MySqlClient; using Newtonsoft.Json; using Supercell.Laser.Server.Database.Models; using Supercell.Laser.Server.Settings; public static class SCIDBase { private static long AccountsMaxId; private static string ConnectionString; public static void Init(string user, string password) { MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder(); builder.Server = "127.0.0.1"; builder.UserID = user; builder.Password = password; builder.SslMode = MySqlSslMode.Disabled; builder.Database = Configuration.Instance.DatabaseName; builder.CharacterSet = "utf8mb4"; JsonConvert.DefaultSettings = () => new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore }; ConnectionString = builder.ToString(); try { using var conn = new MySqlConnection(ConnectionString); conn.Open(); using var cmd = new MySqlCommand( "CREATE TABLE IF NOT EXISTS `SCIDaccounts` (" + "`Id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, " + "`ScidToken` VARCHAR(255) NOT NULL, " + "`Data` LONGTEXT NOT NULL, " + "KEY `idx_scidtoken` (`ScidToken`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", conn); cmd.ExecuteNonQuery(); } catch (Exception ex) { SCIDStart.Log($"Table init warning: {ex.Message}"); } AccountsMaxId = GetMaxAccountId(); } public static long GetMaxAccountId() { try { using var connection = new MySqlConnection(ConnectionString); connection.Open(); using MySqlCommand command = new MySqlCommand("SELECT coalesce(MAX(Id), 0) FROM SCIDaccounts", connection); return Convert.ToInt64(command.ExecuteScalar()); } catch { return 0; } } public static void Create(SCIDAccount account) { if (account == null) return; account.AccountId = Interlocked.Increment(ref AccountsMaxId); string json = JsonConvert.SerializeObject(account); using var connection = new MySqlConnection(ConnectionString); connection.Open(); using MySqlCommand command = new MySqlCommand("INSERT INTO SCIDaccounts (`ScidToken`, `Data`) VALUES (@token, @data)", connection); command.Parameters.AddWithValue("@token", account.ScidToken); command.Parameters.AddWithValue("@data", json); command.ExecuteNonQuery(); } public static void Save(SCIDAccount account) { if (account == null) return; string json = JsonConvert.SerializeObject(account); using var connection = new MySqlConnection(ConnectionString); connection.Open(); using MySqlCommand command = new MySqlCommand("UPDATE SCIDaccounts SET `Data` = @data WHERE `ScidToken` = @token", connection); command.Parameters.AddWithValue("@token", account.ScidToken); command.Parameters.AddWithValue("@data", json); command.ExecuteNonQuery(); } private static SCIDAccount Load(string token) { using var connection = new MySqlConnection(ConnectionString); connection.Open(); using MySqlCommand command = new MySqlCommand("SELECT `Data` FROM SCIDaccounts WHERE `ScidToken` = @token", connection); command.Parameters.AddWithValue("@token", token); using MySqlDataReader reader = command.ExecuteReader(); if (reader.Read()) return Deserialize(reader); return null; } public static SCIDAccount LoadAny(string input) { if (string.IsNullOrWhiteSpace(input)) return null; input = input.Trim().TrimEnd('\0'); SCIDAccount account = LoadByToken(input); if (account != null) return account; account = LoadByScid(input); if (account != null) return account; account = LoadByIdentity(input); if (account != null) return account; return null; } public static SCIDAccount LoadByToken(string token) { if (string.IsNullOrWhiteSpace(token)) return null; token = token.Trim().TrimEnd('\0'); SCIDAccount account = Load(token); if (account != null) return account; try { string[] parts = token.Split('.'); if (parts.Length >= 2) { string json = TryDecodeBase64Url(parts[1]); if (!string.IsNullOrEmpty(json)) { using var doc = System.Text.Json.JsonDocument.Parse(json); if (doc.RootElement.TryGetProperty("scid", out var scidProp)) { string scid = scidProp.GetString(); if (!string.IsNullOrEmpty(scid)) { account = LoadByScid(scid); if (account != null) return account; } } } } } catch { } return null; } private static string TryDecodeBase64Url(string value) { try { string base64 = value.Replace('-', '+').Replace('_', '/'); base64 = base64.PadRight((base64.Length + 3) / 4 * 4, '='); return Encoding.UTF8.GetString(Convert.FromBase64String(base64)); } catch { return null; } } public static SCIDAccount LoadByIdentity(string identity) { return LoadFirst(account => string.Equals(account.Identity, identity, StringComparison.OrdinalIgnoreCase)); } public static SCIDAccount LoadByScid(string scid) { return LoadFirst(account => string.Equals(account.Scid, scid, StringComparison.Ordinal)); } private static SCIDAccount LoadFirst(Func predicate) { using var connection = new MySqlConnection(ConnectionString); connection.Open(); using MySqlCommand command = new MySqlCommand("SELECT `Data` FROM SCIDaccounts", connection); using MySqlDataReader reader = command.ExecuteReader(); while (reader.Read()) { SCIDAccount account = Deserialize(reader); if (account != null && predicate(account)) return account; } return null; } private static SCIDAccount Deserialize(MySqlDataReader reader) { try { object value = reader["Data"]; if (value == null || value == DBNull.Value) return null; return JsonConvert.DeserializeObject(Convert.ToString(value)); } catch (JsonException exception) { SCIDStart.Log($"ignoring malformed SCID account data: {exception.Message}"); return null; } } public static void Delete(string token) { using MySqlConnection connection = new(ConnectionString); connection.Open(); using MySqlCommand command = new("DELETE FROM SCIDaccounts WHERE ScidToken = @token", connection); command.Parameters.AddWithValue("@token", token); command.ExecuteNonQuery(); } } }