Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
360 changes: 143 additions & 217 deletions TrionControlPanel.API/Controllers/TrionController.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -52,70 +52,52 @@ public static bool ServerRunningWorld()
FormData.UI.Form.MOPWorldRunning;
}

// Asynchronously checks if logon servers are running.
public static async Task ServerRunningLogonAsync()
{
var currentRunning = SystemData.GetLogonProcessesID();
if (currentRunning.Count > 0)
var current = SystemData.GetLogonProcessesID();

var runningNames = await Task.Run(() =>
{
await Task.WhenAll(currentRunning.Select(item => Task.Run(() =>
{
switch (item.Name)
{
case "WoW Classic Logon":
FormData.UI.Form.ClassicLogonRunning = IsApplicationRunning(item.ID);
break;
case "The Burning Crusade Logon":
FormData.UI.Form.TBCLogonRunning = IsApplicationRunning(item.ID);
break;
case "Wrath of the Lich King Logon":
FormData.UI.Form.WotLKLogonRunning = IsApplicationRunning(item.ID);
break;
case "Cataclysm Logon":
FormData.UI.Form.CataLogonRunning = IsApplicationRunning(item.ID);
break;
case "Mists of Pandaria Logon":
FormData.UI.Form.MOPLogonRunning = IsApplicationRunning(item.ID);
break;
case "Custom Core":
FormData.UI.Form.CustLogonRunning = IsApplicationRunning(item.ID);
break;
}
})));
}
var hs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in current)
if (IsApplicationRunning(item.ID))
hs.Add(item.Name);
return hs;
});

await Task.Run(() =>
{
FormData.UI.Form.ClassicLogonRunning = runningNames.Contains("WoW Classic Logon");
FormData.UI.Form.TBCLogonRunning = runningNames.Contains("The Burning Crusade Logon");
FormData.UI.Form.WotLKLogonRunning = runningNames.Contains("Wrath of the Lich King Logon");
FormData.UI.Form.CataLogonRunning = runningNames.Contains("Cataclysm Logon");
FormData.UI.Form.MOPLogonRunning = runningNames.Contains("Mists of Pandaria Logon");
FormData.UI.Form.CustLogonRunning = runningNames.Contains("Custom Core");
});
}

// Asynchronously checks if world servers are running.
public static async Task ServerRunningWorldAsync()
{
var currentRunning = SystemData.GetWorldProcessesID();
if (currentRunning.Count > 0)
var current = SystemData.GetWorldProcessesID();

var runningNames = await Task.Run(() =>
{
await Task.WhenAll(currentRunning.Select(item => Task.Run(() =>
{
switch (item.Name)
{
case "WoW Classic World":
FormData.UI.Form.ClassicWorldRunning = IsApplicationRunning(item.ID);
break;
case "The Burning Crusade World":
FormData.UI.Form.TBCWorldRunning = IsApplicationRunning(item.ID);
break;
case "Wrath of the Lich King World":
FormData.UI.Form.WotLKWorldRunning = IsApplicationRunning(item.ID);
break;
case "Cataclysm World":
FormData.UI.Form.CataWorldRunning = IsApplicationRunning(item.ID);
break;
case "Mists of Pandaria World":
FormData.UI.Form.MOPWorldRunning = IsApplicationRunning(item.ID);
break;
case "Custom Core":
FormData.UI.Form.CustWorldRunning = IsApplicationRunning(item.ID);
break;
}
})));
}
var hs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in current)
if (IsApplicationRunning(item.ID))
hs.Add(item.Name);
return hs;
});

