# 传奇联机助手（PC 客户端）

把「单机传奇」变成可以和朋友一起联机玩的小型服务器 —— 配套的 Windows 桌面客户端。

- 技术：C# / .NET 8 / WinForms
- 解决方案：`LegendAssistant.sln`（可用 Visual Studio 2022 直接打开）
- 平台地址：写死在 `AppInfo.cs` 中（登录界面不可编辑），应用标识 app_key 同样内置且不在界面展示
- 当前平台地址：`https://zs.oddpp.com`（原 `http://192.168.5.20` 已作废）

---

## 一、功能

| 模块 | 说明 |
| --- | --- |
| 登录 | 网站地址不可编辑、app_key 隐藏；账号密码保存在本机，关闭后重开自动登录 |
| 切换账号 | 「操作 → 退出登录」会清空登录态并弹出登录窗口，**退出后不会自动用原账号登录**，可直接输入其他账号密码换号 |
| 自动更新 | 启动时自动检查新版本；发现新版会显示更新说明，点「立即更新」即在客户端内下载、校验、自动替换并重启。也可手动「帮助 → 检查更新」 |
| 我的区服 | 列出自己创建的区服，显示本机引擎端口、玩家监听端口、联机地址、开服状态、收费与到期时间 |
| 列表右键刷新 | 在「我的区服」/「区服大厅」列表上点右键：**在某个区服那一行右键** → 只刷新该区服；**在空白处右键** → 刷新全部区服 |
| 右键收藏 | 在「区服大厅」**某个区服那一行**右键 → 「收藏 / 取消收藏」（同一项按当前状态自动切换文案），收藏状态与网站同步，收藏数列会立即刷新 |
| 右键新增区服 | 在「我的区服」列表**空白处**右键 → 「新增区服（打开网站区服大厅）」，会用默认浏览器打开网站的区服大厅，可在网页上自助新增区服 |
| 右键区服置顶 | 在「我的区服」**某个区服那一行**右键 → 「区服置顶（打开网站置顶页）」，会用默认浏览器打开网站的置顶页，可在网页上把该区服**排在区服大厅更前面**（免费置顶 / 固定价格 / 竞价三种玩法与价格由站长在网站后台「自助区服」里设置；**三种方式都只能在站长设定的每日置顶时间内参与**） |
| 列表排序 | 「我的区服」与「区服大厅」的列表顺序**按网站统一规则排列**：① 站长手动置顶的区服 → ② 付费置顶生效的区服（竞价按本轮金额排名，固定价按支付时间先后） → ③ 正在开服中的区服（按开服时间先后） |
| 公网区服处理 | 见第六节「公网联机区服」：公网区服不走助手的本机转发，助手端对应功能会自动禁用 / 改为给出下载地址 |
| 开服检测 | 每 5 秒检测本机三个网关端口（7000 / 7100 / 7200），三个都通才判定为「开服」并上报平台 |
| 一键内网穿透 | 调用 frpc **一次性建立三条 TCP 映射**（登录 / 选角 / 游戏网关）；映射地址只在内部转发时使用，**界面不展示**，启动后会自动校验三条映射是否真的建立成功 |
| 本机三端口转发 | 玩家点击「联机进入」时，在本机同时监听 IPv4/IPv6 的 7000 / 7100 / 7200 并转发到区服联机地址，**登陆器无需修改任何配置** |
| 联机自检 | 逐跳检查「网关信息 → 对外地址拨号 → 本机监听 / 引擎端口」，直接指出是哪一环断了 |
| 状态上报 | 每分钟（可配置）批量心跳上报，状态变化时立即上报 |
| 区服大厅 | 内网区服与公网区服分组排列，显示服主、地址、可联机、**联机数**、开服状态、是否需要密码、收藏数、开号方式。底部「**获取下载地址**」按键（排在「联机自检」后面）内网 / 公网区服都可用，点开即列出服主填写的 客户端 / 补丁 / 登陆器 地址，每条都可一键「打开」或「复制」；客户端 / 补丁 / 登陆器的网盘提取码会一并展示并可复制 |
| 联机数 | 「区服大厅」的「联机数」列显示当前有多少玩家正在通过联机助手联机该区服。**只有「可联机」状态**的区服才统计并显示数字（初始为 0），其余（未映射 / 公网联机）显示「—」 |
| 每服独立客户端路径 | 每个区服单独保存本机游戏客户端 / 登陆器路径；**启动参数由程序自动处理，默认不传参数**，个别登陆器需要时可在「操作 → 启动参数模板（高级）」里按区服设置。「设置客户端路径」按钮位于「区服大厅」 |
| 联机密码 | 服主的区服可直接读取密码；他人区服需输入服主提供的密码 |

