#!/usr/bin/env python3
"""
SecureVeil - 端到端加密伪装传输工具 (Python 本地版)

支持大文件加密/解密，零宽字符隐写，与 Web 版完全兼容。

用法:
  python secure_veil.py encrypt-text <文本> [--scene 场景] [-o 输出文件]
  python secure_veil.py encrypt-file <文件路径> [--scene 场景] [-o 输出文件]
  python secure_veil.py decrypt-text <伪装文本文件> [-o 输出文件]
  python secure_veil.py decrypt-file <伪装文本文件> [-o 输出目录]

依赖: pip install cryptography
"""

import argparse
import base64
import hashlib
import json
import os
import secrets
import struct
import sys
import textwrap

try:
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
    from cryptography.hazmat.primitives import hashes
except ImportError:
    print("=" * 50)
    print("  SecureVeil - 缺少依赖库")
    print("=" * 50)
    print()
    print("错误: 未安装 cryptography 库")
    print()
    print("请打开命令行（CMD/PowerShell），运行：")
    print()
    print("  pip install cryptography")
    print()
    print("安装完成后重新运行本程序。")
    print()
    input("按回车键退出...")
    sys.exit(1)

# ============================================================
# 常量
# ============================================================

PBKDF2_ITERATIONS = 100_000
SALT_LENGTH = 16
IV_LENGTH = 12
MAGIC_HEADER = b'ENC2'

# 零宽字符映射 (每字符 2 bit)
ZW_CHARS = ['\u200B', '\u200C', '\u200D', '\uFEFF']
ZW_MAGIC_INDICES = [0, 3, 2, 1]  # 魔数标记

# 伪装场景
SCENES = {
    'chat': [
        '吃了，确实很好吃，下次我们可以再去。最近工作挺忙的，不过周末终于可以放松一下了。对了，你上次说的那个事情我想了想，觉得还是可以尝试一下的。毕竟机会难得，错过了可能就没有了。我这边没什么问题，主要就是时间上需要协调一下。你看下周什么时候方便，我们找个时间详细聊聊？',
        '今天天气还不错，早上出门的时候阳光挺好的。上午在公司忙了一阵，中午跟同事去楼下吃了个饭。下午继续干活，开了个会讨论下个季度的计划。下班后去超市买了点水果，苹果和香蕉。晚上回到家随便做了点吃的，看了会儿手机就准备休息了。明天还得早起，希望天气能一直这么好。',
        '跟你分享一下我今天遇到的事情。早上坐地铁的时候差点坐过站，幸好反应快跳下去了。到公司发现忘带工牌了，又跑回去拿。中午吃饭的时候碰到好久没见的朋友，聊了一会儿。下午工作还算顺利，把拖了几天的事情终于搞定了。晚上打算早点休息，养足精神明天继续加油。',
    ],
    'note': [
        '购物清单：鸡蛋一盒、牛奶两瓶、全麦面包一袋、西红柿四个、黄瓜两根、土豆三个、洋葱一个、生姜一块、大蒜两头、生抽一瓶、食盐一袋、洗洁精一瓶、垃圾袋两卷、抽纸三包。记得看看有没有打折的，如果有机蔬菜新鲜的话也可以买一些。另外家里的洗衣液快用完了，这次也顺便带上。回来之后把冰箱整理一下，过期的东西扔掉。',
        '本周工作安排：周一上午部门例会，下午整理上月数据报告；周二与客户对接项目需求，准备演示材料；周三团队内部培训，分享新技术方案；周四完成项目阶段性总结文档，提交审核；周五上午处理遗留问题，下午自由安排。注意事项：记得提前预约周三的会议室，培训材料周二晚上前发给大家审阅。',
    ],
    'diary': [
        '三月十五号，星期五，天气晴。今天早上醒来的时候窗外阳光已经很好了，照在床上一片温暖。起床后做了简单的早餐，一杯牛奶加两片吐司。出门的时候温度刚刚好，穿了一件薄外套就够了。上班路上听了一期播客，讲的是关于时间管理的方法，有些观点挺有启发的。中午休息的时候在楼下花园走了走，樱花开了，很漂亮。',
        '今天做得很成功的一道菜是红烧牛腩。按照网上的教程一步步来，先把牛腩焯水去腥，然后用冰糖炒色，加入各种调料慢炖两个小时。最后收汁的时候加了点土豆和胡萝卜，味道特别好。下次可以再做，可以试试加点八角和桂皮，味道应该会更香。做饭这种事情真的是熟能生巧，多练几次就好了。',
    ],
    'message': [
        '您好，关于您咨询的产品问题，这边给您回复一下：首先，您反馈的使用异常我们已经收到，技术团队正在排查中，预计两个工作日内给出解决方案。其次，关于您提到的功能建议，我们已经记录并转交给产品团队评估。最后，您的会员权益已延期一个月作为补偿，请查收确认。如有其他问题欢迎随时联系我们客服团队。',
        '通知：各位同事，本月团建活动定于下周六举行。活动地点选在了郊外的拓展基地，包含户外拓展和烧烤环节。请大家穿着运动服装，做好防晒准备。当天早上八点在集合出发，预计下午五点返回。如有特殊情况不能参加的，请提前向部门负责人报备。详细安排见附件文档，请大家提前查阅做好准备。',
    ],
}

# ============================================================
# 加密模块
# ============================================================

def derive_key(password: str, salt: bytes) -> bytes:
    """PBKDF2-SHA256 密钥派生，返回 32 字节 AES-256 密钥"""
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=PBKDF2_ITERATIONS,
    )
    return kdf.derive(password.encode('utf-8'))


def encrypt_layer(data: bytes, password: str):
    """单层 AES-256-GCM 加密，返回 (密文, salt, iv)"""
    salt = secrets.token_bytes(SALT_LENGTH)
    iv = secrets.token_bytes(IV_LENGTH)
    key = derive_key(password, salt)
    aesgcm = AESGCM(key)
    encrypted = aesgcm.encrypt(iv, data, None)
    return encrypted, salt, iv


def decrypt_layer(encrypted: bytes, password: str, salt: bytes, iv: bytes) -> bytes:
    """单层 AES-256-GCM 解密"""
    key = derive_key(password, salt)
    aesgcm = AESGCM(key)
    return aesgcm.decrypt(iv, encrypted, None)


