#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
mhp3bin — MHP3RD.BIN(配信クエストセーブ) ⇄ 平文data.bin パイプライン

2層の暗号を扱う:
  Layer A (PSPセーブ暗号)   : endecrypter.exe (本フォルダ。cielavenir/psp-savedata-endecrypter をMSVCでビルド)
                              ※ ULJM05800QST の gamekey(16バイト) が必須
  Layer B (MH独自暗号)      : 本物の endecryptQST3rd.exe (GUI) で実施
                              ※ クエスト追加/編集だけなら平文data.binを mhp3pack.py で操作

== コマンド ==
  extractA  <MHP3RD.BIN> <gamekey.bin> <out_mid.bin>
        Layer A を剥がす: MHP3RD.BIN → 途中データ(=Layer B暗号済 data.bin)
        この後 endecryptQST3rd.exe で 途中データ→data.bin に復号(Layer B)

  buildA    <mid.bin> <gamekey.bin> <PARAM.SFO> <out_MHP3RD.BIN>
        Layer A を掛ける: 途中データ → MHP3RD.BIN (PARAM.SFOのハッシュも更新)
        ※ mid.bin は endecryptQST3rd.exe で data.bin→途中データ に暗号化(Layer B)したもの

  setquest  <data.bin> <slot> <quest.dat|.mis|.pat> <out.bin>
        平文data.binの指定スロットにクエストを差し込む(mhp3pack のラッパ)

  list      <data.bin>          平文data.bin内のクエスト一覧

== 完全な流れ(配布BINを作る) ==
  1) python mhp3bin.py extractA MHP3RD.BIN gamekey.bin mid.bin
  2) endecryptQST3rd.exe で mid.bin を「復号」→ data.bin (GUI: 復号ボタン→mid.binを選択)
  3) python mhp3bin.py setquest data.bin 0 myquest.dat data_new.bin   (好きなだけ差込)
  4) endecryptQST3rd.exe で data_new.bin を「暗号化」→ mid_new.bin (GUI)
  5) python mhp3bin.py buildA mid_new.bin gamekey.bin PARAM.SFO MHP3RD_new.bin
  6) MHP3RD_new.bin と PARAM.SFO を PSP/SAVEDATA/ULJM05800QST/ に配置 → 集会所イベントに出現
"""
import sys, os, subprocess, shutil

HERE = os.path.dirname(os.path.abspath(__file__))
ENDE = os.path.join(HERE, "endecrypter.exe")

def need(p, what):
    if not os.path.exists(p): sys.exit(f"[エラー] {what} が見つかりません: {p}")

def run_decrypt(enc_bin, gamekey, out_bin):
    need(ENDE,"endecrypter.exe"); need(enc_bin,"入力BIN"); need(gamekey,"gamekey")
    with open(out_bin,"wb") as fo:
        r = subprocess.run([ENDE, enc_bin, gamekey], stdout=fo)
    sz = os.path.getsize(out_bin)
    if r.returncode!=0 or sz==0:
        sys.exit(f"[エラー] Layer A 復号に失敗(code={r.returncode}, out={sz}B)。gamekeyが正しいか確認してください。")
    print(f"[OK] Layer A 復号: {out_bin} ({sz} バイト)")

def run_encrypt(dec_bin, gamekey, sfo, out_bin):
    need(ENDE,"endecrypter.exe"); need(dec_bin,"入力(途中データ)"); need(gamekey,"gamekey"); need(sfo,"PARAM.SFO")
    # endecrypter は PARAM.SFO を読み、ハッシュを更新して上書きする。元を壊さぬよう作業コピーを使う。
    work_sfo = out_bin + ".PARAM.SFO"
    shutil.copyfile(sfo, work_sfo)
    with open(out_bin,"wb") as fo:
        r = subprocess.run([ENDE, dec_bin, gamekey, work_sfo], stdout=fo)
    sz = os.path.getsize(out_bin)
    if r.returncode!=0 or sz==0:
        sys.exit(f"[エラー] Layer A 暗号化に失敗(code={r.returncode}, out={sz}B)。")
    print(f"[OK] Layer A 暗号化: {out_bin} ({sz} バイト)")
    print(f"[OK] 更新済み PARAM.SFO: {work_sfo}  ← これも SAVEDATA に一緒に置いてください")

def cmd_extractA(a): run_decrypt(a[0], a[1], a[2])
def cmd_buildA(a):   run_encrypt(a[0], a[1], a[2], a[3])

def cmd_setquest(a):
    sys.path.insert(0, HERE); import mhp3pack
    import argparse
    ns = argparse.Namespace(databin=a[0], slot=int(a[1]), questdat=a[2], outbin=a[3])
    mhp3pack.cmd_db_set(ns)

def cmd_list(a):
    sys.path.insert(0, HERE); import mhp3pack
    import argparse
    mhp3pack.cmd_db_list(argparse.Namespace(databin=a[0]))

def main():
    if len(sys.argv)<2:
        print(__doc__); return
    cmd=sys.argv[1]; rest=sys.argv[2:]
    table={"extractA":(cmd_extractA,3),"buildA":(cmd_buildA,4),"setquest":(cmd_setquest,4),"list":(cmd_list,1)}
    if cmd not in table: print(__doc__); sys.exit(f"unknown command: {cmd}")
    fn,n=table[cmd]
    if len(rest)<n: sys.exit(f"[使い方] {cmd} は引数{n}個必要です。--help で詳細。")
    fn(rest)

if __name__=="__main__":
    main()
