テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】暗号化したAssetBundleはLoadFromStreamでロードすればメモリに優しい

暗号化したAssetBundleを、「メモリに一旦全展開してからの復号」ではなく、メモリ負荷的に優しい「Streamで復号しながら読む」アプローチについてです。

AssetBundleを暗号化でAssetBundle.LoadFromMemoryを使うとメモリに優しくない

DeNA TechCon 2019のUnity 2018-2019を見据えたDeNAのUnity開発のこれからにて、AssetBundleを暗号化した場合でもStreamに乗せれば比較的低い負荷でロード出来る事が紹介されました。

f:id:tsubaki_t1:20190316143007p:plain

以前、暗号化されたAssetBundleは一旦データをメモリ上で復号してからの AssetBundle.LoadFromMemory(...) という形で復号する事が多かったのですが、これはヒープにメモリを全て乗せるという都合上、幾つかデメリットがありました。

  • AssetBundleを展開しっぱなしに出来ない
  • 全てのデータをロードしなければ使用できない
  • 復号前とAssetBundleの両方のメモリ負荷が計上されてあまりよろしくない
  • ヒープが伸びる

今回はソレを回避するアプローチです。

AssetBundle.LoadStreamで少しずつ復号する

AssetBundle.LoadFromStream( Stream )は、.NETのStreamを通してAssetBundleをロードするAPIです。

上手く使えば割と色々な事が出来る反面、利用には幾つかの条件があり中々に手軽に使うには面倒くさいという性質もありました。特にstream.CanSeekがTrueを返す暗号化Streamは無かった気がするので、ソコの所は自作する必要があります。

tsubakit1.hateblo.jp

暗号化に対応したStream

自作が面倒だったので探した所、ありました。 念の為コードは写しておきますが、オリジナルはコチラから確認出来ます。

https://gist.github.com/tsubaki/4c15f08f592fc303cce97f13c686243f

使用する場合は、下のようなコードになります。Seekが可能なので、AssetBundle.LoadFromStreamでも使用できます。

void test()
{
        // bufの中身を1 ~ 255の数値で埋める
        var buf = new byte[255];
        for (byte i = 0; i < buf.Length; i++)
            buf[i] = i;

        //暗号化したデータをbaseStream に書き込む
        var uniqueSalt = new byte[16]; //** WARNING **: MUST be unique for each stream otherwise there is NO security
        var baseStream = new MemoryStream();
        var cryptor = new SeekableAesStream(baseStream, "password", uniqueSalt);
        cryptor.Write(buf, 0, buf.Length);

        //200までシークして、200から50まで復号
        // 200 ~ 250の数値がdecryptedBufferに格納される
        cryptor.Position = 200;
        var decryptedBuffer = new byte[50];
        cryptor.Read(decryptedBuffer, 0, 50);
}

内容は多分、一定のメモリをブロックにして暗号化というものだと思われます。

あとはコレを使ってAssetBundleを暗号化し、ロードします。

AssetBundleの構築

AssetBundleを構築は下のようなコードにしました。あまり汎用的ではないですが、サンプルです。お察しください。 今回は普通にAssetBundle Nameで設定しています。

f:id:tsubaki_t1:20190316161611j:plain

内容は何も考えずAssetBundleを暗号化して「e + assetbundle名」という名前で保存します。

f:id:tsubaki_t1:20190316161808j:plain

gist.github.com

なお、AssetBundleの圧縮フォーマットは必ず「LZ4(ChunkBasedCompression)」もしくは「無圧縮(UncompressedAssetBundle)」を使用します。何も指定しないとLZMAが使用され、メモリに全展開されます。

tsubakit1.hateblo.jp

暗号化したAssetBundleの利用

AssetBundleを復号してロードします。当然ですが、パスワードとsoltは暗号化した値と同じものを使用する必要があります。

gist.github.com

上でも書きましたが、このアプローチはLoadFromMemoryと比較してメモリに優しいです。試しに合計120MBくらいになる量のTextureを暗号化してAssetBundle化、復号をしてみても、Reserved Monoのメモリは殆ど増えませんでした。

f:id:tsubaki_t1:20190317044011j:plain

感想

今回は暗号化を使いましたが、単純に「簡単にAssetBundleの中身を見られたくない」といったモチベーションならば、もっと簡単なやり方はありそうな気がします。バッファの中身を少しズラすとか、最初のブロックだけ暗号化とか、それだけでカジュアルにアセットをハックされにくくなると思います。この辺りは手間との相談になりそうな

次はAddressableと組み合わせたい所

関連

LoadFromStreamの説明

tsubakit1.hateblo.jp