def double_encrypt(data: bytes, password1: str, password2: str) -> bytes:
    """
    双层加密。
    输出格式: [MAGIC(4)][SALT1(16)][IV1(12)][SALT2(16)][IV2(12)][ENCRYPTED]
    """
    # Layer 1: 用 password1 加密
    layer1_enc, salt1, iv1 = encrypt_layer(data, password1)
    # Layer 2: 用 password2 加密 layer1 的密文
    layer2_enc, salt2, iv2 = encrypt_layer(layer1_enc, password2)

    # 打包
    result = MAGIC_HEADER + salt1 + iv1 + salt2 + iv2 + layer2_enc
    return result


def double_decrypt(packed: bytes, password1: str, password2: str) -> bytes:
    """双层解密"""
    if packed[:4] != MAGIC_HEADER:
        raise ValueError('无效的加密数据格式（魔数头不匹配）')

    offset = 4
    salt1 = packed[offset:offset + SALT_LENGTH]; offset += SALT_LENGTH
    iv1 = packed[offset:offset + IV_LENGTH]; offset += IV_LENGTH
    salt2 = packed[offset:offset + SALT_LENGTH]; offset += SALT_LENGTH
    iv2 = packed[offset:offset + IV_LENGTH]; offset += IV_LENGTH
    encrypted_data = packed[offset:]

    # 先解密 layer 2 (password2)
    layer1_dec = decrypt_layer(encrypted_data, password2, salt2, iv2)
    # 再解密 layer 1 (password1)
    original = decrypt_layer(layer1_dec, password1, salt1, iv1)
    return original


def generate_password(length: int = 16) -> str:
    """生成随机密码"""
    chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%'
    return ''.join(secrets.choice(chars) for _ in range(length))


# ============================================================
# 自包含格式 (密码嵌入密文)
# ============================================================

def self_contained_encrypt(data: bytes, is_file: bool = False) -> bytes:
    """自包含加密：自动生成密码并嵌入密文"""
    pw1 = generate_password()
    pw2 = generate_password()
    encrypted = double_encrypt(data, pw1, pw2)

    pw1_bytes = pw1.encode('utf-8')
    pw2_bytes = pw2.encode('utf-8')

    flag = 0x02 if is_file else 0x01
    result = bytes([flag, len(pw1_bytes)]) + pw1_bytes + bytes([len(pw2_bytes)]) + pw2_bytes + encrypted
    return result


def self_contained_encrypt_file(file_data: bytes, file_name: str, file_type: str) -> bytes:
    """自包含加密文件：自动生成密码，添加元数据"""
    pw1 = generate_password()
    pw2 = generate_password()
    
    # 添加文件元数据
    meta = json.dumps({'name': file_name, 'type': file_type, 'size': len(file_data)},
                      ensure_ascii=False).encode('utf-8')
    meta_len = struct.pack('<I', len(meta))
    packed = meta_len + meta + file_data
    
    encrypted = double_encrypt(packed, pw1, pw2)

    pw1_bytes = pw1.encode('utf-8')
    pw2_bytes = pw2.encode('utf-8')

    flag = 0x02
    result = bytes([flag, len(pw1_bytes)]) + pw1_bytes + bytes([len(pw2_bytes)]) + pw2_bytes + encrypted
    return result


def self_contained_decrypt(packed: bytes):
    """自包含解密：提取密码并解密，返回 (flag, decrypted_data)"""
    if len(packed) < 4:
        raise ValueError('数据太短')

    flag = packed[0]
    if flag not in (0x01, 0x02):
        raise ValueError('非自包含格式，需要手动输入密码')

    offset = 1
    pw1_len = packed[offset]; offset += 1
    pw1 = packed[offset:offset + pw1_len].decode('utf-8'); offset += pw1_len
    pw2_len = packed[offset]; offset += 1
    pw2 = packed[offset:offset + pw2_len].decode('utf-8'); offset += pw2_len
    encrypted = packed[offset:]

    decrypted = double_decrypt(encrypted, pw1, pw2)
    return flag, decrypted


# ============================================================
# 文件加密/解密
# ============================================================

def encrypt_file_data(file_data: bytes, file_name: str, file_type: str,
                      password1: str, password2: str) -> bytes:
    """加密文件，附带元数据"""
    meta = json.dumps({'name': file_name, 'type': file_type, 'size': len(file_data)},
                      ensure_ascii=False).encode('utf-8')
    meta_len = struct.pack('<I', len(meta))
    packed = meta_len + meta + file_data
    return double_encrypt(packed, password1, password2)


def decrypt_file_data(packed: bytes, password1: str, password2: str):
    """解密文件，提取元数据，返回 (file_data, file_name, file_type)"""
    decrypted = double_decrypt(packed, password1, password2)
    meta_len = struct.unpack('<I', decrypted[:4])[0]
    meta = json.loads(decrypted[4:4 + meta_len].decode('utf-8'))
    file_data = decrypted[4 + meta_len:]
    return file_data, meta['name'], meta['type']


# ============================================================
# 零宽字符隐写模块
# ============================================================

def byte_to_zw(byte_val: int) -> str:
    """单字节转 4 个零宽字符"""
    return (ZW_CHARS[(byte_val >> 6) & 0x03] +
            ZW_CHARS[(byte_val >> 4) & 0x03] +
            ZW_CHARS[(byte_val >> 2) & 0x03] +
            ZW_CHARS[byte_val & 0x03])


def bytes_to_zero_width(data: bytes) -> str:
    """字节数据转零宽字符序列（含头部）"""
    result = ''.join(ZW_CHARS[i] for i in ZW_MAGIC_INDICES)
    # 长度编码 (4 字节, 大端)
    length = len(data)
    for shift in [24, 16, 8, 0]:
        result += byte_to_zw((length >> shift) & 0xFF)
    # 数据编码
    for b in data:
        result += byte_to_zw(b)
    return result