### 界面上的联机地址

一键内网穿透会改变流量的实际走向，但**不会改变界面上显示的联机地址**：

- 内网区服：网页端（区服大厅 / 区服详情 / 我的区服）与联机助手 PC 端显示服主填写的 `ip:port`，
  开启穿透前后完全一致。
- **公网区服：不显示联机地址**。公网区服不使用助手的本机转发，它那条对外通道开在平台的穿透
  服务端上，把地址显示出来等于把平台 IP 暴露给所有人，所以网页端与 PC 端一律只显示
  「公网联机 · 无需联机地址」。
- 穿透产生的映射地址（`map_host:map_port*`）只通过 `gateways[]` 下发给联机助手用于建立本机转发，
  不在任何前台界面展示；后台区服管理页保留一列「穿透映射」仅供管理员排查问题。

### 开服状态怎么来的

| 区服类型 | 开服状态 | 说明 |
| --- | --- | --- |
| 内网区服 | 助手自动检测 | 每 5 秒检测本机三个网关端口，三个都通即上报「开服」；超时未上报会自动判为「已关服」 |
| 公网区服 | 服主手动设置 | 公网联机不经过助手转发，平台无从检测。服主在网站「我的区服 → 编辑区服」勾选「手动设置为『开服中』」，网站与 PC 端同步显示 |

> PC 端的「我的区服 / 区服大厅」列表里，公网区服会标注「（公网·手动设置）」，与自动检测的内网区服区分开。


---

## 二、编译与运行

### 开发调试

```bash
dotnet build LegendAssistant.sln -c Debug
dotnet run --project LegendAssistant/LegendAssistant.csproj
```

或用 Visual Studio 2022 打开 `LegendAssistant.sln` 直接 F5 运行。

### 发布（免安装运行，推荐分发给玩家）

```bash
dotnet publish LegendAssistant/LegendAssistant.csproj -c Release -r win-x64 ^
  --self-contained true -p:PublishSingleFile=true ^
  -p:IncludeNativeLibrariesForSelfExtract=true ^
  -p:EnableCompressionInSingleFile=true -o publish
```

> `EnableCompressionInSingleFile` 一定要带：不带的话单文件约 145 MB，带上约 66 MB。
> 发布后把 `publish/LegendAssistant.exe` 与本说明一起打包为 `legend-assistant.zip`，
> 放到网站 `public/static/download/`，即可在后台「下载中心设置」中提供下载。

若使用「依赖框架」方式发布，则玩家电脑需要安装 .NET 8 Desktop Runtime：

```bash
dotnet publish LegendAssistant/LegendAssistant.csproj -c Release -r win-x64 --self-contained false -o publish
```

---

## 三、三网关联机（重要）

传奇引擎进入游戏需要 **三个网关端口同时连通**，缺一个都进不去：

| 网关 | 引擎程序 | 标准端口 | 用途 |
| --- | --- | --- | --- |
| 登录网关 | LoginGate.exe | 7000 | 连接服务端引擎、登录账号 |
| 选角网关 | SelGate.exe | 7100 | 建立游戏角色、选择角色 |
| 游戏网关 | RunGate.exe | 7200 | 进入游戏 |

而传奇登陆器的服务器列表写死了 `127.0.0.1:7000`，不会跟着区服地址走。本客户端的做法是：

**玩家端 —— 在本机监听固定端口，转发到区服实际地址**

```
127.0.0.1:7000  →  区服联机地址:端口（登录网关）
127.0.0.1:7100  →  区服联机地址:端口（选角网关）
127.0.0.1:7200  →  区服联机地址:端口（游戏网关）
```

监听端口取平台下发的 `gateways[].listen_port`，**固定为 7000 / 7100 / 7200**，
而不是 `local_port`（那是服主本机引擎端口，公网区服通常是非标准端口，如 23391）。
正因为监听端口固定，**无论服主引擎用哪个端口，玩家的登陆器都不需要改配置**。

三个端口在本机监听时开启了地址复用（SO_REUSEADDR），因此在区服大厅切换区服、
重新绑定同一批端口时不会因为上一个连接的 TIME_WAIT 而失败。

同时会额外监听 IPv6 回环 `::1`：Windows 上 `localhost` 经常优先解析到 `::1`，
只监听 `127.0.0.1` 时这类登陆器会连不上。

**服主端 —— 一次性建立三条映射**

