// 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 } }