def zero_width_to_bytes(text: str):
    """从零宽字符文本中提取字节数据"""
    # 收集所有零宽字符的索引
    zw_seq = []
    for char in text:
        if char in ZW_CHARS:
            zw_seq.append(ZW_CHARS.index(char))

    if len(zw_seq) < 20:
        return None

    # 查找魔数标记 [0, 3, 2, 1]
    magic_pos = -1
    for i in range(len(zw_seq) - 3):
        if (zw_seq[i] == 0 and zw_seq[i+1] == 3 and
                zw_seq[i+2] == 2 and zw_seq[i+3] == 1):
            magic_pos = i
            break
    if magic_pos == -1:
        return None

    # 读取长度 (4 字节 = 16 个 ZW 字符)
    len_start = magic_pos + 4
    if len(zw_seq) < len_start + 16:
        return None

    data_len = 0
    for i in range(4):
        byte_val = 0
        for j in range(4):
            byte_val = (byte_val << 2) | zw_seq[len_start + i * 4 + j]
        data_len = (data_len << 8) | byte_val

    if data_len <= 0 or data_len > 50 * 1024 * 1024:
        return None

    payload_start = len_start + 16
    if len(zw_seq) < payload_start + data_len * 4:
        return None

    # 解码数据
    result = bytearray(data_len)
    for i in range(data_len):
        byte_val = 0
        for j in range(4):
            byte_val = (byte_val << 2) | zw_seq[payload_start + i * 4 + j]
        result[i] = byte_val

    return bytes(result)


def encode_disguised(data: bytes, scene: str = 'chat') -> str:
    """将加密数据编码为伪装文本"""
    import random
    zw_data = bytes_to_zero_width(data)
    templates = SCENES.get(scene, SCENES['chat'])
    cover_text = random.choice(templates)

    # 在中间标点符号后插入零宽字符
    chars = list(cover_text)
    punct = set('，。！？；、：')
    mid_start = len(chars) // 5
    mid_end = len(chars) * 7 // 10

    insert_idx = -1
    for i in range(mid_start, mid_end):
        if chars[i] in punct:
            insert_idx = i + 1
            break

    if insert_idx == -1:
        insert_idx = len(chars) // 2

    result = ''.join(chars[:insert_idx]) + zw_data + ''.join(chars[insert_idx:])
    return result


def decode_disguised(text: str):
    """从伪装文本中提取加密数据"""
    return zero_width_to_bytes(text)


def has_hidden_data(text: str) -> bool:
    """检测文本中是否包含隐藏数据"""
    return zero_width_to_bytes(text) is not None


# ============================================================
# CLI 命令
# ============================================================

def cmd_encrypt_text(args):
    """加密文本消息"""
    text = args.input
    scene = args.scene

    print(f"[*] 加密文本 ({len(text)} 字符), 场景: {scene}")

    # 自包含加密
    data = text.encode('utf-8')
    encrypted = self_contained_encrypt(data, is_file=False)

    # 伪装
    disguised = encode_disguised(encrypted, scene)
    print(f"[*] 零宽字符数: {sum(1 for c in disguised if c in ZW_CHARS)}")

    if args.output:
        with open(args.output, 'w', encoding='utf-8') as f:
            f.write(disguised)
        print(f"[+] 已保存到: {args.output}")
    else:
        print("\n--- 伪装文本 (复制以下内容) ---")
        print(disguised)
        print("--- 结束 ---")

    print("[+] 加密完成! 接收方粘贴即可自动解密。")


def cmd_encrypt_file(args):
    """加密文件"""
    file_path = args.input
    scene = args.scene

    if not os.path.isfile(file_path):
        print(f"错误: 文件不存在: {file_path}")
        sys.exit(1)

    file_name = os.path.basename(file_path)
    file_size = os.path.getsize(file_path)
    # 猜测 MIME 类型
    ext = os.path.splitext(file_path)[1].lower()
    mime_map = {
        '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
        '.gif': 'image/gif', '.webp': 'image/webp', '.pdf': 'application/pdf',
        '.txt': 'text/plain', '.json': 'application/json',
        '.mp3': 'audio/mpeg', '.mp4': 'video/mp4', '.zip': 'application/zip',
    }
    file_type = mime_map.get(ext, 'application/octet-stream')

    print(f"[*] 加密文件: {file_name} ({file_size:,} 字节, {file_type})")

    with open(file_path, 'rb') as f:
        file_data = f.read()

    # 打包元数据 + 文件数据
    meta = json.dumps({'name': file_name, 'type': file_type, 'size': file_size},
                      ensure_ascii=False).encode('utf-8')
    meta_len = struct.pack('<I', len(meta))
    packed = meta_len + meta + file_data

    # 自包含加密 (标记为文件)
    encrypted = self_contained_encrypt(packed, is_file=True)

    # 伪装
    disguised = encode_disguised(encrypted, scene)
    zw_count = sum(1 for c in disguised if c in ZW_CHARS)
    print(f"[*] 零宽字符数: {zw_count:,}")

    if args.output:
        with open(args.output, 'w', encoding='utf-8') as f:
            f.write(disguised)
        print(f"[+] 已保存到: {args.output}")
    else:
        out_name = os.path.splitext(file_name)[0] + '_encrypted.txt'
        with open(out_name, 'w', encoding='utf-8') as f:
            f.write(disguised)
        print(f"[+] 已保存到: {out_name}")

    print("[+] 加密完成!")


def cmd_decrypt_text(args):
    """解密文本"""
    input_path = args.input

    # 读取输入 (可以是文件或直接从参数)
    if os.path.isfile(input_path):
        with open(input_path, 'r', encoding='utf-8') as f:
            text = f.read()
        print(f"[*] 从文件读取: {input_path} ({len(text):,} 字符)")
    else:
        text = input_path
        print(f"[*] 解密文本 ({len(text):,} 字符)")

    # 提取隐藏数据
    encrypted = decode_disguised(text)
    if encrypted is None:
        print("错误: 未检测到隐藏数据。请确认内容完整。")
        sys.exit(1)

    print(f"[*] 提取到 {len(encrypted):,} 字节加密数据")

    # 自包含解密
    try:
        flag, decrypted = self_contained_decrypt(encrypted)
    except ValueError as e:
        print(f"错误: {e}")
        sys.exit(1)

    if flag == 0x01:
        # 文本
        result = decrypted.decode('utf-8')
        if args.output:
            with open(args.output, 'w', encoding='utf-8') as f:
                f.write(result)
            print(f"[+] 已保存到: {args.output}")
        else:
            print("\n--- 解密结果 ---")
            print(result)
            print("--- 结束 ---")
    elif flag == 0x02:
        # 文件 - 需要手动解析元数据
        # 自包含文件加密时，data 就是原始文件数据
        # 但我们需要知道文件名和类型... 
        # 实际上自包含模式没有存元数据，需要改进
        # 这里先直接保存为二进制
        out_name = args.output or 'decrypted_file'
        with open(out_name, 'wb') as f:
            f.write(decrypted)
        print(f"[+] 文件已保存到: {out_name} ({len(decrypted):,} 字节)")
    else:
        print("错误: 未知的数据类型")
        sys.exit(1)

    print("[+] 解密完成!")


