Androidアプリ「RELEASE THE SPYCE secret fragrance」から画像と音声を抽出する

「RELEASE THE SPYCE secret fragrance」は、株式会社オルトプラスが2019年にリリース[^1]したゲームで、すでにサービスが終了しています[^2]。
このゲームにはiPhone版とAndroid版がありますが、本記事はAndroid版アプリを対象とします。この記事は「RELEASE THE SPYCE secret fragrance」の画像と音声データの抽出を目的とするものですが、CRI middlewareを採用する他のスマートフォンゲームでも同様の方法が使えるかもしれません。

## 著作権に関する注意
この手順を実施して得た画像や音声をインターネット等で配信することは、著作権([公衆送信権](https://elaws.e-gov.go.jp/document?lawid=345AC0000000048#Mp-Ch_2-Se_3-Ss_3))の侵害となる可能性があり、処罰の対象となる可能性があります。私的利用の範囲に留めましょう。

## 前提条件
* このゲーム (jp.co.altplus.spyce) をインストールしたAndroid端末を所持していること
* その端末のUSBデバッグを有効化していること
* adbコマンドが使えること
* gitコマンドが使えること
* python3がインストールされていること
* pipがインストールされていること
* OS: Ubuntu 20.10 (これ以外でも可能だと思いますが、試していません)

## 作業用ディレクトリを作る

```bash
mkdir spyce
export SPYCE=$(pwd)/spyce
```

## 必要なツールをダウンロードする
### Android Backup Extractor
バックアップしたアプリを解凍するツールです。
https://sourceforge.net/projects/adbextractor/
ZIPファイルがダウンロードされます。`abe.jar`というファイルが含まれていますので、このファイルを適当な場所に保存してください。

### UnityPack (画像抽出用)
UnityPack形式のファイルから画像データ(PNG形式)を抽出するためのpythonスクリプトです。
#### クローン

```bash
cd ${SPYCE}
git clone https://github.com/HearthSim/UnityPack.git
cd UnityPack
git checkout f8cdc2516538d189606a76986ad2d71c3fad5f8b
```

#### パッチ当て
リリフレの画像は[ETC2](https://en.wikipedia.org/wiki/Ericsson_Texture_Compression)というフォーマットになっていますが、UnityPackは対応していません。
そこで、ETC2に対応させるためのパッチを当てます。
次の差分データを `etc2.patch` として保存してください:

```diff
diff --git a/bin/unityextract b/bin/unityextract
index 3056b0c..3e12ac2 100755
--- a/bin/unityextract
+++ b/bin/unityextract
@@ -135,6 +135,7 @@ class UnityExtract:
 					image = d.image
 				except NotImplementedError:
 					print("WARNING: Texture format not implemented. Skipping %r." % (filename))
+					raise
 					continue
 
 				if image is None:
diff --git a/unitypack/engine/texture.py b/unitypack/engine/texture.py
index 35a3b21..652ed17 100644
--- a/unitypack/engine/texture.py
+++ b/unitypack/engine/texture.py
@@ -94,6 +94,8 @@ IMPLEMENTED_FORMATS = (
 	TextureFormat.DXT5,
 	TextureFormat.DXT5Crunched,
 	TextureFormat.BC7,
+        TextureFormat.ETC2_RGB,
+        TextureFormat.ETC2_RGBA8,
 )
 
 
@@ -158,7 +160,9 @@ class Texture2D(Texture):
 	def image(self):
 		from PIL import Image
 		from decrunch import File as CrunchFile
+		import etcpack
 
+		mode = None
 		if self.format not in IMPLEMENTED_FORMATS:
 			raise NotImplementedError("Unimplemented format %r" % (self.format))
 
@@ -171,11 +175,19 @@ class Texture2D(Texture):
 		elif self.format == TextureFormat.BC7:
 			codec = "bcn"
 			args = (7, )
+		elif self.format == TextureFormat.ETC2_RGB:
+			codec = "etc2"
+			args = (1, )
+			mode = 'RGB'
+		elif self.format == TextureFormat.ETC2_RGBA8:
+			codec = "etc2"
+			args = (3, )
 		else:
 			codec = "raw"
 			args = (self.format.pixel_format, )
 
-		mode = "RGB" if self.format.pixel_format in ("RGB", "RGB;16") else "RGBA"
+		if not mode:
+			mode = "RGB" if self.format.pixel_format in ("RGB", "RGB;16") else "RGBA"
 		size = (self.width, self.height)
 
 		data = self.image_data
```

`etc2.patch` を作成したら、このパッチを適用します:

```bash
# UnityPackディレクトリ内で実行する
patch -p1 < etc2.patch
```

#### インストール
UnityPackをインストールします。

```bash
# UnityPack ディレクトリ内で実行する
pip3 install .
```

### acbpy (音声抽出用)
後述するacb2wavが依存するプログラムです。

```
cd ${SPYCE}
git clone https://github.com/CrescentApricot/acbpy.git
cd acbpy
pip3 install .
```

### hcapy (音声抽出用)
後述するacb2wavが依存するプログラムです。

```
cd ${SPYCE}
git clone https://github.com/CrescentApricot/hcapy.git
cd hcapy
sed -i "s,.*url =.*,\turl = https://github.com/Cryptomelone/hca2wav.git,g" .gitmodules
git submodule sync
git submodule update
pip3 install .
```

### acb2wav (音声抽出用)
リリフレの音声は[ACB(Atom CueSheet Binary)](https://game.criware.jp/manual/native/adx2/latest/criatom_basics_acb.html)という形式のファイルに格納されています。このファイルからwavファイルとして音声を抽出するために、 `acb2wav.py` というpythonスクリプトが必要です。

```bash
cd ${SPYCE}
git clone https://github.com/KOZ39/acb2wav.git
ls acb2wav/src/acb2wav.py
```

## 画像と音声の抽出手順
### アプリをPCにバックアップする
リリフレを入れたAndroid端末をPCに接続し、adbコマンドでアプリをPCにバックアップします。

```bash
cd ${SPYCE}
# spyce.adb というファイル名でアプリをバックアップする
adb backup -app -obb -f spyce.adb jp.co.altplus.spyce
# Android端末に「フルバックアップ」というウィンドウが
# 出ますので「データをバックアップ」をタップしてください。
```

### バックアップファイルをtarファイルに変換する
バックアップした`spyce.adb`をtarファイルに変換します。

```
java -jar abe.jar unpack spyce.adb spyce.tar
```

### tarファイルを解凍する
`spyce.tar` を解凍します。 `apps` というディレクトリが作られます。

```
tar xf spyce.tar
test -d apps && echo "apps created"
```

### 画像を抽出する
画像は `apps/jp.co.altplus.spyce/ef/Android` ディレクトリ配下に格納されているファイルに含まれています。

```
cd ${SPYCE}/apps/jp.co.altplus.spyce/ef/Android
ls -l
# drwxrwx--x   6 user user  4096  4月  7 00:45 adveffect
# drwxrwx--x 170 user user  4096  4月  7 00:48 advunit
# drwxrwx--x   2 user user  4096  4月  7 00:45 description
# drwxrwx--x   2 user user  4096  4月  7 00:45 eventquestmapeffect
# ...
```
UnityPackをインストールしたため、 `unityextract` コマンドを使用可能な状態になっているはずです。このコマンドを使ってPNGファイルを抽出します。
キャラクターの立ち絵をこのディレクトリの下の `texturecharacterstanding` のファイルから抽出する例を示します。

```bash
cd ${SPYCE}/apps/jp.co.altplus.spyce/ef/Android/texturecharacterstanding
unityextract --image *
```

`unityextract` コマンドを実行すると、同じディレクトリにPNGファイルが作成されます。
`11001.png` がモモ、 `11002.png` が雪先輩といった具合になっています。
`Android` ディレクトリ配下の他のディレクトリのファイルも大体同じようにPNGファイルを抽出できるようです。

### 音声を抽出する
音声は `apps/jp.co.altplus.spyce/ef/OtherAssets/Sounds` ディレクトリ配下の各ファイルに格納されており、ACB(Atom CueSheet Binary)という形式になっています。
ACBファイルから生音声(wav)を抽出するために、 `acb2wav.py` を使用します。

タイトル「リリースザスパイス シークレットフレグランス」を各キャラクターが読み上げている音声を抽出する例を示します。

```
export ACB2WAV_PY=~/acb2wav/src/acb2wav.py # acb2wav.pyがあるパス
                                           # に読み替えてください。
cd ${SPYCE}/apps/jp.co.altplus.spyce/ef/OtherAssets/Sounds
cd Title
$ACB2WAV_PY -i . -e acb
# afs2: 18 files in ar
# afs2: aligned to 32 bytes
# afs2: a file offset is 4 bytes
# Title_002.wav
# Title_003.wav
# Title_004.wav
# Title_005.wav
# ...
```


[^1]: https://www.altplus.co.jp/topics/20190212-m.html
[^2]: http://www.altplus.co.jp/wp-content/uploads/2020/04/20200401koukoku.pdf