点击「开启一键内网穿透」后，客户端会生成包含三条 `[[proxies]]` 的 frpc 配置
（自动适配 frp v0.52+ 的 TOML 与旧版 INI 格式），同时映射三个引擎端口。
映射建立后会检查 frpc 日志里的 `start proxy success`，三条全部成功才算开启成功 ——
如果穿透服务端没放行映射端口或 Token 不对，frpc 进程虽然活着但映射是失败的，
这种情况会被直接拦住并提示，不会再出现「界面显示已开启、玩家却连不上」。

> 服主请把引擎的三个网关地址都配置为本机 `127.0.0.1`（单机默认即是），
> 否则引擎下发给客户端的是不可达地址。

**联机自检**

「操作 → 联机自检」或列表下方的「联机自检」按钮会按下面的顺序逐跳验证并给出结论：

1. 平台下发的三个网关是否完整（`host` / `port` / `listen_port`）；
2. 从本机实际拨号到三个网关的对外地址（这一步最能说明问题）；
3. 本机侧：服主看引擎端口是否监听、穿透服务端是否可达；玩家看本机转发端口是否在监听。

第 ② 步三个都通，基本就代表链路是通的。所以玩家报「连不上」时，先让对方跑一次自检，
把结果发过来，就能直接定位是哪一环断了。


---

## 四、一键内网穿透说明

> **穿透的开 / 关只在联机助手 PC 端操作**：在「我的区服」列表里选中区服，点「开启一键内网穿透」即可。
> 网站前台「新增区服 / 编辑区服」与后台区服表单都**没有**这个开关（老版本有过，已去掉），
> 平台侧只需要管理员把功能打开（见下第 1 条）。

1. 平台后台「系统设置 → 内网穿透」中开启功能，填写 **FRP 服务端地址**、端口、Token，并把 `frpc.exe` 放到网站 `public/static/download/` 目录，在「frpc客户端下载地址」中填写该地址。
2. 客户端在本机找不到 frpc.exe 时，会提示从平台自动下载。
3. 打开区服的穿透开关后，客户端会用平台下发的参数生成 frpc 配置（**三条映射，对应登录 / 选角 / 游戏三个网关**）并启动，随后校验三条映射是否全部建立成功。
4. 映射地址由 **平台**根据 FRP 配置计算（域名或服务端地址 + 平台分配的映射端口），
   **仅供客户端内部建立转发使用，不在网站前台与客户端界面展示**；
   前台与客户端显示的联机地址始终是服主填写的地址。
5. 穿透服务端除了放行 `frp_port`（frpc 连接端口），还要放行平台分配区间
   （后台「映射端口起始 / 结束」，默认 20000-30000），否则映射建立会失败。

---

## 五、本机转发（玩家联机通道）

状态栏里的「本机转发（玩家联机通道）：未开启」指的是下面这件事，**未开启是正常状态**。

传奇登陆器的服务器列表写死了 `127.0.0.1:7000`，登录后引擎还会让客户端连 `7100`（选角）
与 `7200`（游戏）。玩家和服主不在同一台机器时，这三个地址在玩家本机是不存在的，
所以助手会：

1. 在玩家本机监听 `7000 / 7100 / 7200`；
2. 把流量转发到所选区服的联机地址（穿透映射地址 / 公网IP / 内网IP）。

这样登陆器完全不用改配置，点「联机进入」就能进游戏。

| 状态 | 含义 |
| --- | --- |
| 未开启 | 还没有建立转发。启动程序、还没点「联机进入」时就是这个状态，属正常 |
| 已建立 7000、7100、7200 → 区服名 | 转发已生效，此时启动游戏即可进入 |

用法：在「区服大厅」选中区服 → 点「联机进入」（或双击）。助手会先向平台拉一次该区服的
最新信息（避免服主刚开关穿透、这边还是旧数据），再按下面的顺序决定要不要转发：

1. 已经在为这个区服转发 → 直接用；
2. 是自己的区服，且本机 7000/7100/7200 都在跑（引擎就在本机）→ 不需要转发；
3. 区服还没有可用的对外地址 → 提示「暂时无法联机」并给出原因，不启动游戏；
4. 建立三个端口的转发，并**立即回连验证三个端口确实在监听**，通过后才启动游戏；
5. 建立失败但本机三个端口已被自己的引擎占用 → 提示「引擎就在本机，直接启动登陆器即可」。

想停掉转发就点「断开联机」。

> **转发建立不依赖「是不是自己的区服」。** 服主换一台电脑当玩家玩自己的区服时，
> 引擎并不在那台机器上，同样会（也应该）建立转发。
>
> 玩家与服主是同一台机器时，因为引擎已经占用了 7000/7100/7200，转发没法绑定；助手会识别出
> 这种情况并提示「引擎就在本机」，然后照常启动登陆器——不会误报成失败。

