#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
mhp3bin_auto — MHP3RD.BIN ⇄ 平文data.bin を「PC完全自動」で変換（1コマンド）

Layer A (PSPセーブ暗号)  : endecrypter.exe（本フォルダ。検証済み）
Layer B (MH独自暗号)     : 本物の endecryptQST3rd.exe を pywinauto でGUI自動操作

== コマンド ==
  extract <MHP3RD.BIN> <gamekey.bin> <out_data.bin>
        BIN → (Layer A) → 途中データ → (Layer B) → 平文data.bin   ※全自動
  build   <data.bin> <gamekey.bin> <PARAM.SFO> <out_MHP3RD.BIN>
        data.bin → (Layer B) → 途中データ → (Layer A) → MHP3RD.BIN ※全自動
  list    <data.bin>
        data.bin内のクエスト一覧（名前・報酬・星）

依存: pip install pywinauto capstone ; endecrypter.exe と endecryptQST3rd.exe(=../endecryptQST/)
"""
import sys, os, time, shutil, subprocess

HERE = os.path.dirname(os.path.abspath(__file__))
ENDE = os.path.join(HERE, "endecrypter.exe")
QST_DIR = None
for c in [os.path.join(HERE,"..","endecryptQST"), os.path.join(HERE,"endecryptQST")]:
    if os.path.exists(os.path.join(c,"endecryptQST3rd.exe")): QST_DIR=os.path.abspath(c); break
QST = os.path.join(QST_DIR,"endecryptQST3rd.exe") if QST_DIR else None

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

# ---- Layer A (endecrypter.exe) ----
def layerA_decrypt(enc, key, out):
    need(ENDE,"endecrypter.exe"); need(enc,"BIN"); need(key,"gamekey")
    with open(out,"wb") as f: r=subprocess.run([ENDE,enc,key],stdout=f)
    if r.returncode!=0 or os.path.getsize(out)==0: sys.exit("[エラー] Layer A復号失敗（gamekeyを確認）")
    print(f"  [Layer A] 復号 OK ({os.path.getsize(out)} bytes)")

def layerA_encrypt(dec, key, sfo, out):
    need(ENDE,"endecrypter.exe"); need(dec,"mid"); need(key,"gamekey"); need(sfo,"PARAM.SFO")
    wsfo=out+".PARAM.SFO"; shutil.copyfile(sfo,wsfo)
    with open(out,"wb") as f: r=subprocess.run([ENDE,dec,key,wsfo],stdout=f)
    if r.returncode!=0 or os.path.getsize(out)==0: sys.exit("[エラー] Layer A暗号化失敗")
    print(f"  [Layer A] 暗号化 OK ({os.path.getsize(out)} bytes) / 更新SFO: {wsfo}")

# ---- Layer B (endecryptQST GUI via pywinauto) ----
def _qst_gui(menu_index, dialog_kw, set_path, expect_file):
    from pywinauto.application import Application
    from pywinauto import Desktop
    need(QST,"endecryptQST3rd.exe")
    if os.path.exists(expect_file): os.remove(expect_file)
    app=Application(backend="win32").start(f'"{QST}"', work_dir=QST_DIR); time.sleep(1.5)
    w=app.top_window(); w.set_focus(); w.menu().item(menu_index).click(); time.sleep(1.2)
    dlg=None
    for _ in range(20):
        for win in Desktop(backend="win32").windows(class_name="#32770"):
            t=win.window_text()
            if any(k in t for k in dialog_kw): dlg=win; break
        if dlg: break
        time.sleep(0.3)
    if not dlg: app.kill(); sys.exit("[エラー] endecryptQSTのダイアログを検出できません")
    dlg.set_focus()
    for ed in dlg.descendants(class_name="Edit"):
        try: ed.set_edit_text(set_path); break
        except Exception: pass
    time.sleep(0.3)
    done=False
    for bt in dlg.descendants(class_name="Button"):
        try:
            tt=bt.window_text()
            if any(k in tt for k in ("開","保存","Open","Save")): bt.click_input(); done=True; break
        except Exception: pass
    if not done:
        try: dlg.child_window(control_id=1).click_input()
        except Exception: pass
    for _ in range(40):
        if os.path.exists(expect_file) and os.path.getsize(expect_file)>0: break
        time.sleep(0.5)
    time.sleep(1.0)
    ok=os.path.exists(expect_file) and os.path.getsize(expect_file)>0
    try: app.kill()
    except Exception: pass
    if not ok: sys.exit("[エラー] Layer B（endecryptQST）処理に失敗")
    print(f"  [Layer B] OK -> {expect_file} ({os.path.getsize(expect_file)} bytes)")

def layerB_decrypt(mid, out_databin):
    # endecryptQST: 復号(menu1) は選択ファイルを読み data.bin を cwd に出力
    tmp_mid=os.path.join(QST_DIR,"_in.bin"); shutil.copyfile(mid,tmp_mid)
    data_out=os.path.join(QST_DIR,"data.bin")
    _qst_gui(1, ("復号","選択"), tmp_mid, data_out)
    shutil.copyfile(data_out, out_databin)

def layerB_encrypt(databin, out_mid):
    # endecryptQST: 暗号(menu0) は cwd の data.bin を読み 保存先に出力
    shutil.copyfile(databin, os.path.join(QST_DIR,"data.bin"))
    _qst_gui(0, ("暗号","保存"), out_mid, out_mid)

# ---- 高レベル ----
def cmd_extract(a):
    binf,key,out=[os.path.abspath(x) for x in a]
    mid=out+".mid"
    print("[1/2] Layer A 復号..."); layerA_decrypt(binf,key,mid)
    print("[2/2] Layer B 復号（endecryptQST自動操作）..."); layerB_decrypt(mid,out)
    os.remove(mid)
    print(f"[完了] 平文data.bin: {out}")
    cmd_list([out])

def cmd_build(a):
    databin,key,sfo,out=[os.path.abspath(x) for x in a]
    mid=out+".mid"
    print("[1/2] Layer B 暗号化（endecryptQST自動操作）..."); layerB_encrypt(databin,mid)
    print("[2/2] Layer A 暗号化..."); layerA_encrypt(mid,key,sfo,out)
    os.remove(mid)
    print(f"[完了] 配布用 MHP3RD.BIN: {out}（+ {out}.PARAM.SFO も一緒に配置）")

def cmd_list(a):
    b=open(a[0],'rb').read(); T=bytes.fromhex('94000000763030')
    def nm(q):
        try:
            n=q[172]|(q[173]<<8); bs=q[n]|(q[n+1]<<8); o0=q[bs]|(q[bs+1]<<8); o1=q[bs+4]|(q[bs+5]<<8)
            return q[o0:o1].split(b'\x00')[0].decode('utf-8','replace')
        except: return '?'
    c=0
    for k in range(100):
        base=k*0x6010; q=b[base+0x20:base+0x6000]
        if q[:7]!=T: continue
        c+=1; mp=q[180]; mn=q[156]|(q[157]<<8)|(q[158]<<16); st=q[178]
        fn=b[base+0x6000:base+0x600c].split(b'\x00')[0].decode('latin1')
        print(f'  slot{k:02d} [{fn:11s}] 星{st} {mn:6d}z  "{nm(q)}"')
    print(f'  合計 {c} クエスト')

def main():
    if len(sys.argv)<2: print(__doc__); return
    cmd=sys.argv[1]; rest=sys.argv[2:]
    tbl={"extract":(cmd_extract,3),"build":(cmd_build,4),"list":(cmd_list,1)}
    if cmd not in tbl: print(__doc__); sys.exit(f"unknown: {cmd}")
    fn,n=tbl[cmd]
    if len(rest)<n: sys.exit(f"[使い方] {cmd} は引数{n}個必要")
    fn(rest)

if __name__=="__main__": main()
