Medusa-CS/Medusa.Core/Services/CardService.cs
2024-08-15 20:06:56 +02:00

197 lines
6.0 KiB
C#

using Medusa.Core.Utils;
using System.Text;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Medusa.Core.Services
{
public class CardService : ICardService
{
private const string Alphabet = "0123456789ABCDEFGHJKLMNPRSTUWXYZ";
private readonly byte[] RawKey = Encoding.ASCII.GetBytes("?I'llB2c.YouXXXeMeHaYpy!");
private readonly DESEncryption _desEncryption;
private readonly ILogger<CardService> _logger;
public CardService(ILogger<CardService> logger)
{
var encryptionKey = RawKey.Select(x => (byte)(x * 2)).ToArray();
_desEncryption = new DESEncryption(encryptionKey);
_logger = logger;
}
public string ConvertKonamiIdToUid(string konamiId)
{
if(konamiId.Length != 16)
{
_logger.LogError("Invalid Konami ID length must be 16 characters");
return string.Empty;
}
int cardType = konamiId[14] == '1' ? 1 : konamiId[14] == '2' ? 2 : -1;
if(cardType == -1)
{
_logger.LogError("Invalid Konami ID");
return string.Empty;
}
var card = konamiId.Select(k => (byte)Alphabet.IndexOf(k)).ToArray();
if(card[11] % 2 != card[12] % 2 || card[13] != (card[12] ^ 1) || card[15] != CalculateChecksum(card))
{
_logger.LogError("Invalid card data");
return string.Empty;
}
for(int i = 13; i > 0; i--)
{
card[i] ^= card[i - 1];
}
card[0] ^= (byte)cardType;
var packed = Pack5(card.Take(13).ToArray()).Take(8).ToArray();
var decrypted = _desEncryption.Decrypt(packed).Reverse().ToArray();
string cardId = BitConverter.ToString(decrypted).Replace("-", "").ToUpper();
if(cardType == 1 && !cardId.StartsWith("E004") || cardType == 2 && cardId[0] != '0')
{
_logger.LogError("Invalid card `type");
return string.Empty;
}
return cardId;
}
public string ConvertUidToKonamiId(string uid)
{
if(uid.Length != 16)
{
_logger.LogError("Invalid UID length must be 16 characters");
return string.Empty;
}
int cardType = uid.StartsWith("E004") ? 1 : uid[0] == '0' ? 2 : -1;
if(cardType == -1)
{
_logger.LogError("Invalid UID prefix");
return string.Empty;
}
var konamiId = Enumerable.Range(0, uid.Length / 2)
.Select(x => Convert.ToByte(uid.Substring(x * 2, 2), 16))
.ToArray();
konamiId = konamiId.Reverse().ToArray();
if(konamiId.Length != 8)
{
_logger.LogError("Invalid UID length. Must be 8 bytes");
return string.Empty;
}
var encryptedId = _desEncryption.Encrypt(konamiId);
if(encryptedId.Length != 8)
{
_logger.LogError("Invalid ID length. Must be 8 bytes");
return string.Empty;
}
var unpackedId = Unpack5(encryptedId);
unpackedId = unpackedId.Take(13).Concat(new byte[] { 0, 0, 0 }).ToArray();
if(unpackedId.Length != 16)
{
_logger.LogError("Invalid unpacked ID length. Must be 16 bytes");
return string.Empty;
}
unpackedId[0] ^= (byte)cardType;
unpackedId[13] = 1;
for(int i = 1; i < 14; i++)
{
unpackedId[i] ^= unpackedId[i - 1];
}
unpackedId[14] = (byte)cardType;
unpackedId[15] = (byte)CalculateChecksum(unpackedId);
return string.Concat(unpackedId.Select(u => Alphabet[u]));
}
private static byte[] Unpack5(byte[] data)
{
// Convert each byte to an 8-bit binary string and concatenate them
var binaryString = new StringBuilder();
foreach(byte b in data)
{
binaryString.Append(Convert.ToString(b, 2).PadLeft(8, '0'));
}
// Ensure the total length is a multiple of 5 by padding with zeroes if necessary
int remainder = binaryString.Length % 5;
if(remainder != 0)
{
binaryString.Append(new string('0', 5 - remainder));
}
// Convert the 5-bit chunks back into bytes
var result = new List<byte>();
for(int i = 0; i < binaryString.Length; i += 5)
{
string chunk = binaryString.ToString(i, 5);
result.Add(Convert.ToByte(chunk, 2));
}
return [.. result];
}
private static byte[] Pack5(byte[] data)
{
var packed = new StringBuilder();
foreach(byte b in data)
{
packed.Append(Convert.ToString(b, 2).PadLeft(5, '0'));
}
// Padding if not a multiple of 8
if(packed.Length % 8 != 0)
{
packed.Append('0', 8 - packed.Length % 8);
}
return Enumerable.Range(0, packed.Length / 8)
.Select(i => Convert.ToByte(packed.ToString().Substring(i * 8, 8), 2))
.ToArray();
}
private static byte CalculateChecksum(byte[] data)
{
int checksum = 0;
for(int i = 0; i < 15; i++)
{
if(i >= data.Length)
{
break;
}
checksum += data[i] * ((i % 3) + 1);
}
// Reduce the checksum to fit within 5 bits
while(checksum > 31)
{
checksum = (checksum >> 5) + (checksum & 31);
}
// Return the checksum as a byte
return (byte)checksum;
}
}
}