//
// ServerWorldCryptography.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.Text;
using NosSmooth.Cryptography.Extensions;
namespace NosSmooth.Cryptography;
///
/// A cryptography used on world server, has to have a session id (encryption key) set from the world.
///
public class ServerWorldCryptography : ICryptography
{
///
/// Initializes a new instance of the class.
///
/// The encryption key.
public ServerWorldCryptography(int encryptionKey)
{
EncryptionKey = encryptionKey;
}
///
/// Gets or sets the encryption key.
///
public int EncryptionKey { get; set; }
///
public string Decrypt(in ReadOnlySpan str, Encoding encoding)
{
if (EncryptionKey == 0)
{
return DecryptUnauthed(str);
}
return DecryptAuthed(str, EncryptionKey, encoding);
}
///
public byte[] Encrypt(string packet, Encoding encoding)
{
byte[] strBytes = encoding.GetBytes(packet);
int bytesLength = strBytes.Length;
byte[] encryptedData = new byte[bytesLength + (int)Math.Ceiling((decimal)bytesLength / 0x7E) + 1];
int ii = 0;
for (int i = 0; i < bytesLength; i++)
{
if (i % 0x7E == 0)
{
encryptedData[i + ii] = (byte)(bytesLength - i > 0x7E ? 0x7E : bytesLength - i);
ii++;
}
encryptedData[i + ii] = (byte)~strBytes[i];
}
encryptedData[encryptedData.Length - 1] = 0xFF;
return encryptedData;
}
private static string DecryptAuthed(in ReadOnlySpan str, int encryptionKey, Encoding encoding)
{
var encryptedString = new StringBuilder(str.Length);
int sessionKey = encryptionKey & 0xFF;
byte sessionNumber = unchecked((byte)(encryptionKey >> 6));
sessionNumber &= 0xFF;
sessionNumber &= 3;
byte firstbyte = unchecked((byte)(sessionKey + 0x40));
switch (sessionNumber)
{
case 0:
case 1:
foreach (byte character in str)
{
byte highbyte = unchecked((byte)(character + firstbyte));
encryptedString.Append((char)highbyte);
}
break;
case 2:
case 3:
foreach (byte character in str)
{
byte highbyte = unchecked((byte)(character + firstbyte ^ 0xC3));
encryptedString.Append((char)highbyte);
}
break;
default:
encryptedString.Append((char)0xF);
break;
}
var temp = encryptedString.ToString().SplitAllocationless((char)0xFF);
var save = new StringBuilder(encryptedString.Length);
foreach (var packet in temp)
{
save.Append(DecryptPrivate(packet, encoding));
save.Append((char)'\n');
}
return save.ToString();
}
private static string DecryptPrivate(in ReadOnlySpan str, Encoding encoding)
{
using var receiveData = new MemoryStream();
char[] table = { ' ', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '\n' };
for (int count = 0; count < str.Length; count++)
{
if (str[count] <= 0x7A)
{
int len = str[count];
for (int i = 0; i < len; i++)
{
count++;
try
{
receiveData.WriteByte(unchecked((byte)(str[count] ^ 0xFF)));
}
catch
{
receiveData.WriteByte(255);
}
}
}
else
{
int len = str[count];
len &= 0x7F;
for (int i = 0; i < len; i++)
{
count++;
int highbyte;
try
{
highbyte = str[count];
}
catch
{
highbyte = 0;
}
highbyte &= 0xF0;
highbyte >>= 0x4;
int lowbyte;
try
{
lowbyte = str[count];
}
catch
{
lowbyte = 0;
}
lowbyte &= 0x0F;
if (highbyte != 0x0 && highbyte != 0xF)
{
receiveData.WriteByte(unchecked((byte)table[highbyte - 1]));
i++;
}
if (lowbyte != 0x0 && lowbyte != 0xF)
{
receiveData.WriteByte(unchecked((byte)table[lowbyte - 1]));
}
}
}
}
byte[] tmp = Encoding.Convert(encoding, Encoding.UTF8, receiveData.ToArray());
return Encoding.UTF8.GetString(tmp);
}
private static string DecryptUnauthed(in ReadOnlySpan str)
{
try
{
var encryptedStringBuilder = new StringBuilder(str.Length);
for (int i = 1; i < str.Length; i++)
{
if (Convert.ToChar(str[i]) == 0xE)
{
return encryptedStringBuilder.ToString();
}
int firstbyte = Convert.ToInt32(str[i] - 0xF);
int secondbyte = firstbyte;
secondbyte &= 240;
firstbyte = Convert.ToInt32(firstbyte - secondbyte);
secondbyte >>= 4;
switch (secondbyte)
{
case 0:
case 1:
encryptedStringBuilder.Append(' ');
break;
case 2:
encryptedStringBuilder.Append('-');
break;
case 3:
encryptedStringBuilder.Append('.');
break;
default:
secondbyte += 0x2C;
encryptedStringBuilder.Append(Convert.ToChar(secondbyte));
break;
}
switch (firstbyte)
{
case 0:
case 1:
encryptedStringBuilder.Append(' ');
break;
case 2:
encryptedStringBuilder.Append('-');
break;
case 3:
encryptedStringBuilder.Append('.');
break;
default:
firstbyte += 0x2C;
encryptedStringBuilder.Append(Convert.ToChar(firstbyte));
break;
}
}
return encryptedStringBuilder.ToString();
}
catch (OverflowException)
{
return string.Empty;
}
}
}