await Task.Run(() =>
{
FormData.UI.Form.ClassicWorldRunning = runningNames.Contains("WoW Classic World");
FormData.UI.Form.TBCWorldRunning = runningNames.Contains("The Burning Crusade World");
FormData.UI.Form.WotLKWorldRunning = runningNames.Contains("Wrath of the Lich King World");
FormData.UI.Form.CataWorldRunning = runningNames.Contains("Cataclysm World");
FormData.UI.Form.MOPWorldRunning = runningNames.Contains("Mists of Pandaria World");
FormData.UI.Form.CustWorldRunning = runningNames.Contains("Custom Core");
});
}
public static async Task ServerRunningDatabaseAsync()
{
Expand Down
141 changes: 141 additions & 0 deletions TrionControlPanel.Desktop/Extensions/Cryptography/SRP6Hashing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,147 @@
{
return RandomNumberGenerator.GetBytes(32);
}
public class CMaNGosSRP6
{
// SRP6 constants from CMaNGOS
private const int GENERATOR = 7;
private const int SALT_BYTE_SIZE = 32;
private const int VERIFIER_BYTE_SIZE = 32;

// Large safe prime N (from CMaNGOS SRP6.cpp)
private static readonly BigInteger N = BigInteger.Parse("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", NumberStyles.HexNumber);

public static byte[] GenerateSalt()
{
return RandomNumberGenerator.GetBytes(SALT_BYTE_SIZE);
}


public static byte[] CreateVerifier(string username, string password, byte[] salt)
{
if (salt == null || salt.Length != SALT_BYTE_SIZE)
throw new ArgumentException($"Salt must be exactly {SALT_BYTE_SIZE} bytes", nameof(salt));

// Normalize username and password (CMaNGOS normalizeString function)
username = NormalizeString(username);
password = NormalizeString(password);

//Calculate SHA1 hash of "USERNAME:PASSWORD" (CalculateShaPassHash)
string credentials = $"{username}:{password}";
byte[] credentialsHash = SHA1.HashData(Encoding.UTF8.GetBytes(credentials));

//Reverse the salt (CMaNGOS uses little-endian byte order)
byte[] reversedSalt = new byte[salt.Length];
Array.Copy(salt, reversedSalt, salt.Length);
Array.Reverse(reversedSalt);

//Reverse the credentials hash
byte[] reversedCredsHash = new byte[credentialsHash.Length];
Array.Copy(credentialsHash, reversedCredsHash, credentialsHash.Length);
Array.Reverse(reversedCredsHash);

//Calculate x = SHA1(salt | credentials_hash)
byte[] combined = new byte[reversedSalt.Length + reversedCredsHash.Length];
Array.Copy(reversedSalt, 0, combined, 0, reversedSalt.Length);
Array.Copy(reversedCredsHash, 0, combined, reversedSalt.Length, reversedCredsHash.Length);

byte[] xBytes = SHA1.HashData(combined);

//Convert x to BigInteger (little-endian, unsigned)
BigInteger x = new BigInteger(xBytes, isUnsigned: true, isBigEndian: false);

// Calculate verifier v = g^x mod N
BigInteger verifier = BigInteger.ModPow(GENERATOR, x, N);

// Convert verifier to byte array (32 bytes, big-endian for storage)
byte[] verifierBytes = verifier.ToByteArray(isUnsigned: true, isBigEndian: true);

// Pad to 32 bytes if necessary
if (verifierBytes.Length < VERIFIER_BYTE_SIZE)
{
byte[] padded = new byte[VERIFIER_BYTE_SIZE];
Array.Copy(verifierBytes, 0, padded, VERIFIER_BYTE_SIZE - verifierBytes.Length, verifierBytes.Length);
return padded;
}
else if (verifierBytes.Length > VERIFIER_BYTE_SIZE)
{
// Take the last 32 bytes
byte[] trimmed = new byte[VERIFIER_BYTE_SIZE];
Array.Copy(verifierBytes, verifierBytes.Length - VERIFIER_BYTE_SIZE, trimmed, 0, VERIFIER_BYTE_SIZE);
return trimmed;
}

return verifierBytes;
}

public static bool VerifyPassword(string username, string password, byte[] salt, byte[] storedVerifier)
{
if (salt == null || salt.Length != SALT_BYTE_SIZE)
return false;

if (storedVerifier == null || storedVerifier.Length != VERIFIER_BYTE_SIZE)
return false;

byte[] calculatedVerifier = CreateVerifier(username, password, salt);

return calculatedVerifier.SequenceEqual(storedVerifier);
}

public static string BytesToHexString(byte[] bytes)
{
if (bytes == null)
throw new ArgumentNullException(nameof(bytes));

return BitConverter.ToString(bytes).Replace("-", "");

Check notice on line 171 in TrionControlPanel.Desktop/Extensions/Cryptography/SRP6Hashing.cs

View check run for this annotation

codefactor.io / CodeFactor

TrionControlPanel.Desktop/Extensions/Cryptography/SRP6Hashing.cs#L171

Prefer 'System.Convert.ToHexString(byte[])' over call chains based on 'System.BitConverter.ToString(byte[])'. (CA1872)
}

public static byte[] HexStringToBytes(string hex)
{
if (string.IsNullOrEmpty(hex))
throw new ArgumentException("Hex string cannot be null or empty", nameof(hex));

if (hex.Length % 2 != 0)
throw new ArgumentException("Hex string must have an even number of characters", nameof(hex));

byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
}
return bytes;
}

private static string NormalizeString(string input)
{
if (string.IsNullOrEmpty(input))
return input;

StringBuilder result = new StringBuilder(input.Length);

foreach (char c in input)
{
// Only uppercase ASCII letters a-z (0x61-0x7A -> 0x41-0x5A)
if (c >= 'a' && c <= 'z')
{
result.Append((char)(c - 32));
}
else
{
result.Append(c);
}
}

return result.ToString();
}

public static (string SaltHex, string VerifierHex) CreateAccountCredentials(string username, string password)
{
byte[] salt = GenerateSalt();
byte[] verifier = CreateVerifier(username, password, salt);

return (BytesToHexString(salt), BytesToHexString(verifier));
}
}

}
}
47 changes: 33 additions & 14 deletions TrionControlPanel.Desktop/Extensions/Database/AccountManager.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Text;
using TrionControlPanel.Desktop.Extensions.Classes.Monitor;
using TrionControlPanel.Desktop.Extensions.Classes.Monitor;
using TrionControlPanel.Desktop.Extensions.Cryptography;
using TrionControlPanel.Desktop.Extensions.Modules.Lists;
using static TrionControlPanel.Desktop.Extensions.Cryptography.SRP6;
using static TrionControlPanel.Desktop.Extensions.Modules.Enums;

