0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

jarファイルを指定してclassファイルをデコンパイルしながらUithub風テキストにしてくれるPythonコード

0
Posted at

動機

  • jarファイルでもデコンパイルしてUithubっぽいのを使いたい
  • Pythonファイルをクリックするだけで手軽に使いたい

使い方

  • 前提条件としてJavaがインストールされていること(java -version で確認)
  • "JarDecompileUit.py"ファイルを作成してエディタでソースコードを張り付ける
    • target_extensions 変数を変更することで、対象ファイルの種類を自由に設定可能なので、各々の作業に合わせて調整する(.classはこの設定とは無関係)
  • "cfr-0.152.jar"を配置して、cfr_jar_path 変数を配置したパスとなるように変更
  • "JarDecompileUit.py"をクリックして、指示に従ってjarファイルを指定する
  • "output.txt"にUithub風テキスト化した結果が入っているので、生成AIへの質問等で活用する

ソースコード(Pythonファイル)

import os
import zipfile
import subprocess
import tempfile

# CFRのパス
cfr_jar_path="cfr-0.152.jar"

# 処理する拡張子のリスト
target_extensions = ['.MF', '.LIST', '.properties', '.xml', '.dot']

def collect_jar_tree(jar_path) -> str:
    lines = []
    with zipfile.ZipFile(jar_path, 'r') as jar:
        entries = sorted(jar.namelist())
        indent_stack = []

        for entry in entries:
            levels = entry.strip("/").split("/")
            for i in range(len(indent_stack), len(levels) - 1):
                indent_stack.append("")

            indent = "".join(indent_stack[:len(levels) - 1])
            prefix = "├── " if len(indent_stack) > 0 else ""
            lines.append(f"{indent}{prefix}{levels[-1]}")

            if entry.endswith("/"):
                indent_stack = indent_stack[:len(levels)]
            else:
                indent_stack = indent_stack[:len(levels) - 1]

    return "\n".join(lines)

def decompile_jar_to_single_txt(jar_path, output_txt_path, cfr_jar_path):
    # 1. ツリー構造取得
    jar_tree_text = collect_jar_tree(jar_path)

    with tempfile.TemporaryDirectory() as temp_dir:
        with zipfile.ZipFile(jar_path, 'r') as jar:
            jar.extractall(temp_dir)
            jar_entries = jar.namelist()

        decompiled_dir = os.path.join(temp_dir, "decompiled_java")
        os.makedirs(decompiled_dir, exist_ok=True)

        # 2. .class をデコンパイルして .java に保存
        for root, _, files in os.walk(temp_dir):
            for file in files:
                if file.endswith(".class"):
                    class_path = os.path.abspath(os.path.join(root, file))
                    print(f"Decompiling: {file}")
                    result = subprocess.run(
                        ["java", "-jar", cfr_jar_path, class_path, "--outputdir", decompiled_dir],
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        text=True
                    )
                    if result.returncode != 0:
                        print(f" Failed: {file}\n{result.stderr}")

        # 3. 出力ファイルを開く(階層 + .java + その他ファイルの中身)
        with open(output_txt_path, "w", encoding="utf-8") as out_file:
            # ツリー構造を最初に書く
            out_file.write(jar_tree_text)
            out_file.write("\n\n")

            # 各 .java の内容を追記
            for root, _, files in os.walk(decompiled_dir):
                for file in sorted(files):
                    if file.endswith(".java"):
                        java_path = os.path.join(root, file)
                        with open(java_path, "r", encoding="utf-8") as jf:
                            out_file.write("------------------------------\n")
                            out_file.write(f"{file}\n")
                            out_file.write("------------------------------\n")
                            out_file.write(jf.read())
                            out_file.write("\n\n")

            # 4. .class 以外のファイルをJARから抽出して書き出す
            for entry in jar_entries:
                if entry.endswith("/") or entry.endswith(".class"):
                    continue  # ディレクトリや.classは除外(すでに処理済み)

                if not any(entry.endswith(ext) for ext in target_extensions):
                    continue

                file_path = os.path.join(temp_dir, entry)
                if os.path.isfile(file_path):
                    try:
                        with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                            content = f.read()
                        out_file.write("------------------------------\n")
                        out_file.write(f"{entry}\n")
                        out_file.write("------------------------------\n")
                        out_file.write(content)
                        out_file.write("\n\n")
                    except Exception as e:
                        out_file.write("------------------------------\n")
                        out_file.write(f"{entry}\n")
                        out_file.write("------------------------------\n")
                        out_file.write(f" Could not read file: {e}\n\n")

def main():
    # 現在の作業ディレクトリを取得
    current_dir = os.getcwd()

    # JARファイルの入力を促す
    jar_path = input("JARファイルを入力してください: ")

    # output.txtの初期化
    output_file = os.path.join(current_dir, "output.txt")
    with open(output_file, "w", encoding="utf-8") as file_output:
        file_output.write("\n\n")

    # JARファイルの存在チェック
    if not os.path.isfile(jar_path):
        with open(output_file, "w", encoding="utf-8") as file_output:
            file_output.write("指定されたJARファイルは存在しません。\n")
        print("指定されたJARファイルは存在しません。")
        return

    decompile_jar_to_single_txt(
        jar_path=jar_path,
        output_txt_path=output_file,
        cfr_jar_path=cfr_jar_path
    )

    # cmdに表示
    with open(output_file, "r", encoding="utf-8") as file_output:
        contents = file_output.read()
        print(contents)

    # 入力待ち
    input("続行するには何かキーを押してください...")

if __name__ == "__main__":
    main()

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?