def cmd_decrypt_file(args):
    """解密文件（从伪装文本中提取并还原文件）"""
    input_path = args.input
    output_path = args.output

    if not os.path.isfile(input_path):
        print(f"错误: 文件不存在: {input_path}")
        sys.exit(1)

    with open(input_path, 'r', encoding='utf-8') as f:
        text = f.read()

    print(f"[*] 从文件读取: {input_path} ({len(text):,} 字符)")

    # 提取隐藏数据
    encrypted = decode_disguised(text)
    if encrypted is None:
        print("错误: 未检测到隐藏数据。请确认内容完整。")
        sys.exit(1)

    print(f"[*] 提取到 {len(encrypted):,} 字节加密数据")

    # 自包含解密
    try:
        flag, decrypted = self_contained_decrypt(encrypted)
    except ValueError as e:
        print(f"错误: {e}")
        sys.exit(1)

    if flag == 0x01:
        # 文本类型 - 提示用 decrypt-text
        print("提示: 此内容是文本加密，建议使用 decrypt-text 命令解密")
        result = decrypted.decode('utf-8')
        out_path = output_path or 'decrypted_text.txt'
        with open(out_path, 'w', encoding='utf-8') as f:
            f.write(result)
        print(f"[+] 文本已保存到: {out_path}")
    elif flag == 0x02:
        # 文件类型 - 提取元数据
        meta_len = struct.unpack('<I', decrypted[:4])[0]
        meta = json.loads(decrypted[4:4 + meta_len].decode('utf-8'))
        file_data = decrypted[4 + meta_len:]
        file_name = meta['name']
        out_path = output_path or file_name
        with open(out_path, 'wb') as f:
            f.write(file_data)
        print(f"[+] 文件已还原: {out_path} ({len(file_data):,} 字节)")
        print(f"    原始文件名: {file_name}")
        print(f"    原始类型: {meta['type']}")
    else:
        print("错误: 未知的数据类型")
        sys.exit(1)

    print("[+] 解密完成!")


def cmd_detect(args):
    """检测文本中是否包含隐藏数据"""
    input_path = args.input

    if os.path.isfile(input_path):
        with open(input_path, 'r', encoding='utf-8') as f:
            text = f.read()
    else:
        text = input_path

    if has_hidden_data(text):
        data = zero_width_to_bytes(text)
        print(f"[+] 检测到隐藏数据! 大小: {len(data):,} 字节")
    else:
        print("[-] 未检测到隐藏数据")


def interactive_mode():
    """交互模式 - 双击运行时进入"""
    print()
    print("=" * 50)
    print("  SecureVeil - 端到端加密伪装传输工具")
    print("=" * 50)
    print()
    print("  1. 加密文本消息")
    print("  2. 加密文件")
    print("  3. 解密文本")
    print("  4. 解密文件")
    print("  5. 检测隐藏数据")
    print("  0. 退出")
    print()

    choice = input("请选择操作 [0-5]: ").strip()

    if choice == '1':
        text = input("请输入要加密的文本: ").strip()
        if not text:
            print("错误: 文本不能为空")
            return
        scene = input("伪装场景 (chat/note/diary/message) [默认 chat]: ").strip() or 'chat'
        result = encrypt_text(text, scene)
        print("\n" + "=" * 50)
        print("加密结果 (已复制到剪贴板):")
        print("=" * 50)
        print(result)
        try:
            pyperclip.copy(result)
            print("\n[+] 已复制到剪贴板")
        except Exception:
            pass

    elif choice == '2':
        filepath = input("请输入文件路径: ").strip()
        if not filepath or not os.path.isfile(filepath):
            print(f"错误: 文件不存在 - {filepath}")
            return
        scene = input("伪装场景 (chat/note/diary/message) [默认 chat]: ").strip() or 'chat'
        output = input("输出文件路径 [默认: 原文件名_enc.txt]: ").strip()
        if not output:
            base = os.path.splitext(os.path.basename(filepath))[0]
            output = f"{base}_enc.txt"
        # 文件加密逻辑
        file_name = os.path.basename(filepath)
        file_size = os.path.getsize(filepath)
        ext = os.path.splitext(filepath)[1].lower()
        mime_map = {
            '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
            '.gif': 'image/gif', '.webp': 'image/webp', '.pdf': 'application/pdf',
            '.txt': 'text/plain', '.json': 'application/json',
            '.mp3': 'audio/mpeg', '.mp4': 'video/mp4', '.zip': 'application/zip',
        }
        file_type = mime_map.get(ext, 'application/octet-stream')
        with open(filepath, 'rb') as f:
            file_data = f.read()
        meta = json.dumps({'name': file_name, 'type': file_type, 'size': file_size},
                          ensure_ascii=False).encode('utf-8')
        meta_len = struct.pack('<I', len(meta))
        packed = meta_len + meta + file_data
        encrypted = self_contained_encrypt(packed, is_file=True)
        disguised = encode_disguised(encrypted, scene)
        if not output:
            base = os.path.splitext(os.path.basename(filepath))[0]
            output = f"{base}_enc.txt"
        with open(output, 'w', encoding='utf-8') as f:
            f.write(disguised)
        print(f"\n[+] 加密完成! 输出: {output}")

    elif choice == '3':
        text = input("请粘贴伪装文本 (粘贴后按回车): ").strip()
        if not text:
            # 尝试从文件读取
            filepath = input("或输入文件路径: ").strip()
            if filepath and os.path.isfile(filepath):
                with open(filepath, 'r', encoding='utf-8') as f:
                    text = f.read()
            else:
                print("错误: 未提供有效输入")
                return
        result = decrypt_text(text)
        print("\n" + "=" * 50)
        print("解密结果:")
        print("=" * 50)
        print(result)

    elif choice == '4':
        filepath = input("请输入加密文件路径: ").strip()
        if not filepath or not os.path.isfile(filepath):
            print(f"错误: 文件不存在 - {filepath}")
            return
        output_path = input("输出文件路径 [默认: 自动命名]: ").strip() or None
        
        with open(filepath, 'r', encoding='utf-8') as f:
            text = f.read()
        
        encrypted = decode_disguised(text)
        if encrypted is None:
            print("错误: 未检测到隐藏数据")
            return
        
        flag, decrypted = self_contained_decrypt(encrypted)
        if flag != 0x02:
            print("错误: 此内容不是文件加密")
            return
        
        meta_len = struct.unpack('<I', decrypted[:4])[0]
        metadata = json.loads(decrypted[4:4+meta_len].decode('utf-8'))
        file_data = decrypted[4+meta_len:]
        
        out_path = output_path or metadata['name']
        with open(out_path, 'wb') as f:
            f.write(file_data)
        print(f"\n[+] 解密完成! 文件: {out_path} (原始: {metadata['name']}, {metadata['size']:,} 字节)")

    elif choice == '5':
        text = input("请粘贴要检测的文本 (粘贴后按回车): ").strip()
        if not text:
            filepath = input("或输入文件路径: ").strip()
            if filepath and os.path.isfile(filepath):
                with open(filepath, 'r', encoding='utf-8') as f:
                    text = f.read()
            else:
                print("错误: 未提供有效输入")
                return
        if has_hidden_data(text):
            data = zero_width_to_bytes(text)
            print(f"\n[+] 检测到隐藏数据! 大小: {len(data):,} 字节")
        else:
            print("\n[-] 未检测到隐藏数据")

    elif choice == '0':
        return

    else:
        print("无效选择")


