// System
using System;
// Unity
using UnityEngine;
// GUPS - AntiCheat - Core
using GUPS.AntiCheat.Core.Protected;
// GUPS - AntiCheat
using GUPS.AntiCheat.Detector;
using GUPS.AntiCheat.Settings;
namespace GUPS.AntiCheat.Protected
{
///
/// Represents a protected string that provides an additional layer of security for sensitive values.
/// In most cases, it is recommended to replace the default string type with the protected one, considering the overhead introduced by complex encryption and encoding.
///
[Serializable]
public struct ProtectedString : IProtected, IDisposable, ISerializationCallbackReceiver
{
///
/// A struct does not have a default constructor that is called when the structure is created. Therefore, the protected primitive must return
/// a default value if it does not have an assigned value.
///
private bool isInitialized;
///
/// Encrypts the specified string with the given secret and encodes it to UTF-8.
///
/// The string to be protected.
/// The secret key for protection.
/// The encrypted and UTF-8 encoded representation of the input string.
private static string EncryptToUTF8(string _String, int _Secret)
{
if (_String == null)
{
return null;
}
if (_String.Length == 0)
{
return "";
}
uint key1 = 0x45435345 + (uint)_Secret;
uint key2 = 0x95656543;
byte[] buff1 = System.Text.UTF8Encoding.UTF8.GetBytes(_String);
byte[] buff = new byte[buff1.Length + 1];
buff[0] = (byte)(GlobalSettings.RandomProvider.RandomInt32(1, Int32.MaxValue) % 256);
byte d = buff[0];
for (int i = 1; i < buff.Length; i++)
{
buff[i] = buff1[i - 1];
key1 = (key1 * 4343255 + d + 5235457) % 0xFFFFFFFE;
key2 = (key2 * 5354354 + d + 22646641) % 0xFFFFFFFE;
d = buff[i];
buff[i] = (byte)((uint)buff[i] ^ key1);
buff[i] = (byte)((byte)buff[i] + (byte)key2);
}
return System.Convert.ToBase64String(buff);
}
///
/// Decrypts the protected string with the given secret and decodes it from UTF-8.
///
/// The string to be unprotected.
/// The secret key for protection.
/// The decrypted and UTF-8 decoded representation of the input string.
private static string DecryptFromUTF8(string _String, int _Secret)
{
if (_String == null)
{
return null;
}
if (_String.Length == 0)
{
return "";
}
uint key1 = 0x45435345 + (uint)_Secret;
uint key2 = 0x95656543;
byte[] buff1 = System.Convert.FromBase64String(_String);
byte[] buff = new byte[buff1.Length - 1];
byte d = buff1[0];
for (int i = 0; i < buff.Length; i++)
{
buff[i] = buff1[i + 1];
key1 = (key1 * 4343255 + d + 5235457) % 0xFFFFFFFE;
key2 = (key2 * 5354354 + d + 22646641) % 0xFFFFFFFE;
buff[i] = (byte)((byte)buff[i] - (byte)key2);
buff[i] = (byte)((uint)buff[i] ^ key1);
d = buff[i];
}
return System.Text.UTF8Encoding.UTF8.GetString(buff, 0, buff.Length);
}
///
/// Gets a value indicating whether the protected value has integrity, i.e., whether it has maintained its original state.
///
private bool hasIntegrity;
///
/// Gets a value indicating whether the protected value has integrity, i.e., whether it has maintained its original state.
///
public bool HasIntegrity { get => hasIntegrity || !isInitialized; private set => hasIntegrity = value; }
///
/// The obfuscated value of the protected.
///
private string obfuscatedValue;
///
/// A secret key used to obfuscate the true value.
///
private Int32 secret;
///
/// A honeypot pretending to be the original value. If a user attempts to change this value via a cheat/hack engine, you will get notified.
/// The protected value will keep its true value.
///
[SerializeField]
private string fakeValue;
///
/// Unity serialization hook. Ensures the correct values are serialized.
///
public void OnBeforeSerialize()
{
this.fakeValue = Value;
}
///
/// Unity deserialization hook. Ensures the correct values are deserialized.
///
public void OnAfterDeserialize()
{
this = this.fakeValue;
}
///
/// Creates a new protected string with the specified value.
///
/// The initial value of the protected string.
public ProtectedString(string _Value = null)
{
// Initialization
this.isInitialized = true;
this.secret = GlobalSettings.RandomProvider.RandomInt32(1, +5432);
this.obfuscatedValue = EncryptToUTF8(_Value, this.secret);
//
this.hasIntegrity = true;
//
this.fakeValue = _Value;
}
///
/// Gets and sets the true unencrypted field value.
///
public string Value
{
get
{
if (!this.isInitialized)
{
return null;
}
if (!this.CheckIntegrity())
{
AntiCheatMonitor.Instance.GetDetector()?.OnNext(this);
}
return this.UnObfuscate();
}
set { this.Obfuscate(value); }
}
///
/// Gets the true unencrypted field value.
///
object IProtected.Value => this.Value;
///
/// Obfuscates the specified value, encrypting it with the secret key.
///
/// The value to be obfuscated.
private void Obfuscate(string _Value)
{
// Obfuscate the value.
this.obfuscatedValue = EncryptToUTF8(_Value, this.secret);
// Assign the fake value.
this.fakeValue = _Value;
}
///
/// Unobfuscates the secured value and returns the true unencrypted value.
///
/// The true unencrypted value.
private string UnObfuscate()
{
// Get the unobfuscated value.
return DecryptFromUTF8(this.obfuscatedValue, this.secret);
}
///
/// Obfuscates the current value, generating a new random secret key.
///
public void Obfuscate()
{
// Unobfuscate the secured value.
string var_UnobfuscatedValue = this.UnObfuscate();
// Create a new random secret.
this.secret = GlobalSettings.RandomProvider.RandomInt32(1, +5432);
// Obfuscate the value.
this.Obfuscate(var_UnobfuscatedValue);
}
///
/// Checks the integrity of the protected value, detecting if an attacker changed the honeypot fake value.
///
/// True if the protected value has integrity; otherwise, false.
public bool CheckIntegrity()
{
// Unobfuscate the secured value.
string var_UnobfuscatedValue = this.UnObfuscate();
// Check if an attacker changed the honeypot fake value.
if (this.fakeValue != var_UnobfuscatedValue)
{
this.HasIntegrity = false;
}
// Return the integrity status.
return this.HasIntegrity;
}
///
/// Releases the resources used by the protected string.
///
public void Dispose()
{
this.secret = 0;
this.obfuscatedValue = null;
}
///
/// Returns a string representation of the protected string.
///
/// A string representation of the protected string.
public override string ToString()
{
return this.Value;
}
///
/// Gets the hash code for the protected value.
///
/// The hash code for the protected value.
public override int GetHashCode()
{
if (this.Value == null)
{
return 0;
}
return this.Value.GetHashCode();
}
#region Serialization
///
/// Used to serialize the protected to the player prefs.
///
/// The obfuscated value of the protected.
/// The secret key used to obfuscate the true value.
internal void Serialize(out String _ObfuscatedValue, out int _Secret)
{
_ObfuscatedValue = this.obfuscatedValue;
_Secret = this.secret;
}
///
/// Used to deserialize the protected from the player prefs.
///
/// The obfuscated value of the protected.
/// The secret key used to obfuscate the true value.
internal void Deserialize(String _ObfuscatedValue, int _Secret)
{
this.obfuscatedValue = _ObfuscatedValue;
this.secret = _Secret;
this.fakeValue = this.UnObfuscate();
}
#endregion
#region Implicit operator
///
/// Implicitly converts a regular string to a protected string.
///
/// The regular string to be converted.
/// A new instance of the protected string.
public static implicit operator ProtectedString(string _Value)
{
return new ProtectedString(_Value);
}
///
/// Implicitly converts a protected string to a regular string.
///
/// The protected string to be converted.
/// The true unencrypted value of the protected string.
public static implicit operator string(ProtectedString _Value)
{
return _Value.Value;
}
#endregion
#region Calculation operator
///
/// Concatenates two protected strings.
///
/// The first protected string.
/// The second protected string.
/// A new protected string representing the concatenation of the input strings.
public static ProtectedString operator +(ProtectedString v1, ProtectedString v2)
{
return new ProtectedString(v1.Value + v2.Value);
}
#endregion
#region Equality operator
///
/// Checks if two protected strings are equal.
///
/// The first protected string.
/// The second protected string.
/// True if the values of the protected strings are equal; otherwise, false.
public static bool operator ==(ProtectedString v1, ProtectedString v2)
{
return v1.Value == v2.Value;
}
///
/// Checks if two protected strings are not equal.
///
/// The first protected string.
/// The second protected string.
/// True if the values of the protected strings are not equal; otherwise, false.
public static bool operator !=(ProtectedString v1, ProtectedString v2)
{
return v1.Value != v2.Value;
}
///
/// Checks if the protected string is equal to the specified object.
///
/// The object to compare with the protected string.
/// True if the values are equal; otherwise, false.
public override bool Equals(object obj)
{
if (obj is ProtectedString)
{
return this.Value == ((ProtectedString)obj).Value;
}
if (this.Value == null && obj == null)
{
return true;
}
return this.Value.Equals(obj);
}
#endregion
}
}