//
// CryptographyManager.cs
//
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Buffers;
using System.Text;
namespace NosSmooth.Cryptography;
///
/// A storage of server and client cryptography.
///
public class CryptographyManager
{
///
/// Initializes a new instance of the class.
///
public CryptographyManager()
{
ServerWorld = new ServerWorldCryptography(0);
ServerLogin = new ServerLoginCryptography();
ClientLogin = new ClientLoginCryptography();
ClientWorld = new ClientWorldCryptography(0);
}
///
/// Gets the cryptography for server world.
///
public ICryptography ServerWorld { get; }
///
/// Gets the cryptography for server login.
///
public ICryptography ServerLogin { get; }
///
/// Gets the cryptography for client world.
///
public ICryptography ClientWorld { get; }
///
/// Gets the cryptography for client login.
///
public ICryptography ClientLogin { get; }
///
/// Gets or sets the encryption key of the connection.
///
public int EncryptionKey
{
get => ((ServerWorldCryptography)ServerWorld).EncryptionKey;
set
{
((ServerWorldCryptography)ServerWorld).EncryptionKey = value;
((ClientWorldCryptography)ClientWorld).EncryptionKey = value;
}
}
///
/// Attempts to decrypt a packet that was received by the client. May be login or world packet.
/// Encryption key is not needed.
///
///
/// Expects only failc or NsTeST for login packets.
///
/// The received data.
/// The encoding.
/// Decrypted packet or empty string in case of fail.
public string DecryptUnknownClientPacket(in ReadOnlySpan data, Encoding encoding)
{
if (data.Length < 6)
{
return ClientWorld.Decrypt(data, encoding);
}
var firstCharDecrypted = ClientLogin.Decrypt(data.Slice(0, 1), encoding);
if (firstCharDecrypted.StartsWith("f") ||
firstCharDecrypted.StartsWith("N"))
{
var beginning = data.Slice(0, 6);
var beginningLoginDecrypted = ClientLogin.Decrypt(beginning, encoding);
if (beginningLoginDecrypted.StartsWith("failc") ||
beginningLoginDecrypted.StartsWith("NsTeST"))
{
return ClientLogin.Decrypt(data, encoding);
}
}
return ClientWorld.Decrypt(data, encoding);
}
///
/// Attempts to decrypt a packet that was received by the server. May be login or world packet.
/// Encryption key is needed for world packets. If the packet contains session id,
/// it will be saved to .
///
///
/// Expects only failc or NsTeST for login packets.
///
/// The received data.
/// The encoding.
/// Decrypted packet or empty string in case of fail.
public string DecryptUnknownServerPacket(in ReadOnlySpan data, Encoding encoding)
{
var firstCharDecrypted = ClientLogin.Decrypt(data.Slice(0, 1), encoding);
if (firstCharDecrypted.StartsWith("N") ||
firstCharDecrypted.StartsWith("f") ||
firstCharDecrypted.StartsWith("c"))
{
var beginning = data.Slice(0, 4);
var beginningLoginDecrypted = ClientLogin.Decrypt(beginning, encoding);
if (beginningLoginDecrypted.StartsWith("NoS0") ||
beginningLoginDecrypted.StartsWith("f_st") ||
beginningLoginDecrypted.StartsWith("c_cl"))
{
return ServerLogin.Decrypt(data, encoding);
}
}
var decryptedString = ServerWorld.Decrypt(data, encoding);
if (EncryptionKey == 0)
{ // we are not in a session, so the packet may be the session id.
// or we are in an initialized session and won't know the encryption key...
var decryptedSpan = (ReadOnlySpan)decryptedString;
var firstSpaceIndex = decryptedSpan.IndexOf(' ');
if (firstSpaceIndex != -1)
{
if (int.TryParse(decryptedSpan.Slice(firstSpaceIndex + 1), out var obtainedEncryptionKey))
{
EncryptionKey = obtainedEncryptionKey;
}
}
}
return decryptedString;
}
}