' ============================================================
'  应用盾 - 易语言加盾客户端源码 (玩家本地版, 参考实现)
' ============================================================
'  运行位置: 玩家自己的电脑上
'  功能: 注册/登录 → 显示线路与流量 → 启动盾(后台运行 agent.exe)
'        → 选择游戏EXE加盾(agent 挂起启动游戏并注入Hook DLL)
'        → 游戏所有TCP连接自动经多IP节点出口, 游戏服务器看不到玩家真实IP
'        同时显示实时流量/在线状态。
'
'  依赖: 精易模块 (网页_访问_对象 / 类_json / 编码_URL编码)
'  配套: agent.exe (Go编译, 与本客户端同目录) + shield_hook64.dll
'
'  搭建步骤:
'    1. 新建 Windows 窗口程序
'    2. 按下面注释建立两个窗口: 启动窗口 / 主窗口, 一个通用对话框组件
'    3. 把本文件代码分别粘贴到对应窗口程序集
'    4. 修改常量"面板地址"为你自己的面板 API 地址
'    5. 静态编译
'
'  ── 启动窗口组件 ────────────────────────────────────────────
'    编辑框_面板地址    (内容: http://你的域名/panel/api.php)
'    编辑框_账号
'    编辑框_密码        (输入方式: 密码输入)
'    按钮_登录
'    按钮_注册
'    标签_提示
'
'  ── 注册窗口组件 ────────────────────────────────────────────
'    编辑框_账号 / 编辑框_密码 / 编辑框_确认密码 / 编辑框_邮箱 / 编辑框_源机IP
'    按钮_确定注册 / 标签_提示
'
'  ── 主窗口组件 ──────────────────────────────────────────────
'    标签_节点        (显示 节点IP:端口)
'    标签_代理账号    (显示 代理账号)
'    标签_到期时间
'    标签_流量        (已用/上限)
'    标签_源机IP      (当前绑定)
'    编辑框_源机IP    (手动填写新源机IP, 留空自动)
'    按钮_刷新信息 / 按钮_绑定源机IP
'    按钮_启动盾 / 按钮_停止盾 / 按钮_加盾
'    标签_状态        (盾运行状态)
'    时钟_状态
'    通用对话框_选择文件
' ============================================================

.版本 2
.程序集 窗口程序集_启动窗口
.程序集变量 集_token, 文本型

.常量 面板地址, "“http://你的域名/panel/api.php”"        ' ← 改成你的面板地址
.常量 代理程序名, "“agent.exe”"                          ' 与易语言客户端同目录

.子程序 __启动窗口_创建完毕
    ' 读取上次保存的配置 (与主程序同目录 shield.ini)
    编辑框_面板地址.内容 ＝ 读配置项 (取运行目录 () ＋ “\shield.ini”, “账号”, “面板地址”, 面板地址)
    编辑框_账号.内容 ＝ 读配置项 (取运行目录 () ＋ “\shield.ini”, “账号”, “账号”, “”)

.子程序 请求面板, 文本型
.参数 动作, 文本型
.参数 提交数据, 文本型, 可空
.参数 带令牌, 逻辑型, 可空
.局部变量 地址, 文本型
.局部变量 附加头, 文本型
.局部变量 返回文本, 文本型
    地址 ＝ 编辑框_面板地址.内容 ＋ “?action=” ＋ 动作
    附加头 ＝ “Content-Type: application/x-www-form-urlencoded”
    .如果真 (带令牌 且 集_token ≠ “”)
        附加头 ＝ 附加头 ＋ #换行符 ＋ “Authorization: Bearer ” ＋ 集_token
    .如果真结束
    ' 注: 网页_访问_对象 参数顺序以你安装的精易模块为准
    返回文本 ＝ 到文本 (网页_访问_对象 (地址, “POST”, 提交数据, , , 附加头, , , , 10))
    返回 (返回文本)

