//
// ClientWorldCryptography.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;
namespace NosSmooth.Cryptography;
///
/// A cryptography used on world server, has to have a session id (encryption key) set from the client.
///
public class ClientWorldCryptography : ICryptography
{
private static readonly char[] Keys = { ' ', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'n' };
///
/// Gets or sets the encryption key.
///
public int EncryptionKey { get; set; }
///
/// Initializes a new instance of the class.
///
/// Encryption key received by LoginServer.
public ClientWorldCryptography(int encryptionKey = 0)
{
EncryptionKey = encryptionKey;
}
///
public string Decrypt(in ReadOnlySpan bytes, Encoding encoding)
{
try
{
int index = 0;
var currentPacket = new List();
while (index < bytes.Length)
{
byte currentByte = bytes[index++];
if (currentByte == 0xFF)
{
currentPacket.Add((byte)'\n');
continue;
}
int length = currentByte & 0x7F;
if ((currentByte & 0x80) != 0)
{
while (length != 0)
{
if (index < bytes.Length)
{
currentByte = bytes[index++];
int firstIndex = (currentByte & 0xF0) >> 4;
char first = '?';
if (firstIndex != 0)
{
firstIndex--;
first = firstIndex != 14 ? Keys[firstIndex] : '\u0000';
}
if (first != 0x6E)
{
currentPacket.Add((byte)first);
}
if (length <= 1)
{
break;
}
int secondIndex = currentByte & 0xF;
char second = '?';
if (secondIndex != 0)
{
secondIndex--;
second = secondIndex != 14 ? Keys[secondIndex] : '\u0000';
}
if (second != 0x6E)
{
currentPacket.Add((byte)second);
}
length -= 2;
}
else
{
length--;
}
}
}
else
{
while (length != 0)
{
if (index < bytes.Length)
{
currentPacket.Add((byte)(bytes[index] ^ 0xFF));
index++;
}
else if (index == bytes.Length)
{
currentPacket.Add((byte)'\n');
index++;
}
length--;
}
}
}
return string.Concat(currentPacket.Select(x => (char)x));
// byte[] tmp = Encoding.Convert(encoding, Encoding.UTF8, currentPacket.ToArray());
// return Encoding.UTF8.GetString(tmp);
}
catch
{
return string.Empty;
}
}
///
public byte[] Encrypt(string value, Encoding encoding)
{
try
{
var output = new List();
string mask = new string
(
value.Select
(
c =>
{
sbyte b = (sbyte)c;
if (c == '#' || c == '/' || c == '%')
{
return '0';
}
if ((b -= 0x20) == 0 || (b += unchecked((sbyte)0xF1)) < 0 || (b -= 0xB) < 0 ||
b - unchecked((sbyte)0xC5) == 0)
{
return '1';
}
return '0';
}
).ToArray()
);
int packetLength = value.Length;
int sequenceCounter = 0;
int currentPosition = 0;
while (currentPosition <= packetLength)
{
int lastPosition = currentPosition;
while (currentPosition < packetLength && mask[currentPosition] == '0')
{
currentPosition++;
}
int sequences;
int length;
if (currentPosition != 0)
{
length = currentPosition - lastPosition;
sequences = length / 0x7E;
for (int i = 0; i < length; i++, lastPosition++)
{
if (i == sequenceCounter * 0x7E)
{
if (sequences == 0)
{
output.Add((byte)(length - i));
}
else
{
output.Add(0x7E);
sequences--;
sequenceCounter++;
}
}
output.Add((byte)((byte)value[lastPosition] ^ 0xFF));
}
}
if (currentPosition >= packetLength)
{
break;
}
lastPosition = currentPosition;
while (currentPosition < packetLength && mask[currentPosition] == '1')
{
currentPosition++;
}
if (currentPosition == 0)
{
continue;
}
length = currentPosition - lastPosition;
sequences = length / 0x7E;
for (int i = 0; i < length; i++, lastPosition++)
{
if (i == sequenceCounter * 0x7E)
{
if (sequences == 0)
{
output.Add((byte)((length - i) | 0x80));
}
else
{
output.Add(0x7E | 0x80);
sequences--;
sequenceCounter++;
}
}
byte currentByte = (byte)value[lastPosition];
switch (currentByte)
{
case 0x20:
currentByte = 1;
break;
case 0x2D:
currentByte = 2;
break;
case 0xFF:
currentByte = 0xE;
break;
default:
currentByte -= 0x2C;
break;
}
if (currentByte == 0x00)
{
continue;
}
if (i % 2 == 0)
{
output.Add((byte)(currentByte << 4));
}
else
{
output[output.Count - 1] = (byte)(output.Last() | currentByte);
}
}
}
output.Add(0xFF);
sbyte sessionNumber = (sbyte)((EncryptionKey >> 6) & 0xFF & 0x80000003);
if (sessionNumber < 0)
{
sessionNumber = (sbyte)(((sessionNumber - 1) | 0xFFFFFFFC) + 1);
}
byte sessionKey = (byte)(EncryptionKey & 0xFF);
if (EncryptionKey == 0)
{
sessionNumber = -1;
}
switch (sessionNumber)
{
case 0:
for (int i = 0; i < output.Count; i++)
{
output[i] = (byte)(output[i] + sessionKey + 0x40);
}
break;
case 1:
for (int i = 0; i < output.Count; i++)
{
output[i] = (byte)(output[i] - (sessionKey + 0x40));
}
break;
case 2:
for (int i = 0; i < output.Count; i++)
{
output[i] = (byte)((output[i] ^ 0xC3) + sessionKey + 0x40);
}
break;
case 3:
for (int i = 0; i < output.Count; i++)
{
output[i] = (byte)((output[i] ^ 0xC3) - (sessionKey + 0x40));
}
break;
default:
for (int i = 0; i < output.Count; i++)
{
output[i] = (byte)(output[i] + 0x0F);
}
break;
}
return output.ToArray();
}
catch
{
return Array.Empty();
}
}
}