NScripter のシナリオファイル nscript.dat を Python で復号化する

はじめに

venoroa1358です。

NScripter のシナリオファイル nscript.dat は暗号化されたファイルですが、XOR暗号によって暗号化されているため容易に復号できます。すでにYet Another nsdecという使いやすい復号化ツールが公開されていますが、ここではPythonによる復号化方法を紹介します。

実行にはPython3環境が必要です。復号化したファイルの取り扱いには注意してください。

原理

オープンソース版NScripter 実行環境である、ONscripter のシナリオ復号化処理を見ると、キー「0x84」を用いたXOR暗号であることが分かります。include文を除くと、たったの一行で実現されています。

XOR暗号は、あるキーで暗号化したファイルに対して、同じキーでもう一度暗号化すると元の状態に戻るという性質があります。したがって、0x84をキーとしたXOR暗号化プログラムを作成することで、nscript.datを復号化できます。

最小限のコードによる復号化

nscripter_bin = bytearray()

with open("nscript.dat", "rb") as f:
    # nscript.dat バイト列として読み込む
    nscript_dat = f.read()

    for b in nscript_dat:
        # 復号化
        nscripter_bin.append(b ^ 0x84)

# バイト列を Shift-JISでデコードしたものを文字列として保存
result = bytes(nscripter_bin).decode("shift_jis")

with open("00.txt", "w+", encoding="utf-8") as f:
    # utf-8 として書き出し
    f.write(result)

文字コードに関する厄介な問題を避けるため、復号化後のデータをShift-JISとみなし、それをutf-8に変換して保存しています。

上記の内容をテキストエディタ等にコピーし、拡張子を.pyにして保存し、nscript.datがあるディレクトリで実行すると復号化できます。次の例はWindowsでのものです。

> python.exe ファイル名.py

動作しない場合はPython3が正しくインストールされているか、nscript.datの場所を間違っていないかご確認ください。

おまけ:例外処理を加えて変換元や保存先のファイル名を指定する

おまけとして、例外処理をある程度頑張って、変換元と保存先のファイル名を指定できるようにしたコードを紹介します。

もし復号化対象のnscript.datの文字コードがShift-JISではなくutf-8だった場合も結果を正しく保存できるようにしたつもりです。Shift-JISでないnscript.datが存在するかは不明。

ファイル名はコマンドライン引数で取っています。実行時は、変換元のファイル名 保存先のファイル名 の順で指定してください。

> python.exe .\nsdec.py nscript.dat story.txt

コードはこんな感じです。

import sys


def decode_nscript(input="nscript.dat", output="00.txt"):
    NSCRIPTER_XOR_KEY = 0x84
    nscripter_bin = bytearray()

    try:
        print(f"{input} の変換を開始します。")

        with open(input, "rb") as f:
            nscript_dat = f.read()
            for b in nscript_dat:
                nscripter_bin.append(b ^ NSCRIPTER_XOR_KEY)

        print("復号化に成功しました。")

    except FileNotFoundError:
        sys.exit(f"{input} が見つかりません。中断します。")

    except ValueError:
        sys.exit(f"{input} をオープンできません。中断します。")

    except PermissionError:
        sys.exit(f"{input} へのアクセスが拒否されました。中断します。")

    result = bytes(nscripter_bin)

    try:
        result = result.decode("shift_jis")
        save_result(output, result, "w+", "utf-8")

    except UnicodeDecodeError:
        print("Shift-JISからutf-8への変換に失敗しました。")
        save_result(output, result, "wb+", None)


def save_result(output, result, mode, enctype):
    try:
        with open(output, mode, encoding=enctype) as f:
            f.write(result)

            print(f"復号したデータの保存に成功しました。 {output} を確認してください。")

    except ValueError:
        sys.exit(f"{output} をオープンできません。中断します。")

    except PermissionError:
        sys.exit(f"{output} へのアクセスが拒否されました。中断します。")


if __name__ == "__main__":
    args = sys.argv

    if len(args) >= 2:
        input = args[1]
        output = args[2]
        decode_nscript(input, output)

    else:
        decode_nscript()

余談

XOR暗号の性質上、NScripter 公式の暗号化ツール(nscmake.exe)をnscript.datに適用すると復号化できそうな気がするのですが、どうなんでしょうね。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です