namespace TrionControlPanel.Desktop.Extensions.Database
Expand Down Expand Up @@ -71,16 +71,12 @@ public static async Task<AccountOpResult> CreateAccount(string username, string
// --- Validation ---
if (string.IsNullOrEmpty(username) || username.Length > MaxAccountLength)
return AccountOpResult.NameTooLong;

if (string.IsNullOrEmpty(password) || password.Length > MaxPasswordLength)
return AccountOpResult.PassTooLong;

if (string.IsNullOrEmpty(email) || email.Length > MaxEmailLength)
return AccountOpResult.EmailTooLong; // Corrected from NameTooLong

return AccountOpResult.EmailTooLong;
if (await GetUser(username, settings) > 0)
return AccountOpResult.NameAlreadyExist;

if (await GetEmail(email, settings) > 0)
return AccountOpResult.EmailAlreadyExist;

Expand All @@ -93,8 +89,6 @@ public static async Task<AccountOpResult> CreateAccount(string username, string
{
case Cores.AzerothCore:
case Cores.TrinityCore335:
case Cores.CMaNGOS:
case Cores.VMaNGOS:
byte[] legacySalt = SRP6.GenerateSalt();
byte[] legacyVerifier = SRP6.LegecySHA1.CreateVerifier(username, password, legacySalt);
sql = SqlQueryManager.CreateAccount(settings.SelectedCore);
Expand All @@ -109,16 +103,23 @@ public static async Task<AccountOpResult> CreateAccount(string username, string
};
break;

case Cores.AscEmu:
var passhash = AscEmuSHA1.GetPasswordHash(username, password);
case Cores.CMaNGOS:
case Cores.VMaNGOS:
// Use CMaNGOS-specific SRP6 implementation
var (saltHex, verifierHex) = CMaNGosSRP6.CreateAccountCredentials(username, password);

sql = SqlQueryManager.CreateAccount(settings.SelectedCore);
parameters = new
{
Username = username,
EncryptedPassword = passhash,
Username = username.ToUpper(), // CMaNGOS stores username in uppercase
Salt = saltHex,
Verifier = verifierHex,
Email = email,
RegMail = email,
JoinDate = DateTime.Now,
};

TrionLogger.Log($"CMaNGOS account credentials generated - Salt: {saltHex.Substring(0, 8)}..., Verifier: {verifierHex.Substring(0, 8)}...", "DEBUG");
break;

case Cores.TrinityCore:
Expand Down Expand Up @@ -157,7 +158,6 @@ public static async Task<AccountOpResult> CreateAccount(string username, string
return AccountOpResult.DBInternalError;
}
}

/// <summary>
/// Checks if a username already exists.
/// </summary>
Expand Down Expand Up @@ -217,5 +217,24 @@ public static async Task<int> GetUserID(string username, AppSettings settings)
return 0; // Return 0 or -1 to indicate failure
}
}

public static async Task<AccountOpResult> SetGMLevel(int AccountId, int GmLevel, int RealmID, AppSettings settings)
{
try
{
object parameters = new
{
AccountId,
GmLevel,
RealmID,
};
await AccessManager.SaveData(SqlQueryManager.GrantGmLevel(settings.SelectedCore), parameters, AccessManager.ConnectionString(settings, settings.AuthDatabase));
return AccountOpResult.GMSet;
}
catch (Exception ex)
{
return AccountOpResult.Faild;
}
}
}
}
Loading
Loading