SCHALE.GameServer/SCHALE.Common/Crypto/TableEncryptionService.cs

208 lines
7.3 KiB
C#

using SCHALE.Common.Crypto.XXHash;
using System.Buffers.Binary;
using System.Collections;
using System.Reflection;
using System.Text;
namespace SCHALE.Common.Crypto
{
public static class TableEncryptionService
{
private static readonly Dictionary<Type, MethodInfo?> converterCache = [];
private static readonly Dictionary<string, byte[]> keyCache = [];
public static byte[] CreateKey(string name)
{
if (keyCache.TryGetValue(name, out var key))
return key;
byte[] password = GC.AllocateUninitializedArray<byte>(8);
using var xxhash = XXHash32.Create();
xxhash.ComputeHash(Encoding.UTF8.GetBytes(name));
var mt = new MersenneTwister((int)xxhash.HashUInt32);
int i = 0;
while (i < password.Length)
{
Array.Copy(BitConverter.GetBytes(mt.Next()), 0, password, i, Math.Min(4, password.Length - i));
i += 4;
}
keyCache.Add(name, password);
return password;
}
/// <summary>
/// Used for decrypting .bytes flatbuffers bin. Doesn't work yet
/// </summary>
/// <param name="name"></param>
/// <param name="bytes"></param>
public static void XOR(string name, byte[] bytes)
{
using var xxhash = XXHash32.Create();
xxhash.ComputeHash(Encoding.UTF8.GetBytes(name));
var mt = new MersenneTwister((int)xxhash.HashUInt32);
var key = mt.NextBytes(bytes.Length);
Crypto.XOR.Crypt(bytes, key);
}
public static MethodInfo? GetConvertMethod(Type type)
{
if (!converterCache.TryGetValue(type, out MethodInfo? convertMethod))
{
convertMethod = typeof(TableEncryptionService).GetMethods(BindingFlags.Static | BindingFlags.Public)
.FirstOrDefault(x => x.Name == nameof(Convert) && (x.ReturnType == type));
converterCache.Add(type, convertMethod);
}
return convertMethod;
}
public static List<T> Convert<T>(List<T> value, byte[] key) where T : class
{
var baseType = value.GetType().GenericTypeArguments[0];
var convertMethod = GetConvertMethod(baseType.IsEnum ? Enum.GetUnderlyingType(value.GetType()) : baseType);
if (convertMethod is null)
return value;
for (int i = 0; i < value.Count; i++)
{
value[i] = (T)convertMethod.Invoke(null, [value[i], key])!;
}
return value;
}
public static T Convert<T>(T value, byte[] key) where T : Enum
{
var convertMethod = GetConvertMethod(Enum.GetUnderlyingType(value.GetType()));
if (convertMethod is null)
return value;
return (T)convertMethod.Invoke(null, [value, key])!;
}
public static bool Convert(bool value, byte[] key)
{
return value;
}
public static int Convert(int value, byte[] key)
{
var bytes = GC.AllocateUninitializedArray<byte>(sizeof(int));
BinaryPrimitives.WriteInt32LittleEndian(bytes, value);
Crypto.XOR.Crypt(bytes, key);
return BinaryPrimitives.ReadInt32LittleEndian(bytes);
}
public static long Convert(long value, byte[] key)
{
var bytes = GC.AllocateUninitializedArray<byte>(sizeof(long));
BinaryPrimitives.WriteInt64LittleEndian(bytes, value);
Crypto.XOR.Crypt(bytes, key);
return BinaryPrimitives.ReadInt64LittleEndian(bytes);
}
public static uint Convert(uint value, byte[] key)
{
var bytes = GC.AllocateUninitializedArray<byte>(sizeof(uint));
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
Crypto.XOR.Crypt(bytes, key);
return BinaryPrimitives.ReadUInt32LittleEndian(bytes);
}
public static ulong Convert(ulong value, byte[] key)
{
var bytes = GC.AllocateUninitializedArray<byte>(sizeof(ulong));
BinaryPrimitives.WriteUInt64LittleEndian(bytes, value);
Crypto.XOR.Crypt(bytes, key);
return BinaryPrimitives.ReadUInt64LittleEndian(bytes);
}
public static float Convert(float value, byte[] key)
{
var bytes = GC.AllocateUninitializedArray<byte>(sizeof(float));
BinaryPrimitives.WriteSingleLittleEndian(bytes, value);
Crypto.XOR.Crypt(bytes, key);
return BinaryPrimitives.ReadSingleLittleEndian(bytes);
}
public static double Convert(double value, byte[] key)
{
var bytes = GC.AllocateUninitializedArray<byte>(sizeof(double));
BinaryPrimitives.WriteDoubleLittleEndian(bytes, value);
Crypto.XOR.Crypt(bytes, key);
return BinaryPrimitives.ReadDoubleLittleEndian(bytes);
}
public static string Convert(string value, byte[] key)
{
var strBytes = System.Convert.FromBase64String(value);
Crypto.XOR.Crypt(strBytes, key);
return Encoding.Unicode.GetString(strBytes);
}
}
public static class FlatBuffersExtension
{
private static readonly Dictionary<Type, PropertyInfo[]> propertiesCache = [];
private static readonly Dictionary<Type, byte[]> keyCache = [];
/// <summary>
/// <b>Only</b> to be invoked by ExcelT, and only be invoked <b>once</b> per object.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="table"></param>
public static void Decode<T>(this T table) where T : class
{
var type = table.GetType();
PropertyInfo[] props;
if (!propertiesCache.TryGetValue(type, out props!))
{
props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
propertiesCache.Add(type, props);
}
foreach (var prop in props)
{
byte[] key;
if (!keyCache.TryGetValue(type, out key!))
{
key = TableEncryptionService.CreateKey(type.Name.Replace("ExcelT", ""));
keyCache.Add(type, key);
}
var baseType = prop.PropertyType.Name == "List`1" ? prop.PropertyType.GenericTypeArguments[0] : prop.PropertyType;
var convertMethod = TableEncryptionService.GetConvertMethod(baseType.IsEnum ? Enum.GetUnderlyingType(baseType) : baseType);
if (convertMethod is not null)
{
if (prop.PropertyType.Name == "List`1")
{
var list = (IList)prop.GetValue(table, null)!;
for (int i = 0; i < list.Count; i++)
{
list[i] = convertMethod.Invoke(null, [list[i], key]);
}
}
else
{
prop.SetValue(table, convertMethod.Invoke(null, [prop.GetValue(table, null), key]));
}
}
}
}
}
}