はじめに
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に適用すると復号化できそうな気がするのですが、どうなんでしょうね。