水無川旅館 2024/07/16 12:00

【Unity】一番簡単な AssetBundle & 暗号化

 こんにちは! 今日は AssetBundle 暗号化について、記事を書いてみたいと思います!

 大前提……今回の記事を参考にする場合、自己責任でお願い致します。

 この記事を参考にして起きたいかなる事態にも責任は負いかねますので、ご了承願います。

一番簡単? Addressables じゃないの?

 AssetBundle より Addressables のほうが新しいですから、多くの場合、 Addressables を使ったほうがよいでしょう。ただ、 Addressables で暗号化をするのは非常に大変なのです。

(参考) AddressableでAssetBundleを暗号化して扱う

 一応、できなくはありません。ただ、 AssetBundleProvider というクラスのコードを丸々コピペして改造しなければならず、大変なのです。

 そう、コピペ、です。継承とかではありません。

「えっ!? 今日は Addressables 使っていいのか!?」
「ああ…どんどん使え」

もぐ…もぐ…もぐ…

「ただ今より暗号化実装を開始する!!」

 暗号化するなら生で AssetBundle を使ったほうが簡単です。

 今回は 一番簡単な ということで、生で AssetBundle を使うことにしました。

なにはともあれ暗号化しよう!

 とりあえず暗号化コードを書きましょう(Unity非依存の純粋な C# のコード)。本質的なコードはこれだけなので、ここまでできたら完成したも同然ですね!!

暗号化

using System.IO;
using System.Security.Cryptography;

public static void Encrypt(Aes algorithm, Stream plainTextStream, Stream cipherTextStream) {
    var encryptor = algorithm.CreateEncryptor(algorithm.Key, algorithm.IV);

    using (var cryptoStream = new CryptoStream(cipherTextStream, encryptor, CryptoStreamMode.Write)) {
        // 非同期にするには CopyToAsync を使えば OK
        plainTextStream.CopyTo(cryptoStream);
    }
}

復号

using System.IO;
using System.Security.Cryptography;

public static void Decrypt(Aes algorithm, Stream cipherTextStream, Stream plainTextStream) {
    var decryptor = algorithm.CreateDecryptor(algorithm.Key, algorithm.IV);

    using (var cryptoStream = new CryptoStream(cipherTextStream, decryptor, CryptoStreamMode.Read)) {
        // 非同期にするには CopyToAsync を使えば OK
        cryptoStream.CopyTo(plainTextStream);
    }
}

AssetBundle をビルド&暗号化する

 まずは AssetBundle に含めるアセットを設定します。 AssetBundle には色々なアセットが含められますが、今回はシーンを丸ごと含める想定です。


↑シーンのインスペクタから AssetBundle の名前を指定します。

 そして Editor フォルダ以下に次のコードを追加します。すると Assets > Build AssetBundles というメニューが表示されますので、クリック。 AssetBundle がビルドされます!

 これで Assets/AssetBundles平文の AssetBundle が保存されます。

private const string plainAssetBundleDirectory = "Assets/AssetBundles";

[MenuItem("Assets/Build AssetBundles")]
static void BuildAllAssetBundles()
{
    if(!Directory.Exists(plainAssetBundleDirectory)) {
        Directory.CreateDirectory(plainAssetBundleDirectory);
    }

    BuildPipeline.BuildAssetBundles(plainAssetBundleDirectory, 
                                    BuildAssetBundleOptions.None, 
                                    BuildTarget.StandaloneWindows);
}

AssetBundle を暗号化する

 次のコードを Editor 以下に追加すると Assets > Encrypt AssetBundles というメニューが現れますので、クリック。これで Assets/StreamingAssets/AssetBundles 以下に暗号化された AssetBundle が保存されます。

 StreamingAssets というのは、 Unity で特別扱いされるフォルダのひとつで、 AssetBundle とかを配置して読み込むのに使います。

// ASCII で 32 桁の文字列にすること!!
private const string KEY = "";
private const string plainAssetBundleDirectory = "Assets/AssetBundles";
private const string cipherAssetBundleDirectory = "Assets/StreamingAssets/AssetBundles";

[MenuItem("Assets/Encrypt AssetBundles")]
static void EncryptAllAssetBundles()
{
    if(!Directory.Exists(plainAssetBundleDirectory)) {
        Directory.CreateDirectory(plainAssetBundleDirectory);
    }

    if(!Directory.Exists(cipherAssetBundleDirectory)) {
        Directory.CreateDirectory(cipherAssetBundleDirectory);
    }

    var key = Encoding.ASCII.GetBytes(KEY);

    foreach (string plainFilePath in Directory.EnumerateFiles(plainAssetBundleDirectory)) {
        var fileName = Path.GetFileName(plainFilePath);
        var cipherFilePath = Path.Combine(cipherAssetBundleDirectory, fileName);
        var ivFilePath = Path.Combine(cipherAssetBundleDirectory, fileName + ".iv");

        using (Aes algorithm = Aes.Create()) {
            algorithm.Key = key;

            using (var ivFileStream = new FileStream(ivFilePath, FileMode.Create, FileAccess.Write)) {
                ivFileStream.Write(algorithm.IV, 0, algorithm.IV.Length);
            }

            using (var plainFileStream = new FileStream(plainFilePath, FileMode.Open, FileAccess.Read)) {
                using (var cipherFileStream = new FileStream(cipherFilePath, FileMode.Create, FileAccess.Write)) {
                    AES.Encrypt(algorithm, plainFileStream, cipherFileStream);
                }
            }
        }
    }
}

AssetBundle を復号する

 最後に AssetBundle を復号するコードを書きます。これで暗号化された AssetBundle を読み込むことができました! おつかれまでした!

using System.IO;
using System.Security.Cryptography;
using System.Text;

using UnityEngine;

[SerializeField] private string KEY = "";
[SerializeField] private string assetBundleName = "";

// Assets/StreamingAssets/AssetBundles のパス
var assetBundlesPath = Path.Combine(Application.streamingAssetsPath, "AssetBundles");

// AES の IV のパス
var assetBundleIvPath = Path.Combine(assetBundlesPath, assetBundleName + ".iv");

// 暗号化された AssetBundle のパス
var assetBundlePath = Path.Combine(assetBundlesPath, assetBundleName);

var key = Encoding.ASCII.GetBytes(KEY);
var iv = File.ReadAllBytes(assetBundleIvPath);

using (var cipherStream = new FileStream(assetBundlePath, FileMode.Open, FileAccess.Read)) {
    using (var plainStream = new MemoryStream()) {
        using (Aes algorithm = Aes.Create()) {
            algorithm.Key = key;
            algorithm.IV = iv;

            // 説明のため、同期
            // 実際には非同期にすると吉
            AES.Decrypt(algorithm, cipherStream, plainStream);
            AssetBundle.LoadFromStream(plainStream);
        }
    }
}

ちょっと待って! このままだと…

 このままですと、大きな問題があります。それは……

暗号化前のアセットもビルドに含まれてしまう

 ことです。

 暗号化するなら、元々のファイルはビルドから取り除かなければ意味がありません。

シーンをビルドから取り除く

 AssetBundle を読み込むだけの Boot シーン(仮称)を作成し、その他のシーンは Build Settings から取り除きます。

Before

After

 Boot シーンでは、 AssetBundle を復号して読み込む他、最初のシーン(Hijack の場合は GameMaster)を読み込みます。ちなみに、 GameMaster はシーン共通のオブジェクトを常駐させたり、他のシーン(Action, Adventure, LosAngeles, DeathValley)などを読み込んで動かすシーンです。

 これで Boot シーンが依存しているアセットだけがビルドに含まれるようになりました。今度こそ、おつかれさまでした!

なお、 Resources や C# のコード等に注意

 ほとんどのアセットは基本的に Build Settings に含まれるシーンから参照されていないとビルドには含まれませんが、 Resources や C# のコード、 Preloaded Assets に設定したアセット等は、シーンの存在に関係なくビルドに含まれてしまいます。そこは注意しましょう。

 きちんと何が含まれていて何が含まれていないかを確認するには、 Build Report Toolを使うと楽です。ちょうどセールになりそうなので、チェックするといいかもしれません!

 このあたりを厳密にやると本当に大変なので、今回の記事はここまでです。ただ、注意点としては気にかけていてくださいね。

無料特典コード

 無料特典で、今回のコードの非同期版&そのまま完璧にコピペで動く完全版もご用意しています! ぜひぜひ、よろしくお願いします~~!!

 なお、著作権は放棄していません。MPL 2.0 というライセンスで提供します。使用する場合は、クレジット表記・ライセンス表記はお願い致します。

 また無保証です(OSSでよくある条件です)。このコードを使うことで起きたいかなる事態にも責任は負いかねますので、ご了承ください。

特典コードの利用規約

 特典コードは Mozilla Public License, version 2.0 (MPL 2.0) で提供されています。 MPL 2.0 の日本語参考訳もあります。

フォロワー以上限定無料

無料プラン限定特典を受け取ることができます

無料

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

記事のタグから探す

月別アーカイブ

限定特典から探す

記事を検索