.子程序 _按钮_登录_被单击
.局部变量 json, 类_json
.局部变量 数据, 文本型
    .如果 (编辑框_账号.内容 ＝ “” 或 编辑框_密码.内容 ＝ “”)
        标签_提示.标题 ＝ “请输入账号和密码”
        返回 ()
    .如果结束
    数据 ＝ “username=” ＋ 编码_URL编码 (编辑框_账号.内容, 真, 真) ＋ “&password=” ＋ 编码_URL编码 (编辑框_密码.内容, 真, 真)
    json.解析 (请求面板 (“login”, 数据, 假))
    .如果 (json.取通用属性 (“code”) ＝ “0”)
        集_token ＝ json.取通用属性 (“data.token”)
        写配置项 (取运行目录 () ＋ “\shield.ini”, “账号”, “面板地址”, 编辑框_面板地址.内容)
        写配置项 (取运行目录 () ＋ “\shield.ini”, “账号”, “账号”, 编辑框_账号.内容)
        载入 (窗口_主窗口, , 真)
    .否则
        标签_提示.标题 ＝ json.取通用属性 (“msg”)
    .如果结束

.子程序 _按钮_注册_被单击
    载入 (窗口_注册, , 假)


.版本 2
.程序集 窗口程序集_注册

.子程序 _按钮_确定注册_被单击
.局部变量 json, 类_json
.局部变量 数据, 文本型
.局部变量 源机IP, 文本型
    .如果 (编辑框_密码.内容 ≠ 编辑框_确认密码.内容)
        标签_提示.标题 ＝ “两次密码不一致”
        返回 ()
    .如果结束
    源机IP ＝ 编辑框_源机IP.内容
    .如果真 (源机IP ＝ “”)
        ' 自动获取当前公网IP
        .局部变量 j2, 类_json
        j2.解析 (到文本 (网页_访问_对象 (窗口_启动窗口.编辑框_面板地址.内容 ＋ “?action=my_ip”, “GET”, , , , , , , , 10)))
        源机IP ＝ j2.取通用属性 (“data.ip”)
    .如果真结束
    数据 ＝ “username=” ＋ 编码_URL编码 (编辑框_账号.内容, 真, 真) ＋ “&password=” ＋ 编码_URL编码 (编辑框_密码.内容, 真, 真)
    数据 ＝ 数据 ＋ “&email=” ＋ 编码_URL编码 (编辑框_邮箱.内容, 真, 真) ＋ “&src_ip=” ＋ 编码_URL编码 (源机IP, 真, 真)
    json.解析 (窗口_启动窗口.请求面板 (“register”, 数据, 假))
    .如果 (json.取通用属性 (“code”) ＝ “0”)
        信息框 (“注册成功, 请登录”, 0, , )
        窗口_启动窗口.编辑框_账号.内容 ＝ 编辑框_账号.内容
        本窗口.销毁 ()
    .否则
        标签_提示.标题 ＝ json.取通用属性 (“msg”)
    .如果结束


.版本 2
.程序集 窗口程序集_主窗口
.程序集变量 集_心跳计数, 整数型

.子程序 __主窗口_创建完毕
    子程序_刷新信息 ()
    时钟_状态.时钟周期 ＝ 0

