Защита контента
Пока есть возможность использовать шифрование во время передачи ваших ассетов, пока они не добрались до получателя, у вас всё ещё есть возможность вернуть свой контент обратно. Например, есть инструменты, позволяющие сохранять 3D данные на уровне драйверов, позволяя пользователям извлекать модели и текстуры во время их отправки на обработку вашей видеокартой. С этой целью мы и решили дать возможность вашим пользователям извлекать нужные им ассеты из вашей игры.
Однако, если хотите, вы можете использовать и свой собственный способ шифрования данных для AssetBundle файлов.
Одним из способов добиться этого, является использование TextAsset типа для хранения ваших данных в байтах. Можно зашифровать нужные вам файлы, сохранив их с расширением .bytes, которое Unity будет воспринимать как TextAsset тип. После импортирования в редактор, данные файлы будут включены в ваши AssetBundles как TextAssets и размещены на сервере. Затем на стороне клиента будет скачан нужный AssetBundle, из TextAsset типов файлов которого будут извлечены необходимые клиенту данные. При таком подходе будут шифроваться не сами AssetBundles, а лишь TextAsset файлы, находящиеся в них.
string url = "http://www.mywebsite.com/mygame/assetbundles/assetbundle1.unity3d"; IEnumerator Start () < while (!Caching.ready) yield return null; // Start a download of the encrypted assetbundle WWW www = new WWW.LoadFromCacheOrDownload (url, 1); // Wait for download to complete yield return www; // Load the TextAsset from the AssetBundle TextAsset textAsset = www.assetBundle.Load("EncryptedData", typeof(TextAsset)); // Get the byte data byte[] encryptedData = textAsset.bytes; // Decrypt the AssetBundle data byte[] decryptedData = YourDecryptionMethod(encryptedData); // Use your byte array. The AssetBundle will be cached >
В качестве альтернативы можно полностью зашифровать AssetBundles в хранилище и затем скачать его путём использования WWW класса. До тех пор, пока ваши файлы хранятся на сервере в качестве бинарных данных, они могут иметь любое расширение. Как только нужный AssetBundle будет скачан, вам нужно будет декодировать данные, используя свойство .bytes вашего WWW экземпляра, чтобы получить расшифрованные данные из AssetBundle и воссоздать из памяти AssetBundle, используя AssetBundle.CreateFromMemory.
string url = "http://www.mywebsite.com/mygame/assetbundles/assetbundle1.unity3d"; IEnumerator Start () < // Start a download of the encrypted assetbundle WWW www = new WWW (url); // Wait for download to complete yield return www; // Get the byte data byte[] encryptedData = www.bytes; // Decrypt the AssetBundle data byte[] decryptedData = YourDecryptionMethod(encryptedData); // Create an AssetBundle from the bytes array AssetBundleCreateRequest acr = AssetBundle.CreateFromMemory(decryptedData); yield return acr; AssetBundle bundle = acr.assetBundle; // You can now use your AssetBundle. The AssetBundle is not cached. >
Преимущество второго подхода в сравнении с первым в том, что вы можете использоваться любой метод (кроме AssetBundles.LoadFromCacheOrDownload) для передачи своих байтов и данных в качестве закодированных — например сокеты в плагине. Минусом здесь будет то, что данные не будут кешироваться при использовании автоматического кеширования Unity. Можно добавить любые проигрыватели, кроме WebPlayer-а, который должен быть загружен вручную с вашего жёсткого диска с использованием AssetBundles.CreateFromFile
В качестве третьего способа будет неплохо взять лучшее из двух других, путём хранения самого AssetBundle в качестве TextAsset в других, нормальных AssetBundles. Не закодированный AssetBundle, содержащий в себе закодированный будет кешироваться. Оригинальный AssetBundle может быть затем загружен в память, декодирован и инстанциирован, используяAssetBundle.CreateFromMemory.
string url = "http://www.mywebsite.com/mygame/assetbundles/assetbundle1.unity3d"; IEnumerator Start () < while (!Caching.ready) yield return null; // Start a download of the encrypted assetbundle WWW www = new WWW.LoadFromCacheOrDownload (url, 1); // Wait for download to complete yield return www; // Load the TextAsset from the AssetBundle TextAsset textAsset = www.assetBundle.Load("EncryptedData", typeof(TextAsset)); // Get the byte data byte[] encryptedData = textAsset.bytes; // Decrypt the AssetBundle data byte[] decryptedData = YourDecryptionMethod(encryptedData); // Create an AssetBundle from the bytes array AssetBundleCreateRequest acr = AssetBundle.CreateFromMemory(decryptedData); yield return acr; AssetBundle bundle = acr.assetBundle; // You can now use your AssetBundle. The wrapper AssetBundle is cached >
#14 — Сохранение и загрузка параметров
Благодаря JSON формату мы можем сохранять различные данные от пользователя и загружать их в нашем приложении при повторном входе пользователя. Такой функционал поможет экономить время пользователю.
Видеоурок
Для работы с сохранением, а также с загрузкой пользовательских данных лучше всего использовать формат JSON. Формат позволяет хранить ключи и значения к ним, а также удобно и быстро получать все необходимые данные из файла или же записывать данные в него.
Для работы с этим форматом в Corona используется библиотека json . Эта библиотека имеет ряд функций, которые помогают преобразовать данные из формата JSON (функция decode ), а также наоборот зашифровать данные в этот формат (функция encode ).
Также при сохранении данных необходимо работать с файлами: открывать их, считывать из них информацию, записывать в них что-то и закрывать файлы.
Все это выполняется внутри Corona при помощи специальных встроенных функций для работы с файлами (библиотека «io»).
Проблема с Шифрованием JSON
Есть две сцены (Меню и игра)
В меню есть скрипт, который сохраняет и загружает игру, обращаясь к скрипту сохранения/загрузки а уже этот обращаеться к скрипту, который это кодирует и декдирует.
Цепь такая: Главный скрипт -> Скрипт Save/Load -> Скрипт шифрования
Так вот всё работает прекрасно, НО на второй сцене (Игре), если вызвать функцию сохранения или загрузки — ничего не произойдёт и если перейти обратно из игры в меню (По UI кнопке), то мне напишет в консоль — «Key Not Exist»
Главный скрипт (Меню) (Так-же и в скрипте, который уже в ИГРЕ находится)
1 2 3
[SerializeField] private ALL_SaveAndLoadFromJSON SaveAndLoad; //Ссылка на скрипт Save/Load SaveAndLoad.Save(); //Сохранение SaveAndLoad.Load(); //Загрузка
Скрипт Save/Load
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
public class ALL_SaveAndLoadFromJSON : MonoBehaviour { [SerializeField] private ALL_AesCryptJSON_Logic aesEncrypt; //Шифрование public Saves saves; public class Saves { //Тут много переменных с данными для сохранения //Например public int Money; public int Score; public bool isMusic; public bool isSound; } //Сохранение в JSON public void Save() { string json = JsonUtility.ToJson(saves); byte[] encryptedBytes = this.aesEncrypt.Encrypt(json); EncryptedGameCoreStruct encryptedGameCore = new EncryptedGameCoreStruct() { data = encryptedBytes, }; string encryptedJson = JsonUtility.ToJson(encryptedGameCore); File.WriteAllText(Path.Combine(Application.persistentDataPath + "/Save.json"), encryptedJson); } //Выгрузка сохранений из JSON public void Load() { string encryptedJson = File.ReadAllText(Application.persistentDataPath + "/Save.json"); byte[] encryptedBytes = JsonUtility.FromJsonEncryptedGameCoreStruct>(encryptedJson).data; string json = aesEncrypt.Decrypt(encryptedBytes); saves = JsonUtility.FromJsonSaves>(json); } }
Скрипт Шифрования
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
public class ALL_AesCryptJSON_Logic : MonoBehaviour { public byte[] Encrypt(string str) { byte[] encryptedBytes; using (Aes aes = Aes.Create()) { aes.GenerateKey(); aes.GenerateIV(); ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) { using (StreamWriter sm = new StreamWriter(cs)) { sm.Write(str); } } encryptedBytes = ms.ToArray(); } using (SHA256 sha = SHA256.Create()) { byte[] bytes = sha.ComputeHash(encryptedBytes); string hash = BitConverter.ToString(bytes).Replace(oldValue:"-", newValue:""); AesCryptoParametersStruct c = new AesCryptoParametersStruct { key = aes.Key, iv = aes.IV }; SaveEncryptParameters(c, hash); } } return encryptedBytes; } public string Decrypt(byte[] encryptedBytes) { AesCryptoParametersStruct c = LoadEncryptParameters(encryptedBytes); string decryptedString; using (Aes aes = Aes.Create()) { aes.Key = c.key; aes.IV = c.iv; ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (MemoryStream ms = new MemoryStream(encryptedBytes)) { using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) { using (StreamReader sr = new StreamReader(cs)) { decryptedString = sr.ReadToEnd(); } } } } return decryptedString; } public void SaveEncryptParameters(AesCryptoParametersStruct c, string key) { string json = JsonUtility.ToJson(c); PlayerPrefs.SetString(key, json); } public AesCryptoParametersStruct LoadEncryptParameters(byte[] encryptedBytes) { string key; using (SHA256 sha = SHA256.Create()) { byte[] hash = sha.ComputeHash(encryptedBytes); key = BitConverter.ToString(hash).Replace(oldValue:"-", newValue:""); } if (!PlayerPrefs.HasKey(key)) { throw new PlayerPrefsException(error: "Key Not Exist"); } string json = PlayerPrefs.GetString(key); PlayerPrefs.DeleteKey(key); return JsonUtility.FromJsonAesCryptoParametersStruct>(json); } }
Структура Шифрования
1 2 3 4 5 6 7 8 9 10 11 12 13 14
namespace Structs { [System.Serializable] public struct AesCryptoParametersStruct { public byte[] key; public byte[] iv; } [System.Serializable] public struct EncryptedGameCoreStruct { public byte[] data; } }
Универсальная система сохранений
Здравствуйте товарищи. Уже довольно долго меня мучает вопрос с нормальной системой сохранений. Хочется сделать решение как в TES/Fallout/S.T.A.L.K.E.R., тобишь на сцене должен сохраняться каждый противник, каждая банка и каждая выпущенная пуля. Единственный вариант, который мне приходит в голову — создать интерфейс (условный ISaveable) и вешать его на все объекты подлежащие сохранению/загрузке. Он должен хранить id и писать необходимые поля для каждой сущности. Так же необходимо создать ScriptableObject в котором будут храниться все объекты которые могут сохраняться и их id. Ну и необходимо создать контроллер сохранения/загрузки, который будет иметь ссылку на этот ScriptableObject. Получается при сохранении контроллер будет бегать по сцене, искать объекты с интерфейсом ISaveable, и вызывать у них условный Save(), который будет записывать поля необходимые его сущности и id. При загрузке контроллер будет смотреть id сохраненной сущности, искать такой id в нашем ScriptableObject, и создавать экземпляр этой сущности. Но что-то мне подсказывает что это не самый оптимальный подход, так как данный ScriptableObject должен будет содержать все айтемы, всех юнитов, и вообще все объекты состояние которых мы хотим сохранять, а это сделает его очень жирным. Есть ли у кого мысли, как реализовать данную задачу правильно?
#1
3:17, 13 сен 2021
Dictionary
class EntityData
<
public Pose pose;
public float health;
// .
>
srring — идентификатор экземпляра
В OnEnable создаешь значение в словаре, если оно уже есть, берешь данные и применяешь к объекту, по мере обновления статов пишешь их в контейнер. На чекпойнтах в json его и в префсы/на диск/на сервер, сжатие/шифрование по желанию.
#2
8:55, 13 сен 2021
Raziel1795
> данный ScriptableObject должен будет содержать все айтемы, всех юнитов, и
> вообще все объекты состояние которых мы хотим сохранять
Не всех, а только тех, что необходимо будет спавнить налету. При загрузке можно грузить всю ту же сцену сначала, потом читать сейв, находить на сцене объект с этим id, и далее: либо применять его состояние, либо удалять объект, либо спавнить новый.
Без ScriptableObject можно, наверное, если контент будет в ассет бандлах, тогда спавнить по имени.