---

## 六、公网联机区服（与内网区服的区别）

本客户端主要服务于**内网联机**区服：服主在自己电脑上跑引擎，靠助手做端口转发 / 一键内网穿透。
**公网联机**区服的三个对外端口由**服主自行开放**（开在服主自己的服务器上），
**助手端不参与联机**，因此：

**「我的区服」里选中公网联机区服时** —— 列表下方的功能按键全部禁用
（一键内网穿透、检测三个网关端口、联机自检），右下方的提示条会说明原因。
该区服请到网站上管理。

> 「我的区服」只保留三个按键：一键内网穿透、检测三个网关端口、联机自检。
> **启动游戏 / 登陆器、设置客户端路径、复制联机地址**这三个按键已移除
> —— 前两者与「区服大厅」的功能重叠，后者不适用于当前联机方式。

**「区服大厅」里选中公网联机区服时** —— 除「**获取下载地址**」外的功能按键全部禁用
（按键文案沿用旧版界面，只是整体置灰，效果与「我的区服」里选中公网区服时一致）；
「获取下载地址」固定在「联机自检」后面，内网 / 公网区服选中后都可以用。
点它（或双击该区服）会弹出**服主在网站填写的那三个下载地址**（客户端 / 补丁 / 登陆器），每个地址都可以一键
「打开」或用「复制」；若服主为某个地址填了**网盘提取码**，弹窗会在该地址下方多显示一行「提取码」并可一键复制。
玩家下载安装后直接进入游戏即可，不走本机转发，也不需要登陆器改配置。

> 公网区服的三个对外端口由服主自行开放，平台不展示也不下发其联机地址；
> 因此助手端不检测其开服状态，开服状态由服主在网站「编辑区服」里手动勾选。

> 判断依据是区服信息里的 `net_type`：`public` = 公网联机，其余按内网联机处理。
> 服务端下发的 `is_public` 字段与之等价，可优先使用。

---

## 七、自动更新

客户端**每次启动会自动检查新版本**（检查发生在登录之前，所以即使平台换了 app_key、
旧版登录不了，也仍有机会升级）。发现新版后：

1. 弹窗显示新版本号与更新说明；
2. 点「立即更新」→ 客户端下载新版 → **校验 SHA256** → 启动新版进程；
3. 新版进程等待旧进程退出后覆盖自身并重新启动，用户无需手动操作。

也可以手动点 **「帮助 → 检查更新」**，此时「已是最新版」或「检查失败」都会明确提示。

### 服务端需要什么

网站 `public/static/download/` 下要有两个文件：

| 文件 | 作用 |
| --- | --- |
| `LegendAssistant.exe` | 新版单文件程序本体，`version.json` 里指向它 |
| `version.json` | 更新清单，客户端按它判断有没有新版 |

`version.json` 格式：

```json
{
  "version": "1.2.0.0",
  "url": "https://zs.oddpp.com/static/download/LegendAssistant.exe",
  "sha256": "（exe 的 sha256，小写十六进制）",
  "size": 65860000,
  "notes": "更新说明，可多行",
  "force": false
}
```

- 版本比较用 `System.Version`，**必须是 `a.b.c.d` 四段式**，例如 `1.1.0.0`。
- `sha256` 留空则跳过校验（不推荐）。
- `force: true` 时会隐藏「稍后再说」按钮，强制更新。

### 发布新版本的标准步骤

1. 改 `AppInfo.cs` 的 `Version` 与 `LegendAssistant.csproj` 的 `Version` / `FileVersion`（两处要一致）；
2. `dotnet publish`（见第二节）；
3. 用发布脚本生成 `version.json`（自动算 sha256 与大小），并把 `LegendAssistant.exe`、`version.json`
   一起上传到网站 `public/static/download/`；
4. 重新打包 `legend-assistant.zip`（供新用户首次下载）。

> **注意**：`1.0.0.0` 及更早的客户端没有自动更新代码，**从旧版升级到第一个带更新功能的版本，必须手动下载一次新包**。
> 之后的版本升级就全自动了。

---

## 八、本地数据

| 内容 | 位置 |
| --- | --- |
| 账号、客户端路径等设置 | `%APPDATA%\LegendAssistant\settings.json` |
| frpc 程序与配置、日志 | `%APPDATA%\LegendAssistant\frp\` |

密码以混淆方式保存在本机文件中（仅用于自动登录，不会上传）。

---

## 九、接口

客户端通过 `https://<平台地址>/api/*` 与网站通信，接口清单见项目根目录
[docs/联机助手接口文档.md](../docs/联机助手接口文档.md)。