.子程序 子程序_刷新信息
.局部变量 json, 类_json
.局部变量 已用, 双精度小数型
.局部变量 上限, 双精度小数型
.局部变量 到期, 长整数型
    json.解析 (窗口_启动窗口.请求面板 (“config”, “”, 真))
    .如果 (json.取通用属性 (“code”) ＝ “0”)
        标签_节点.标题 ＝ “节点: ” ＋ json.取通用属性 (“data.node.ip”) ＋ “:” ＋ json.取通用属性 (“data.node.port”)
        标签_代理账号.标题 ＝ “代理账号: ” ＋ json.取通用属性 (“data.proxy.user”)
        到期 ＝ 到长整数 (json.取通用属性 (“data.expire_at”))
        .如果 (到期 ＞ 0)
            标签_到期时间.标题 ＝ “到期: ” ＋ 时间_到文本 (到时间 (增减时间 (到时间 (“1970-01-01 08:00:00”), #秒, 到期)), 1)
        .否则
            标签_到期时间.标题 ＝ “到期: 永久”
        .如果结束
        已用 ＝ 到数值 (json.取通用属性 (“data.traffic_used”))
        上限 ＝ 到数值 (json.取通用属性 (“data.traffic_limit”))
        标签_流量.标题 ＝ “流量: ” ＋ 子程序_字节转GB (已用) ＋ “ / ” ＋ 子程序_字节转GB (上限) ＋ “ GB”
        标签_源机IP.标题 ＝ “绑定源机IP: ” ＋ json.取通用属性 (“data.src_ip”)
        编辑框_源机IP.内容 ＝ json.取通用属性 (“data.src_ip”)
    .否则
        标签_节点.标题 ＝ “获取失败: ” ＋ json.取通用属性 (“msg”)
    .如果结束

.子程序 子程序_字节转GB, 文本型
.参数 字节数, 双精度小数型
    返回 (到文本 (四舍五入 (字节数 ÷ 1073741824, 3)))

.子程序 _按钮_刷新信息_被单击
    子程序_刷新信息 ()

.子程序 _按钮_绑定源机IP_被单击
.局部变量 json, 类_json
.局部变量 数据, 文本型
    数据 ＝ “ip=” ＋ 编码_URL编码 (编辑框_源机IP.内容, 真, 真)      ' 留空则自动绑定当前IP
    json.解析 (窗口_启动窗口.请求面板 (“set_src_ip”, 数据, 真))
    .如果 (json.取通用属性 (“code”) ＝ “0”)
        信息框 (“源机IP绑定成功: ” ＋ json.取通用属性 (“data.src_ip”), 0, , )
        子程序_刷新信息 ()
    .否则
        信息框 (json.取通用属性 (“msg”), 0, , )
    .如果结束

.子程序 子程序_写代理配置
    ' 把面板地址/账号/密码/token 写入 agent.json, agent 启动时读取
.局部变量 内容, 文本型
    内容 ＝ “{” ＋ #换行符
    内容 ＝ 内容 ＋ “    ” ＋ #引号 ＋ “panel” ＋ #引号 ＋ “: ” ＋ #引号 ＋ 窗口_启动窗口.编辑框_面板地址.内容 ＋ #引号 ＋ “,” ＋ #换行符
    内容 ＝ 内容 ＋ “    ” ＋ #引号 ＋ “username” ＋ #引号 ＋ “: ” ＋ #引号 ＋ 窗口_启动窗口.编辑框_账号.内容 ＋ #引号 ＋ “,” ＋ #换行符
    内容 ＝ 内容 ＋ “    ” ＋ #引号 ＋ “password” ＋ #引号 ＋ “: ” ＋ #引号 ＋ 窗口_启动窗口.编辑框_密码.内容 ＋ #引号 ＋ “,” ＋ #换行符
    内容 ＝ 内容 ＋ “    ” ＋ #引号 ＋ “token” ＋ #引号 ＋ “: ” ＋ #引号 ＋ 窗口_启动窗口.集_token ＋ #引号 ＋ “,” ＋ #换行符
    内容 ＝ 内容 ＋ “    ” ＋ #引号 ＋ “local_socks” ＋ #引号 ＋ “: 10800,” ＋ #换行符
    内容 ＝ 内容 ＋ “    ” ＋ #引号 ＋ “local_http” ＋ #引号 ＋ “: 10801,” ＋ #换行符
    内容 ＝ 内容 ＋ “    ” ＋ #引号 ＋ “api_addr” ＋ #引号 ＋ “: ” ＋ #引号 ＋ “127.0.0.1:12701” ＋ #引号 ＋ “,” ＋ #换行符
    内容 ＝ 内容 ＋ “    ” ＋ #引号 ＋ “redirect_port” ＋ #引号 ＋ “: 10802,” ＋ #换行符
    内容 ＝ 内容 ＋ “    ” ＋ #引号 ＋ “dll” ＋ #引号 ＋ “: ” ＋ #引号 ＋ “shield_hook64.dll” ＋ #引号 ＋ #换行符
    内容 ＝ 内容 ＋ “}”
    写到文件 (取运行目录 () ＋ “\agent.json”, 到字节集 (内容))

.子程序 _按钮_启动盾_被单击
.局部变量 代理路径, 文本型
    代理路径 ＝ 取运行目录 () ＋ “\” ＋ 代理程序名
    .如果真 (文件是否存在 (代理路径) ＝ 假)
        信息框 (“未找到 agent.exe, 请先编译并放到本目录”, 0, , )
        返回 ()
    .如果真结束
    子程序_写代理配置 ()
    运行 (代理路径, 假, #隐藏窗口)          ' 后台启动本地代理
    时钟_状态.时钟周期 ＝ 2000
    标签_状态.标题 ＝ “盾启动中...”

.子程序 _按钮_停止盾_被单击
    ' 调用 agent 本地接口停止
    网页_访问_对象 (“http://127.0.0.1:12701/stop”, “GET”, , , , , , , , 3)
    时钟_状态.时钟周期 ＝ 0
    标签_状态.标题 ＝ “盾已停止”

.子程序 _按钮_加盾_被单击
    ' 选择游戏EXE, 由 agent 以挂起方式启动并注入 Hook DLL
    ' 注意: 需先启动盾(本地代理); 目标为64位程序时用64位 agent
.局部变量 目标路径, 文本型
.局部变量 代理路径, 文本型
.局部变量 命令, 文本型
    代理路径 ＝ 取运行目录 () ＋ “\” ＋ 代理程序名
    .如果真 (文件是否存在 (代理路径) ＝ 假)
        信息框 (“未找到 agent.exe”, 0, , )
        返回 ()
    .如果真结束
    .如果真 (通用对话框_选择文件.打开 () ＝ 假)
        返回 ()
    .如果真结束
    目标路径 ＝ 通用对话框_选择文件.文件名
    命令 ＝ #引号 ＋ 代理路径 ＋ #引号 ＋ “ -shield ” ＋ #引号 ＋ 目标路径 ＋ #引号
    运行 (命令, 假, #普通激活)              ' 游戏程序本身需要显示界面
    标签_状态.标题 ＝ “已加盾启动: ” ＋ 目标路径

.子程序 _时钟_状态_周期事件
.局部变量 json, 类_json
.局部变量 上传, 双精度小数型
.局部变量 下载, 双精度小数型
    json.解析 (到文本 (网页_访问_对象 (“http://127.0.0.1:12701/status”, “GET”, , , , , , , , 3)))
    .如果 (json.取通用属性 (“running”) ＝ “true”)
        上传 ＝ 到数值 (json.取通用属性 (“up_bytes”))
        下载 ＝ 到数值 (json.取通用属性 (“down_bytes”))
        标签_状态.标题 ＝ “盾运行中 | 节点 ” ＋ json.取通用属性 (“node”) ＋ “ | 上传 ” ＋ 子程序_字节转GB (上传) ＋ “ GB 下载 ” ＋ 子程序_字节转GB (下载) ＋ “ GB”
        ' 每分钟心跳一次
        集_心跳计数 ＝ 集_心跳计数 ＋ 1
        .如果 (集_心跳计数 ≥ 30)
            集_心跳计数 ＝ 0
            窗口_启动窗口.请求面板 (“heartbeat”, “”, 真)
        .如果结束
    .否则
        标签_状态.标题 ＝ “盾未运行”
        时钟_状态.时钟周期 ＝ 0
    .如果结束

.子程序 _主窗口_将被销毁
    时钟_状态.时钟周期 ＝ 0
