Unity でユーザーデータをJSONにシリアライズして暗号化して保存する
はじめに
- Unity でユーザーのデータを保存したい場合、最も手軽なのは PlayerPrefs だが、List が使えなかったり平文だったりして不便な面もある。
- 今回はユーザーデータを LitJSON でシリアライズして、永続データ領域に保存。保存時に暗号化、読み込み時に復号化、といったことを試してみた。
- 基本的に 【制作実習】Unityでユーザーデータの保存 | ゴゴゴゴ にある通りだが、省略されている部分を補完する。
LitJSON を使って設定データを読み書きする
LitJSON の導入
- http://lbv.github.io/litjson/ から dll をダウンロードして Assets 以下の任意のパスに追加
ラッパークラスの作成
- LitJSON だと float 型が扱えないとか有るそうなのでラッパーの作成
using LitJson; // via http://caitsithware.com/wordpress/archives/140 public class JsonMapper { static JsonMapper() { LitJson.JsonMapper.RegisterExporter<float>( (obj, writer) => { writer.Write( System.Convert.ToDouble( obj ) ); } ); LitJson.JsonMapper.RegisterImporter<double,float>( (input) => { return System.Convert.ToSingle( input ); } ); LitJson.JsonMapper.RegisterImporter<System.Int32,long>( (input) => { return System.Convert.ToInt64( input ); } ); } public static T ToObject<T>( string json ) { return LitJson.JsonMapper.ToObject<T>( json ); } public static string ToJson( object obj ) { return LitJson.JsonMapper.ToJson ( obj ); } }
設定データのクラスを作成
- 内容は任意で
using System.Collections; using System.Collections.Generic; public class GameData { public int level; public List<int> characterIds; }
設定データ読み書き用クラスの作成
using UnityEngine; using System.IO; public class GameDataManager { static string fileName = "GameData"; // via http://www.gogogogo.jp/issue/diary/%E4%B8%80%E4%BA%BA%E5%A4%A7%E5%AD%A6/%E5%88%B6%E4%BD%9C%E5%AE%9F%E7%BF%92/1909/ public static void Save (GameData gameData) { string json = JsonMapper.ToJson(gameData); string filePath = GetFilePath (); FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write); BinaryWriter writer = new BinaryWriter(fileStream); writer.Write(json); writer.Close(); } // via http://www.atmarkit.co.jp/fdotnet/dotnettips/669bincopy/bincopy.html public static GameData Load () { string filePath = GetFilePath (); FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); BinaryReader reader = new BinaryReader(fileStream); GameData gameData = new GameData (); if (reader != null) { string str = reader.ReadString(); gameData = JsonMapper.ToObject<GameData> (str); reader.Close(); } return gameData; } static string GetFilePath () { return Application.persistentDataPath + "/" + fileName; } }
読み書きのテスト
- 下記のスクリプトを任意の GameObject にアタッチして再生して確認する
- Mac だと ~/Library/Caches/"PlayerSettingsのCompanyName"/"Project名"/GameData に有るファイルからも確認できる
sing UnityEngine; using System.Collections; using System.Collections.Generic; public class GameDataTest : MonoBehaviour { // Use this for initialization void Start () { GameData d = new GameData (); d.level = 10000; d.characterIds = new List<int> { 1, 2, 3 }; GameDataManager.Save (d); GameData d2 = GameDataManager.Load (); Debug.Log (d2.level); } }
設定データの暗号化
- 設定データ読み書き用クラスを編集して、保存されるデータを暗号化する
using UnityEngine; using System.IO; using System.Security.Cryptography; public class GameDataManager { static string fileName = "GameData"; public static void Save (GameData gameData) { string json = JsonMapper.ToJson (gameData); json += "[END]"; // 復号化の際にPaddingされたデータを除去するためのデリミタの追記 string crypted = Crypt.Encrypt (json); string filePath = GetFilePath (); FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write); BinaryWriter writer = new BinaryWriter(fileStream); writer.Write(crypted); writer.Close(); } public static GameData Load () { string filePath = GetFilePath (); FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); BinaryReader reader = new BinaryReader(fileStream); GameData gameData = new GameData (); if (reader != null) { string str = reader.ReadString(); string decrypted = Crypt.Decrypt (str); decrypted = System.Text.RegularExpressions.Regex.Replace (decrypted, @"\[END\].*$", ""); gameData = JsonMapper.ToObject<GameData> (decrypted); reader.Close(); } return gameData; } static string GetFilePath () { return Application.persistentDataPath + "/" + fileName; } // via http://yukimemo.hatenadiary.jp/entry/2014/04/15/023802 private class Crypt { private const string AesIV = @"jCddaOybW3zEh0Kl"; private const string AesKey = @"giVJrbHRlWBDIggF"; public static string Encrypt( string text ) { RijndaelManaged aes = new RijndaelManaged (); aes.BlockSize = 128; aes.KeySize = 128; aes.Padding = PaddingMode.Zeros; aes.Mode = CipherMode.CBC; aes.Key = System.Text.Encoding.UTF8.GetBytes (AesKey); aes.IV = System.Text.Encoding.UTF8.GetBytes (AesIV); ICryptoTransform encrypt = aes.CreateEncryptor (); MemoryStream memoryStream = new MemoryStream (); CryptoStream cryptStream = new CryptoStream (memoryStream, encrypt, CryptoStreamMode.Write); byte[] text_bytes = System.Text.Encoding.UTF8.GetBytes (text); cryptStream.Write (text_bytes, 0, text_bytes.Length); cryptStream.FlushFinalBlock (); byte[] encrypted = memoryStream.ToArray (); return (System.Convert.ToBase64String (encrypted)); } public static string Decrypt( string cryptText ) { RijndaelManaged aes = new RijndaelManaged (); aes.BlockSize = 128; aes.KeySize = 128; aes.Padding = PaddingMode.Zeros; aes.Mode = CipherMode.CBC; aes.Key = System.Text.Encoding.UTF8.GetBytes (AesKey); aes.IV = System.Text.Encoding.UTF8.GetBytes (AesIV); ICryptoTransform decryptor = aes.CreateDecryptor (); byte[] encrypted = System.Convert.FromBase64String (cryptText); byte[] planeText = new byte[encrypted.Length]; MemoryStream memoryStream = new MemoryStream (encrypted); CryptoStream cryptStream = new CryptoStream (memoryStream, decryptor, CryptoStreamMode.Read); cryptStream.Read (planeText, 0, planeText.Length); return (System.Text.Encoding.UTF8.GetString (planeText)); } } }
参考サイト
http://www.gogogogo.jp/issue/diary/%E4%B8%80%E4%BA%BA%E5%A4%A7%E5%AD%A6/%E5%88%B6%E4%BD%9C%E5%AE%9F%E7%BF%92/1909/
http://caitsithware.com/wordpress/archives/140
http://lbv.github.io/litjson/docs/quickstart.html
http://yukimemo.hatenadiary.jp/entry/2014/04/15/023802
http://qiita.com/snaka/items/a99747daadfdb0d9ea0b
http://dobon.net/vb/dotnet/string/trim.html