diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..b7ca571 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: aacdff2ab98660843ab3756237c4cf4e +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Wallet/CurrencyAcoount.cs b/Runtime/Wallet/CurrencyAccount.cs similarity index 72% rename from Runtime/Wallet/CurrencyAcoount.cs rename to Runtime/Wallet/CurrencyAccount.cs index 493ae8f..5eb9303 100644 --- a/Runtime/Wallet/CurrencyAcoount.cs +++ b/Runtime/Wallet/CurrencyAccount.cs @@ -4,19 +4,23 @@ namespace CurrencySystem { [Serializable] - public class CurrencyAccount : ICurrency + public sealed class CurrencyAccount : ICurrency { private readonly string _currencyCode; private float _multiplier = 1; - private double _amount; + private IVault _vault; [field: NonSerialized] public event ICurrency.CurrencyEvent OnCurrencyChange; private double Amount { - get { return _amount; } // TODO decrypt - set { _amount = value; } // TODO encrypt + get { return _vault.Amount; } + set + { + _vault.Amount = value; + NotifyEvent(); + } } public string CurrencyCode => _currencyCode; @@ -27,8 +31,8 @@ private double Amount public CurrencyAccount(string currencyCode, double amount, float multiplier = 1) { _currencyCode = currencyCode ?? throw new ArgumentNullException(nameof(currencyCode)); - _amount = amount; _multiplier = multiplier; + _vault = new Vault(amount, currencyCode); } private void NotifyEvent() @@ -40,13 +44,11 @@ private void NotifyEvent() public void Add(double amount) { Amount += amount * _multiplier; - NotifyEvent(); } public void Clear() { Amount = 0; - NotifyEvent(); } public double Get() @@ -57,7 +59,6 @@ public double Get() public void Subtract(double amount) { Amount -= amount; - NotifyEvent(); } public bool TrySubtract(double amount) @@ -65,49 +66,48 @@ public bool TrySubtract(double amount) if (Amount - amount >= 0) { Amount -= amount; - NotifyEvent(); return true; } return false; } - public static CurrencyAccount operator +(CurrencyAccount vault, double income) + public static CurrencyAccount operator +(CurrencyAccount account, double income) { - vault.Add(income); - return vault; + account.Add(income); + return account; } - public static CurrencyAccount operator +(CurrencyAccount vault, CurrencyAccount vault2) + public static CurrencyAccount operator +(CurrencyAccount account, CurrencyAccount account2) { - vault.Add(vault2.Get()); - return vault; + account.Add(account2.Get()); + return account; } - public static CurrencyAccount operator ++(CurrencyAccount vault) + public static CurrencyAccount operator ++(CurrencyAccount account) { - vault.Add(1); - return vault; + account.Add(1); + return account; } - public static CurrencyAccount operator -(CurrencyAccount vault, double toll) + public static CurrencyAccount operator -(CurrencyAccount account, double toll) { - vault.Subtract(toll); - return vault; + account.Subtract(toll); + return account; } - public static CurrencyAccount operator -(CurrencyAccount vault, CurrencyAccount vault2) + public static CurrencyAccount operator -(CurrencyAccount account, CurrencyAccount account2) { - vault.Subtract(vault2.Get()); - return vault; + account.Subtract(account2.Get()); + return account; } - public static CurrencyAccount operator --(CurrencyAccount vault) + public static CurrencyAccount operator --(CurrencyAccount account) { - vault.Subtract(1); - return vault; + account.Subtract(1); + return account; } - public static implicit operator double(CurrencyAccount vault) => vault.Amount; + public static implicit operator double(CurrencyAccount account) => account.Amount; public override bool Equals(object obj) { diff --git a/Runtime/Wallet/CurrencyAcoount.cs.meta b/Runtime/Wallet/CurrencyAccount.cs.meta similarity index 100% rename from Runtime/Wallet/CurrencyAcoount.cs.meta rename to Runtime/Wallet/CurrencyAccount.cs.meta diff --git a/Runtime/Wallet/IVault.cs b/Runtime/Wallet/IVault.cs new file mode 100644 index 0000000..3798d8e --- /dev/null +++ b/Runtime/Wallet/IVault.cs @@ -0,0 +1,7 @@ +namespace CurrencySystem +{ + public interface IVault + { + double Amount { get; set; } + } +} diff --git a/Runtime/Wallet/IVault.cs.meta b/Runtime/Wallet/IVault.cs.meta new file mode 100644 index 0000000..7304aab --- /dev/null +++ b/Runtime/Wallet/IVault.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7bae097503882b54ea9b1c021d74504d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Wallet/Vault.cs b/Runtime/Wallet/Vault.cs new file mode 100644 index 0000000..705010f --- /dev/null +++ b/Runtime/Wallet/Vault.cs @@ -0,0 +1,113 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Security.Cryptography; +using System.Text; + +namespace CurrencySystem +{ + [Serializable] + public class Vault : IVault + { + private byte[] _amount; + private readonly string _currencyCode; + [NonSerialized] + private byte[] _key; + + public Vault(double amount, string currencyCode) + { + _currencyCode = currencyCode; + InitKeys(); + Amount = amount; + } + + public double Amount + { + get + { + return Decrypt(_amount, _key); + } + set + { + _amount = Encrypt(value, _key); + } + } + + internal void InitKeys() + { + _key = GenerateValidKeys(_currencyCode); + } + + private byte[] GenerateValidKeys(string seed) + { + byte[] result = new byte[0]; + do + { + result = result.Concat(Encoding.ASCII.GetBytes(seed)).ToArray(); + } while (result.Length < 16); + Array.Resize(ref result, 16); + return result; + } + + private double Decrypt(byte[] encryptedData, byte[] key) + { + byte[] bytesIv = new byte[16]; + Array.Copy(encryptedData, 0, bytesIv, 0, 16); + byte[] data = new byte[encryptedData.Length - 16]; + Array.Copy(encryptedData, 16, data, 0, data.Length); + + double result; + + using (Aes aes = Aes.Create()) + { + aes.Key = key; + aes.IV = bytesIv; + + ICryptoTransform crypt = aes.CreateDecryptor(aes.Key, aes.IV); + using (MemoryStream ms = new MemoryStream(data)) + { + using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Read)) + { + using (BinaryReader sr = new BinaryReader(cs)) + { + result = sr.ReadDouble(); + } + } + } + } + return result; + } + + private byte[] Encrypt(double data, byte[] key) + { + byte[] encrypted; + using (Aes aes = Aes.Create()) + { + aes.Key = key; + + ICryptoTransform crypt = aes.CreateEncryptor(aes.Key, aes.IV); + using (MemoryStream ms = new MemoryStream()) + { + using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Write)) + { + using (BinaryWriter sw = new BinaryWriter(cs)) + { + sw.Write(data); + //sw.Write(BitConverter.GetBytes(data)); + } + } + encrypted = ms.ToArray(); + } + encrypted = aes.IV.Concat(encrypted).ToArray(); + } + return encrypted; + } + + [OnDeserialized] + private void OnDeserialized(StreamingContext context) + { + InitKeys(); + } + } +} diff --git a/Runtime/Wallet/Vault.cs.meta b/Runtime/Wallet/Vault.cs.meta new file mode 100644 index 0000000..4c313cc --- /dev/null +++ b/Runtime/Wallet/Vault.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 30e55465b7c3b5b40b76936e4e4eb6e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/Basic Sample/Example.unity b/Samples~/Basic Sample/Example.unity index 75e715c..287ee78 100644 --- a/Samples~/Basic Sample/Example.unity +++ b/Samples~/Basic Sample/Example.unity @@ -563,6 +563,139 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 594306795} m_CullTransparentMesh: 1 +--- !u!1 &672965994 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 672965995} + - component: {fileID: 672965998} + - component: {fileID: 672965997} + - component: {fileID: 672965996} + m_Layer: 5 + m_Name: Benchmark + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &672965995 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 672965994} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1090784857} + m_Father: {fileID: 1701787140} + m_RootOrder: 11 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 590, y: 360.87} + m_SizeDelta: {x: 341.77, y: 164.16} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &672965996 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 672965994} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 672965997} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1701787141} + m_TargetAssemblyTypeName: UIMediator, Assembly-CSharp + m_MethodName: Benchmark + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!114 &672965997 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 672965994} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.66215736, g: 0.9056604, b: 0.8984336, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &672965998 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 672965994} + m_CullTransparentMesh: 1 --- !u!1 &739068745 GameObject: m_ObjectHideFlags: 0 @@ -1410,6 +1543,87 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1045606375} m_CullTransparentMesh: 1 +--- !u!1 &1090784856 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1090784857} + - component: {fileID: 1090784859} + - component: {fileID: 1090784858} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1090784857 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1090784856} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 672965995} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1090784858 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1090784856} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 60 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 6 + m_MaxSize: 60 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Benchmark + + 1000 oper.' +--- !u!222 &1090784859 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1090784856} + m_CullTransparentMesh: 1 --- !u!1 &1234861685 GameObject: m_ObjectHideFlags: 0 @@ -1964,6 +2178,7 @@ RectTransform: - {fileID: 739068746} - {fileID: 170402599} - {fileID: 1259944277} + - {fileID: 672965995} m_Father: {fileID: 0} m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Samples~/Basic Sample/UIMediator.cs b/Samples~/Basic Sample/UIMediator.cs index e551a16..4636ca1 100644 --- a/Samples~/Basic Sample/UIMediator.cs +++ b/Samples~/Basic Sample/UIMediator.cs @@ -12,15 +12,15 @@ public void AssignWallet(Wallet wallet) { _wallet = wallet; - if (_wallet.GetAccount("RUB") is null) + if (_wallet.GetAccount("Coins") is null) { Debug.Log("Creating new currency"); - _wallet.AddAccount(new CurrencyAccount("RUB", 1000)); + _wallet.AddAccount(new CurrencyAccount("Coins", 1000)); _wallet.Save(); } - _wallet.GetAccount("RUB").OnCurrencyChange += UpdateUI; - UpdateUI(_wallet.GetAccount("RUB").Get()); + _wallet.GetAccount("Coins").OnCurrencyChange += UpdateUI; + UpdateUI(_wallet.GetAccount("Coins").Get()); } private void UpdateUI(double text) @@ -30,23 +30,23 @@ private void UpdateUI(double text) public void Add9() { - _wallet.GetAccount("RUB").Add(9); + _wallet.GetAccount("Coins").Add(9); } public void Subtract4() { - _wallet.GetAccount("RUB").Subtract(4); + _wallet.GetAccount("Coins").Subtract(4); } public void Double() { - var account = _wallet.GetAccount("RUB"); + var account = _wallet.GetAccount("Coins"); account = account + account; } public void Clear() { - _wallet.GetAccount("RUB").Clear(); + _wallet.GetAccount("Coins").Clear(); } public void SwitchToPlayerPrefs() @@ -75,4 +75,19 @@ public void DeleteSave() PlayerPrefs.DeleteKey(PlayerPrefsSaver.PLAYER_PREFS_WALLET_KEY); File.Delete(Path.Combine(Application.persistentDataPath, FileSaver.FILE_NAME)); } + + public void Benchmark() + { + _wallet.GetAccount("Coins").OnCurrencyChange -= UpdateUI; + var sw = System.Diagnostics.Stopwatch.StartNew(); + var account = _wallet.GetAccount("Coins"); + for (int i = 0; i < 1000; i++) + { + account += 1; + } + sw.Stop(); + Debug.Log($"Elapsed={sw.Elapsed}"); + _wallet.GetAccount("Coins").OnCurrencyChange += UpdateUI; + UpdateUI(_wallet.GetAccount("Coins").Get()); + } } diff --git a/package.json b/package.json index 5493731..d8ee7fd 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,21 @@ { "name": "com.whitebrim.currencies", - "version": "1.0.0", + "version": "1.1.0", "displayName": "CurrencySystem", "description": "Encrypted Currency System for your ingame currencies.", "license": "MIT-0", "keywords": [ "currency", "encrypted", - "coins", - "money" + "coins", + "money" ], "samples": [ - { - "displayName": "Basic sample", - "description": "Basic sample", - "path": "Samples~/Basic Sample" - } + { + "displayName": "Basic sample", + "description": "Basic sample", + "path": "Samples~/Basic Sample" + } ], "author": { "name": "Whitebrim",