# ============================================================
# GUI 模式 (tkinter)
# ============================================================

def run_gui():
    """启动图形界面"""
    try:
        import tkinter as tk
        from tkinter import ttk, filedialog, messagebox, scrolledtext
    except ImportError:
        print("错误: tkinter 不可用，请使用命令行模式")
        return False

    class SecureVeilGUI:
        def __init__(self, root):
            self.root = root
            self.root.title("SecureVeil - 端到端加密伪装传输工具")
            self.root.geometry("700x600")
            self.root.minsize(600, 500)

            # 设置样式
            style = ttk.Style()
            style.theme_use('clam')

            # 创建主界面
            self.create_widgets()

        def create_widgets(self):
            # 标题
            title_frame = ttk.Frame(self.root)
            title_frame.pack(fill=tk.X, padx=10, pady=5)
            ttk.Label(title_frame, text="🔒 SecureVeil", font=('', 16, 'bold')).pack(side=tk.LEFT)
            ttk.Label(title_frame, text="端到端加密伪装传输工具", foreground='gray').pack(side=tk.LEFT, padx=10)

            # 标签页
            self.notebook = ttk.Notebook(self.root)
            self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

            # 加密页
            self.encrypt_frame = ttk.Frame(self.notebook)
            self.notebook.add(self.encrypt_frame, text="  加密  ")
            self.create_encrypt_page()

            # 解密页
            self.decrypt_frame = ttk.Frame(self.notebook)
            self.notebook.add(self.decrypt_frame, text="  解密  ")
            self.create_decrypt_page()

        def create_encrypt_page(self):
            frame = self.encrypt_frame

            # 输入类型
            type_frame = ttk.LabelFrame(frame, text="输入类型")
            type_frame.pack(fill=tk.X, padx=10, pady=5)
            self.encrypt_type = tk.StringVar(value='text')
            ttk.Radiobutton(type_frame, text="文本", variable=self.encrypt_type,
                          value='text', command=self.toggle_encrypt_input).pack(side=tk.LEFT, padx=10, pady=5)
            ttk.Radiobutton(type_frame, text="文件", variable=self.encrypt_type,
                          value='file', command=self.toggle_encrypt_input).pack(side=tk.LEFT, padx=10, pady=5)

            # 文本输入
            self.text_input_frame = ttk.Frame(frame)
            self.text_input_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
            ttk.Label(self.text_input_frame, text="输入要加密的文本:").pack(anchor=tk.W)
            self.encrypt_text = scrolledtext.ScrolledText(self.text_input_frame, height=6, wrap=tk.WORD)
            self.encrypt_text.pack(fill=tk.BOTH, expand=True, pady=5)

            # 文件输入
            self.file_input_frame = ttk.Frame(frame)
            self.selected_file = tk.StringVar()
            file_select = ttk.Frame(self.file_input_frame)
            file_select.pack(fill=tk.X, pady=5)
            ttk.Label(file_select, text="选择文件:").pack(side=tk.LEFT)
            ttk.Entry(file_select, textvariable=self.selected_file, state='readonly').pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
            ttk.Button(file_select, text="浏览...", command=self.browse_encrypt_file).pack(side=tk.RIGHT)
            # 文件输入初始不显示

            # 设置
            settings_frame = ttk.LabelFrame(frame, text="设置")
            settings_frame.pack(fill=tk.X, padx=10, pady=5)

            # 伪装场景
            scene_frame = ttk.Frame(settings_frame)
            scene_frame.pack(fill=tk.X, padx=5, pady=5)
            ttk.Label(scene_frame, text="伪装场景:").pack(side=tk.LEFT)
            self.encrypt_scene = tk.StringVar(value='chat')
            scenes = [('日常聊天', 'chat'), ('备忘清单', 'note'), ('生活日记', 'diary'), ('通知消息', 'message')]
            for text, value in scenes:
                ttk.Radiobutton(scene_frame, text=text, variable=self.encrypt_scene, value=value).pack(side=tk.LEFT, padx=5)

            # 密码模式
            pw_mode_frame = ttk.Frame(settings_frame)
            pw_mode_frame.pack(fill=tk.X, padx=5, pady=5)
            ttk.Label(pw_mode_frame, text="密码模式:").pack(side=tk.LEFT)
            self.pw_mode = tk.StringVar(value='auto')
            ttk.Radiobutton(pw_mode_frame, text="自动密码", variable=self.pw_mode,
                          value='auto', command=self.toggle_pw_mode).pack(side=tk.LEFT, padx=5)
            ttk.Radiobutton(pw_mode_frame, text="手动密码", variable=self.pw_mode,
                          value='manual', command=self.toggle_pw_mode).pack(side=tk.LEFT, padx=5)

            # 手动密码输入框（初始隐藏）
            self.manual_pw_frame = ttk.Frame(settings_frame)
            pw1_frame = ttk.Frame(self.manual_pw_frame)
            pw1_frame.pack(fill=tk.X, padx=5, pady=2)
            ttk.Label(pw1_frame, text="密码1:").pack(side=tk.LEFT)
            self.manual_pw1 = tk.StringVar()
            self.manual_pw1_entry = ttk.Entry(pw1_frame, textvariable=self.manual_pw1, show='*', width=30)
            self.manual_pw1_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
            ttk.Button(pw1_frame, text="显示", width=4,
                      command=lambda: self.toggle_pw_visibility(self.manual_pw1_entry)).pack(side=tk.LEFT, padx=2)

            pw2_frame = ttk.Frame(self.manual_pw_frame)
            pw2_frame.pack(fill=tk.X, padx=5, pady=2)
            ttk.Label(pw2_frame, text="密码2:").pack(side=tk.LEFT)
            self.manual_pw2 = tk.StringVar()
            self.manual_pw2_entry = ttk.Entry(pw2_frame, textvariable=self.manual_pw2, show='*', width=30)
            self.manual_pw2_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
            ttk.Button(pw2_frame, text="显示", width=4,
                      command=lambda: self.toggle_pw_visibility(self.manual_pw2_entry)).pack(side=tk.LEFT, padx=2)

            # 按钮
            btn_frame = ttk.Frame(frame)
            btn_frame.pack(fill=tk.X, padx=10, pady=5)
            ttk.Button(btn_frame, text="🔒 加密", command=self.do_encrypt).pack(side=tk.LEFT, padx=5)
            ttk.Button(btn_frame, text="复制结果", command=self.copy_encrypt_result).pack(side=tk.LEFT, padx=5)
            ttk.Button(btn_frame, text="保存结果", command=self.save_encrypt_result).pack(side=tk.LEFT, padx=5)
            ttk.Button(btn_frame, text="清空", command=self.clear_encrypt).pack(side=tk.RIGHT, padx=5)

            # 结果
            result_frame = ttk.LabelFrame(frame, text="加密结果")
            result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
            self.encrypt_result = scrolledtext.ScrolledText(result_frame, height=6, wrap=tk.WORD)
            self.encrypt_result.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        def create_decrypt_page(self):
            frame = self.decrypt_frame

            # 输入
            input_frame = ttk.LabelFrame(frame, text="粘贴伪装文本或选择文件")
            input_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

            btn_frame = ttk.Frame(input_frame)
            btn_frame.pack(fill=tk.X, padx=5, pady=5)
            ttk.Button(btn_frame, text="从剪贴板粘贴", command=self.paste_decrypt_input).pack(side=tk.LEFT, padx=5)
            ttk.Button(btn_frame, text="选择文件", command=self.browse_decrypt_file).pack(side=tk.LEFT, padx=5)

            self.decrypt_input = scrolledtext.ScrolledText(input_frame, height=8, wrap=tk.WORD)
            self.decrypt_input.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

            # 按钮
            action_frame = ttk.Frame(frame)
            action_frame.pack(fill=tk.X, padx=10, pady=5)
            ttk.Button(action_frame, text="🔓 解密", command=self.do_decrypt).pack(side=tk.LEFT, padx=5)
            ttk.Button(action_frame, text="保存结果", command=self.save_decrypt_result).pack(side=tk.LEFT, padx=5)
            ttk.Button(action_frame, text="清空", command=self.clear_decrypt).pack(side=tk.RIGHT, padx=5)

            # 结果
            result_frame = ttk.LabelFrame(frame, text="解密结果")
            result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
            self.decrypt_result = scrolledtext.ScrolledText(result_frame, height=6, wrap=tk.WORD)
            self.decrypt_result.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        def toggle_encrypt_input(self):
            if self.encrypt_type.get() == 'text':
                self.file_input_frame.pack_forget()
                self.text_input_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
            else:
                self.text_input_frame.pack_forget()
                self.file_input_frame.pack(fill=tk.X, padx=10, pady=5)

        def toggle_pw_mode(self):
            if self.pw_mode.get() == 'manual':
                self.manual_pw_frame.pack(fill=tk.X, padx=5, pady=5)
            else:
                self.manual_pw_frame.pack_forget()

        def toggle_pw_visibility(self, entry_widget):
            current_show = entry_widget.cget('show')
            entry_widget.configure(show='' if current_show == '*' else '*')

        def browse_encrypt_file(self):
            filename = filedialog.askopenfilename(title="选择要加密的文件")
            if filename:
                self.selected_file.set(filename)

        def browse_decrypt_file(self):
            filename = filedialog.askopenfilename(title="选择伪装文本文件",
                filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")])
            if filename:
                try:
                    with open(filename, 'r', encoding='utf-8') as f:
                        content = f.read()
                    self.decrypt_input.delete('1.0', tk.END)
                    self.decrypt_input.insert('1.0', content)
                except Exception as e:
                    messagebox.showerror("错误", f"读取文件失败: {e}")

        def paste_decrypt_input(self):
            try:
                content = self.root.clipboard_get()
                self.decrypt_input.delete('1.0', tk.END)
                self.decrypt_input.insert('1.0', content)
            except tk.TclError:
                messagebox.showwarning("警告", "剪贴板为空或没有文本内容")

        def do_encrypt(self):
            try:
                scene = self.encrypt_scene.get()
                pw_mode = self.pw_mode.get()
                
                if pw_mode == 'manual':
                    pw1 = self.manual_pw1.get().strip()
                    pw2 = self.manual_pw2.get().strip()
                    if not pw1 or not pw2:
                        messagebox.showwarning("警告", "请输入两个密码")
                        return
                    if pw1 == pw2:
                        messagebox.showwarning("警告", "两个密码不能相同（用于双层加密）")
                        return
                
                if self.encrypt_type.get() == 'text':
                    text = self.encrypt_text.get('1.0', tk.END).strip()
                    if not text:
                        messagebox.showwarning("警告", "请输入要加密的文本")
                        return
                    data = text.encode('utf-8')
                    if pw_mode == 'manual':
                        encrypted = double_encrypt(data, pw1, pw2)
                        # 手动密码模式：flag=0x00 表示非自包含
                        result_data = bytes([0x00]) + encrypted
                    else:
                        result_data = self_contained_encrypt(data, is_file=False)
                    result = encode_disguised(result_data, scene)
                else:
                    filepath = self.selected_file.get()
                    if not filepath:
                        messagebox.showwarning("警告", "请选择要加密的文件")
                        return
                    with open(filepath, 'rb') as f:
                        data = f.read()
                    import os
                    import mimetypes
                    file_name = os.path.basename(filepath)
                    file_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
                    if pw_mode == 'manual':
                        packed = encrypt_file_data(data, file_name, file_type, pw1, pw2)
                        # 手动密码模式：flag=0x00 表示非自包含
                        result_data = bytes([0x00]) + packed
                    else:
                        result_data = self_contained_encrypt_file(data, file_name, file_type)
                    result = encode_disguised(result_data, scene)

                self.encrypt_result.delete('1.0', tk.END)
                self.encrypt_result.insert('1.0', result)
                
                if pw_mode == 'manual':
                    msg = f"加密完成！\n\n请牢记密码（解密时需要输入）：\n密码1: {pw1}\n密码2: {pw2}"
                    messagebox.showinfo("成功", msg)
                else:
                    messagebox.showinfo("成功", "加密完成！")
            except Exception as e:
                messagebox.showerror("错误", f"加密失败: {e}")

        def do_decrypt(self):
            try:
                text = self.decrypt_input.get('1.0', tk.END).strip()
                if not text:
                    messagebox.showwarning("警告", "请粘贴伪装文本")
                    return

                if not has_hidden_data(text):
                    messagebox.showwarning("警告", "未检测到隐藏数据")
                    return

                encrypted = decode_disguised(text)
                
                # 检查是否为手动密码模式（flag=0x00）
                flag = encrypted[0]
                if flag == 0x00:
                    # 手动密码模式，需要用户输入密码
                    pw_dialog = tk.Toplevel(self.root)
                    pw_dialog.title("输入密码")
                    pw_dialog.geometry("300x150")
                    pw_dialog.transient(self.root)
                    pw_dialog.grab_set()
                    
                    ttk.Label(pw_dialog, text="请输入密码1:").pack(padx=10, pady=5, anchor=tk.W)
                    pw1_var = tk.StringVar()
                    pw1_entry = ttk.Entry(pw_dialog, textvariable=pw1_var, show='*', width=30)
                    pw1_entry.pack(padx=10, pady=2)
                    
                    ttk.Label(pw_dialog, text="请输入密码2:").pack(padx=10, pady=5, anchor=tk.W)
                    pw2_var = tk.StringVar()
                    pw2_entry = ttk.Entry(pw_dialog, textvariable=pw2_var, show='*', width=30)
                    pw2_entry.pack(padx=10, pady=2)
                    
                    result_data = [None]
                    
                    def on_ok():
                        pw1 = pw1_var.get().strip()
                        pw2 = pw2_var.get().strip()
                        if not pw1 or not pw2:
                            messagebox.showwarning("警告", "请输入两个密码", parent=pw_dialog)
                            return
                        try:
                            encrypted_data = encrypted[1:]  # 去掉 flag 字节
                            decrypted = double_decrypt(encrypted_data, pw1, pw2)
                            # 判断是文本还是文件
                            try:
                                # 尝试解析为文件元数据
                                meta_len = struct.unpack('<I', decrypted[:4])[0]
                                if meta_len < 1000:  # 合理的元数据长度
                                    meta = json.loads(decrypted[4:4 + meta_len].decode('utf-8'))
                                    file_data = decrypted[4 + meta_len:]
                                    result_data[0] = (0x02, meta['name'], meta['type'], file_data)
                                else:
                                    result_data[0] = (0x01, decrypted.decode('utf-8'))
                            except:
                                # 不是文件格式，尝试作为文本
                                result_data[0] = (0x01, decrypted.decode('utf-8'))
                            pw_dialog.destroy()
                        except Exception as e:
                            messagebox.showerror("错误", f"解密失败: {e}", parent=pw_dialog)
                    
                    btn_frame = ttk.Frame(pw_dialog)
                    btn_frame.pack(pady=10)
                    ttk.Button(btn_frame, text="确定", command=on_ok).pack(side=tk.LEFT, padx=5)
                    ttk.Button(btn_frame, text="取消", command=pw_dialog.destroy).pack(side=tk.LEFT, padx=5)
                    
                    self.root.wait_window(pw_dialog)
                    
                    if result_data[0] is None:
                        return  # 用户取消
                    
                    flag, *rest = result_data[0]
                    if flag == 0x02:
                        name, mime, file_data = rest
                        self.decrypt_result.delete('1.0', tk.END)
                        self.decrypt_result.insert('1.0', f"文件名: {name}\n类型: {mime}\n大小: {len(file_data)} 字节\n\n请使用'保存结果'按钮保存文件")
                        self._pending_file_data = (name, file_data)
                    else:
                        text_content = rest[0]
                        self.decrypt_result.delete('1.0', tk.END)
                        self.decrypt_result.insert('1.0', text_content)
                        self._pending_file_data = None
                else:
                    # 自动密码模式
                    flag, data = self_contained_decrypt(encrypted)
                    self.decrypt_result.delete('1.0', tk.END)
                    if flag == 0x02:  # 文件类型
                        # 解析文件元数据
                        meta_len = struct.unpack('<I', data[:4])[0]
                        meta = json.loads(data[4:4 + meta_len].decode('utf-8'))
                        file_data = data[4 + meta_len:]
                        self.decrypt_result.insert('1.0', f"文件名: {meta['name']}\n类型: {meta['type']}\n大小: {len(file_data)} 字节\n\n请使用'保存结果'按钮保存文件")
                        self._pending_file_data = (meta['name'], file_data)
                    else:
                        self.decrypt_result.insert('1.0', data.decode('utf-8'))
                        self._pending_file_data = None

                messagebox.showinfo("成功", "解密完成！")
            except Exception as e:
                messagebox.showerror("错误", f"解密失败: {e}")

        def copy_encrypt_result(self):
            result = self.encrypt_result.get('1.0', tk.END).strip()
            if result:
                self.root.clipboard_clear()
                self.root.clipboard_append(result)
                messagebox.showinfo("成功", "已复制到剪贴板")
            else:
                messagebox.showwarning("警告", "没有可复制的内容")

        def save_encrypt_result(self):
            result = self.encrypt_result.get('1.0', tk.END).strip()
            if not result:
                messagebox.showwarning("警告", "没有可保存的内容")
                return
            filename = filedialog.asksaveasfilename(defaultextension=".txt",
                filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")])
            if filename:
                with open(filename, 'w', encoding='utf-8') as f:
                    f.write(result)
                messagebox.showinfo("成功", "已保存")

        def save_decrypt_result(self):
            result = self.decrypt_result.get('1.0', tk.END).strip()
            if not result:
                messagebox.showwarning("警告", "没有可保存的内容")
                return

            if hasattr(self, '_pending_file_data') and self._pending_file_data:
                # 保存文件
                name, data = self._pending_file_data
                filename = filedialog.asksaveasfilename(initialfile=name, defaultextension=".*")
                if filename:
                    with open(filename, 'wb') as f:
                        f.write(data)
                    messagebox.showinfo("成功", "文件已保存")
                    self._pending_file_data = None
            else:
                # 保存文本
                filename = filedialog.asksaveasfilename(defaultextension=".txt",
                    filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")])
                if filename:
                    with open(filename, 'w', encoding='utf-8') as f:
                        f.write(result)
                    messagebox.showinfo("成功", "已保存")

        def clear_encrypt(self):
            self.encrypt_text.delete('1.0', tk.END)
            self.encrypt_result.delete('1.0', tk.END)
            self.selected_file.set('')

        def clear_decrypt(self):
            self.decrypt_input.delete('1.0', tk.END)
            self.decrypt_result.delete('1.0', tk.END)
            self._pending_file_data = None

    root = tk.Tk()
    app = SecureVeilGUI(root)
    root.mainloop()
    return True


def main():
    # 无参数时优先启动 GUI
    if len(sys.argv) == 1:
        # 尝试启动 GUI
        try:
            import tkinter as tk
            if run_gui():
                return
        except ImportError:
            pass

        # GUI 不可用，进入命令行交互模式
        try:
            while True:
                interactive_mode()
                print()
                again = input("继续操作? (y/n) [默认 n]: ").strip().lower()
                if again != 'y':
                    break
        except (KeyboardInterrupt, EOFError):
            print("\n已退出")
        except Exception as e:
            print(f"\n错误: {e}")
        finally:
            try:
                print()
                input("按回车键退出...")
            except (EOFError, KeyboardInterrupt):
                pass
        return

    # 检查是否指定 GUI 模式
    if '--gui' in sys.argv:
        sys.argv.remove('--gui')
        if not run_gui():
            print("GUI 启动失败，请确保 tkinter 可用")
        return

    # 有参数时进入 CLI 模式
    parser = argparse.ArgumentParser(
        description='SecureVeil - 端到端加密伪装传输工具 (Python 本地版)',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=textwrap.dedent("""
        示例:
          %(prog)s encrypt-text "秘密消息" --scene chat
          %(prog)s encrypt-file photo.jpg --scene diary -o encrypted.txt
          %(prog)s decrypt-text encrypted.txt -o result.txt
          %(prog)s detect "一段可疑的文本..."
        
        场景 (--scene):
          chat    - 日常聊天风格 (默认)
          note    - 备忘清单风格
          diary   - 生活日记风格
          message - 通知消息风格
        """)
    )

    subparsers = parser.add_subparsers(dest='command', help='可用命令')

    # encrypt-text
    p_enc_text = subparsers.add_parser('encrypt-text', help='加密文本消息')
    p_enc_text.add_argument('input', help='要加密的文本')
    p_enc_text.add_argument('--scene', default='chat', choices=SCENES.keys(),
                            help='伪装场景 (默认: chat)')
    p_enc_text.add_argument('-o', '--output', help='输出文件路径')

    # encrypt-file
    p_enc_file = subparsers.add_parser('encrypt-file', help='加密文件')
    p_enc_file.add_argument('input', help='要加密的文件路径')
    p_enc_file.add_argument('--scene', default='chat', choices=SCENES.keys(),
                            help='伪装场景 (默认: chat)')
    p_enc_file.add_argument('-o', '--output', help='输出文件路径')

    # decrypt-text
    p_dec_text = subparsers.add_parser('decrypt-text', help='解密文本')
    p_dec_text.add_argument('input', help='包含伪装内容的文本或文件路径')
    p_dec_text.add_argument('-o', '--output', help='输出文件路径')

    # decrypt-file
    p_dec_file = subparsers.add_parser('decrypt-file', help='解密文件')
    p_dec_file.add_argument('input', help='包含伪装内容的文本文件路径')
    p_dec_file.add_argument('-o', '--output', help='输出目录 (默认: 当前目录)')

    # detect
    p_detect = subparsers.add_parser('detect', help='检测文本中是否包含隐藏数据')
    p_detect.add_argument('input', help='要检测的文本或文件路径')

    args = parser.parse_args()

    if args.command is None:
        parser.print_help()
        return

    commands = {
        'encrypt-text': cmd_encrypt_text,
        'encrypt-file': cmd_encrypt_file,
        'decrypt-text': cmd_decrypt_text,
        'decrypt-file': cmd_decrypt_file,
        'detect': cmd_detect,
    }

    try:
        commands[args.command](args)
    except Exception as e:
        print(f"错误: {e}")
        input("按回车键退出...")
        sys.exit(1)


if __name__ == '__main__':
    main()
