方法 1:CFR に JAR を直接渡す

CFR は JAR ファイルをそのまま引数に渡せる。これが最も簡単な方法だ。

Shell — CFR で JAR を一括処理
# JAR を直接指定(全クラスを逆コンパイル)
java -jar cfr.jar myapp.jar --outputdir decompiled/

# ファイル数を確認
find decompiled/ -name "*.java" | wc -l

# 特定のクラスだけ取り出す場合
java -jar cfr.jar myapp.jar \
  --classname com.example.MyService \
  --outputdir decompiled/

# 複数 JAR をまとめて処理(シェルスクリプト例)
for jar in lib/*.jar; do
  name=$(basename "$jar" .jar)
  java -jar cfr.jar "$jar" --outputdir "decompiled/${name}/"
done
出力ディレクトリ構造
decompiled/
└── com/
    └── example/
        ├── Main.java
        ├── Service.java
        ├── Service$1.java        ← 匿名クラス
        ├── util/
        │   ├── StringUtil.java
        │   └── DateUtil.java
        └── model/
            ├── User.java
            └── Order.java

方法 2:JAR を展開してバッチ処理

JAR を展開してから処理することで、特定ディレクトリのクラスのみを対象にしたり、 前処理(ファイルリスト確認など)を挟んだりできる。

Shell — JAR 展開 → バッチ逆コンパイル
# JAR の内容を一覧表示(展開前の確認)
jar tf myapp.jar | head -30

# 展開(jarコマンド)
mkdir extracted
jar xf myapp.jar -C extracted/

# または unzip でも同じ
unzip myapp.jar -d extracted/

# 展開後の .class ファイル数を確認
find extracted/ -name "*.class" | wc -l

# 自社コードのみを逆コンパイル(com/example 以下だけ)
find extracted/com/example -name "*.class" | while read f; do
  java -jar cfr.jar "$f" --outputdir decompiled/
done

# ディレクトリ全体を一括指定(CFR はディレクトリも受け付ける)
java -jar cfr.jar extracted/com/example/ --outputdir decompiled/

⚠️ 内部クラスを含む場合の注意

Outer$Inner.classOuter.class と同じディレクトリに存在する必要がある。 単体ファイルを指定すると内部クラスが解決できず、/* MISSING */ コメントが出力されることがある。 ディレクトリごと指定するか JAR を直接渡す方法(方法 1)を使うと自動的に解決される。

方法 3:jadx GUI で開く

jadx-gui を使えばコマンドを書かずに JAR をブラウズしながら逆コンパイルできる。

jadx-gui 操作フロー
# 1. jadx-gui を起動
./bin/jadx-gui   # macOS/Linux
bin\jadx-gui.bat # Windows

# 2. File → Open File → myapp.jar を選択
# 3. 左ペインにクラスツリーが表示される
#    Source code タブでソースを確認
#    Smali タブでバイトコードを確認
# 4. 特定クラスを右クリック → Save as Java file
# 5. 全体を保存する場合
#    File → Save All → 出力先ディレクトリを選択

# CLI でエクスポートする場合
./bin/jadx -d output/ myapp.jar

💡 jadx の検索機能

jadx-gui には 全文検索(Ctrl+Shift+F)がある。「どのクラスに特定のメソッドがあるか」を探す際に非常に便利。クラス名・メソッド名の検索もできるため、大規模 JAR の解析起点として重宝する。

内部クラス($)の扱い

Java の内部クラス・匿名クラス・ラムダ由来の合成クラスは Outer$Inner.classOuter$1.class などのファイルとして存在する。

ファイル名パターン意味
Service$1.class匿名クラス(番号付き)
Service$Builder.class名前付き内部クラス
Service$$Lambda$14.classラムダ由来の合成クラス(JVM 実装による)
package-info.classパッケージ情報(Javadoc など)

CFR や jadx はこれらを自動的に外部クラスと統合して復元する。 手動で .class を個別指定する場合は、外部クラスと内部クラスを同じ引数に含めるか、ディレクトリごと指定すること。

出力ディレクトリの整理

Shell — 出力ファイルの整理スクリプト
#!/bin/bash
# decompile_jar.sh — JAR を逆コンパイルして整理する

JAR_FILE="$1"
OUTPUT_DIR="${2:-decompiled}"
CFR_JAR="./cfr.jar"

if [ -z "$JAR_FILE" ]; then
  echo "Usage: $0 <target.jar> [output_dir]"
  exit 1
fi

echo "▶ 逆コンパイル開始: $JAR_FILE → $OUTPUT_DIR"
mkdir -p "$OUTPUT_DIR"

java -jar "$CFR_JAR" "$JAR_FILE" \
  --outputdir "$OUTPUT_DIR" \
  --comments false \
  --showversion false

JAVA_COUNT=$(find "$OUTPUT_DIR" -name "*.java" | wc -l)
echo "✅ 完了: $JAVA_COUNT ファイルを出力"

# 匿名クラスファイル($数字.java)を別ディレクトリに移動
mkdir -p "$OUTPUT_DIR/_anonymous"
find "$OUTPUT_DIR" -name '*$[0-9]*.java' \
  -exec mv {} "$OUTPUT_DIR/_anonymous/" \;

逆コンパイル結果の確認ポイント

確認項目方法
エラーや警告コメントがないか grep -r "MISSING\|ERROR\|// This method" decompiled/
すべてのクラスが出力されているか .class 数と .java 数を比較(内部クラスは統合されるため少なくなる)
再コンパイルできるか javac -cp . decompiled/com/example/*.java を試す
文字化けがないか 日本語文字列を grep して確認

次の PART では…

PART 05 では WAR ファイルからの抽出を解説する。WAR の構造(WEB-INF/classes と WEB-INF/lib の内包 JAR)を理解し、再帰的に処理する手順を説明する。

→ PART 05 — WAR から抽出へ