【Cocos2d-x】Luaスクリプトを簡単に暗号化

#はじめに
Cocos2d-xで開発する時に、言語は何を使っているでしょうか？
C++が多いような気がしますが、手っ取り早く開発するには、Lua、JavaScriptも捨てがたいかと思います。
最近では[Cocos Code IDE](http://www.cocos2d-x.org/products/codeide/)を使えば開発もし易いですね。

ただ、スクリプト言語で開発する場合、そのままだと生のスクリプトファイルが
アプリのパッケージに含まれてしまうため、コードの解析が容易になってしまいます。
これは企業で開発している場合は致命的ですし、個人開発でも避けたい問題です。
この問題を解決するには、スクリプトファイルを暗号化するのが良さそうです。

Cocos2d-xのcocosコマンドには、スクリプトファイルのコンパイル・暗号化のコマンドが用意されており、
コマンド一発で暗号化できます。


#luacompile コマンド
Luaの場合、`luacompile`というコマンドを使います。
[リファレンスはここ](http://www.cocos2d-x.org/wiki/Cocos_luacompile)です。

このコマンドは、Luaスクリプトをコンパイルし、バイトコードに変換してくれるコマンドで、
オプションを指定することで暗号化までやってくれます。

#Luaスクリプトのコンパイル
まずは暗号化はせず、コンパイルだけで実行してみます。

```bash
mv src _src
cocos luacompile -s _src -d src
```

luacompileコマンドでは、読み込み元ディレクトリ(-s)と、出力先ディレクトリ(-d)を指定するので、
*src*ディレクトリを*_src*にリネームしてから実行しています。
実行後に*src*ディレクトリを見ると、**.luac**という拡張子のファイルが出来ているはずです。
これがバイトコードに変換されたファイルになります。

#バイトコードで実行してみる
それでは**Android**でゲームを実行してみましょう。**Androidで**！

`cocos run -p android`

ちなみに*AppDelegate.cpp*の中で`src/main.lua`を指定しているのですが、
このファイル名は**.luac**に変更しなくても問題なく動作します。
詳細は*Cocos2dxLuaLoader.cpp*を見ればわかりますが、
**.lua**、**.luac**のどちらか存在している方のファイルを実行しているからです。

さて、勘のいい方はお気づきでしょうが、iOSではLuaバイトコードは実行できません。
これはCocos2d-xの問題ではなく、LuaJITがarm64に対応していないということが原因のようです。
[Luajit bytecode not supported on arm64？](http://discuss.cocos2d-x.org/t/luajit-bytecode-not-supported-on-arm64/16494)

**（追記）Cocos2d-x 3.6でLuaJIT2.1にアップデートされたようです。未確認ですが、これによりarm64に対応されているはずです。**

試しにXcodeでターゲットをiOSではなくMacにしてみると、問題なくロードすることができます。

`luacompile`では、コンパイルせずに、テキストのまま暗号化することもできるため、その方法を後述します。


#Luaスクリプトの暗号化
次はコンパイルしたファイルをさらに暗号化してみましょう。

ちなみに暗号化は**XXTEA**というアルゴリズムを使っているようです。
C++だと*xxtea.cpp*というソースで複合化を実行しています。
cocosコマンドは、Pythonスクリプトの中でXXTEAの暗号化処理が実装されています。

暗号化するには、`luacompile`コマンドに、`-e`というオプションを付けるだけです。

```bash
cocos luacompile -s _src -d src -e
```

これでバイトコードをさらに暗号化してくれます。簡単ですね。

暗号化キーを指定しないと、デフォルトのキー（2dxLua）になってしまうので、
暗号化キーを指定しましょう。`-k`でキーを指定します。

```bash
cocos luacompile -s _src -d src -e -k 1234567890ABCDEF
```

xxtea.cppを見ると、NULL文字で埋めて16桁にしているようなので、16桁で指定するのが良さそうです。

-s(encryptsign)というオプションもあるのですが、これはデフォルト(XXTEA)のままでも良いと思います。
暗号化する前にファイル内に指定した文字を埋め込んで、複合化の時にその文字と一致するかを
チェックしているようです。

さて、暗号化ではキーを指定するので、複合の際も同じキーを指定する必要があります。
*AppDelegate.cpp*の`AppDelegate::applicationDidFinishLaunching`に、
以下のコードを追加しましょう。

```cpp:AppDelegate.cpp
bool AppDelegate::applicationDidFinishLaunching()
{
    auto engine = LuaEngine::getInstance();
    ScriptEngineManager::getInstance()->setScriptEngine(engine);
    lua_State* L = engine->getLuaStack()->getLuaState();
    lua_module_register(L);

    // If you want to use Quick-Cocos2d-X, please uncomment below code
    // register_all_quick_manual(L);
    
    // ここから追加 ////////
    const char *key = "1234567890ABCDEF";
    const char *sign = "XXTEA";
    engine->getLuaStack()->setXXTEAKeyAndSign(key, (int)strlen(key), sign, (int)strlen(sign));
    // ここまで追加 ////////

    if (engine->executeScriptFile("src/main.lua")) {
        return false;
    }

    return true;
}
```

`setXXTEAKeyAndSign`でキーをセットすることで、Luaファイルをロードする際に、
複合化の処理が実行されるようになります。

Luaの中でrequireしたファイルはどうなるの？と疑問に思うかもしれません。
Cocos2d-x LuaBindingsでは、requireが実行される際、*Cocos2dxLuaLoader.cpp*の中の
`cocos2dx_lua_loader`という関数を実行して、ファイルを読み込んでいます。
この関数の中で複合化処理が実行されるため、requireしたファイルも問題なく複合化されます。
逆に、`setXXTEAKeyAndSign`をでキーをセットしている場合は、暗号化されていないファイルは
reqiureでエラーになります。


#テキストのまま暗号化
iOSでは、Luaバイトコードはロードできないと述べました。
そのためiOSでアプリをリリースしたい場合は、テキストのまま暗号化のみを行うことになります。
コンパイル処理をスキップするには、`--disable-compile`オプションを指定します。

```bash
cocos luacompile -s _src -d src -e -k 1234567890ABCDEF --disable-compile
```

これで暗号化のみが実行されます。

こうしておけば、暗号化してもiOS／Android両方で実行できるようになります。


#おわりに
スクリプトファイルの暗号化については、知りたい方も多いかと思うのですが、
意外に情報が少なかったため、書いてみました。

以上です。