Skip to content

Commit 90056f5

Browse files
committed
feat: 添加客户端自身自动更新功能
新增客户端自身更新机制,支持自动检查、下载和安装新版本: 新增文件: - selfupdate/SelfUpdateChecker.java - 版本检查器 - selfupdate/SelfUpdateDownloader.java - 下载器 - selfupdate/SelfUpdateInstaller.java - 安装器 - selfupdate/SelfUpdateManager.java - 统一管理器 - selfupdate/ClientVersionInfo.java - 版本信息模型 - docs/self-update-design.md - 设计文档 - docs/client-version.json - 服务端版本文件示例 修改文件: - AppConfig.java - 新增 ClientUpdateConfig 配置类 - mcpatch.yml - 新增 client-update 配置项 功能特性: - 版本检查(支持多渠道:stable/beta/alpha) - SHA-256 校验验证 - 自动备份和回滚 - 延迟安装(避免 JAR 文件锁定问题)
1 parent c7f521f commit 90056f5

9 files changed

Lines changed: 964 additions & 1 deletion

File tree

docs/client-version.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"latest_version": "0.0.12", "min_version": "0.0.10", "download_url": "https://your-server.com/mcpatch/Mcpatch-0.0.12.jar", "checksum": "a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890", "changelog": "- 修复空文件下载时崩溃问题\n- 修复 Webdav 协议无法使用的问题\n- 新增自动更新功能", "release_date": "2025-04-04", "force_update": false, "channels": {"stable": {"version": "0.0.12", "download_url": "https://your-server.com/mcpatch/Mcpatch-0.0.12.jar"}, "beta": {"version": "0.0.13-beta.1", "download_url": "https://your-server.com/mcpatch/Mcpatch-0.0.13-beta.1.jar"}, "alpha": {"version": "0.0.14-alpha.1", "download_url": "https://your-server.com/mcpatch/Mcpatch-0.0.14-alpha.1.jar"}}}

docs/self-update-design.md

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# Mcpatch2JavaClient 自动更新方案
2+
3+
## 方案概述
4+
5+
由于客户端作为 Java Agent 运行,JAR 文件在运行时被锁定无法直接替换,因此采用 **"下载-标记-替换"** 的延迟更新策略。
6+
7+
---
8+
9+
## 架构设计
10+
11+
```
12+
┌─────────────────────────────────────────────────────────┐
13+
│ 启动流程 │
14+
├─────────────────────────────────────────────────────────┤
15+
│ 1. 启动 Minecraft │
16+
│ ↓ │
17+
│ 2. Java Agent (update.jar) 加载 │
18+
│ ↓ │
19+
│ 3. 检查客户端自身更新 ←─────────────────┐ │
20+
│ ↓ │ │
21+
│ 4. 如有更新: 下载新版本到临时位置 │ │
22+
│ ↓ │ │
23+
│ 5. 检查游戏文件更新 │ │
24+
│ ↓ │ │
25+
│ 6. 启动 Minecraft │ │
26+
│ ↓ │ │
27+
│ 7. 程序退出时/下次启动: 替换旧版本 ──────┘ │
28+
└─────────────────────────────────────────────────────────┘
29+
```
30+
31+
---
32+
33+
## 实现方案
34+
35+
### 1. 版本检测
36+
37+
在配置文件 `mcpatch.yml` 中添加客户端更新配置:
38+
39+
```yaml
40+
# 客户端自身更新配置
41+
client-update:
42+
# 客户端版本检查 URL
43+
version-url: "https://your-server.com/mcpatch/client-version.json"
44+
# 是否启用自动更新
45+
enabled: true
46+
# 更新渠道: stable/beta/alpha
47+
channel: stable
48+
```
49+
50+
### 2. 版本信息文件 (服务端)
51+
52+
`client-version.json`:
53+
```json
54+
{
55+
"latest_version": "0.0.12",
56+
"min_version": "0.0.10",
57+
"download_url": "https://your-server.com/mcpatch/Mcpatch-0.0.12.jar",
58+
"checksum": "abc123def456...",
59+
"changelog": "修复了若干bug",
60+
"release_date": "2025-04-04",
61+
"force_update": false
62+
}
63+
```
64+
65+
### 3. 核心代码结构
66+
67+
```
68+
src/main/java/com/github/balloonupdate/mcpatch/client/
69+
├── selfupdate/
70+
│ ├── SelfUpdateChecker.java # 检查更新
71+
│ ├── SelfUpdateDownloader.java # 下载新版本
72+
│ ├── SelfUpdateInstaller.java # 安装更新
73+
│ └── ClientVersionInfo.java # 版本信息
74+
```
75+
76+
---
77+
78+
## 详细设计
79+
80+
### 阶段 1: 启动时检查
81+
82+
```java
83+
// Main.java 中的修改
84+
public static void premain(String agentArgs, Instrumentation ins) throws Throwable {
85+
// 1. 先检查客户端自身更新
86+
if (checkSelfUpdate()) {
87+
downloadSelfUpdate();
88+
}
89+
90+
// 2. 检查是否有待安装的更新
91+
installPendingUpdate();
92+
93+
// 3. 继续正常的游戏文件更新流程
94+
// ...
95+
}
96+
```
97+
98+
### 阶段 2: 下载新版本
99+
100+
```java
101+
public class SelfUpdateDownloader {
102+
/**
103+
* 下载新版本到临时文件
104+
* Windows: %TEMP%\mcpatch-update.jar
105+
* Linux/Mac: /tmp/mcpatch-update.jar
106+
*/
107+
public static Path downloadNewVersion(String downloadUrl, String expectedChecksum) {
108+
Path tempFile = getUpdateTempFile();
109+
110+
// 下载文件
111+
downloadFile(downloadUrl, tempFile);
112+
113+
// 校验 checksum
114+
if (!verifyChecksum(tempFile, expectedChecksum)) {
115+
throw new RuntimeException("Checksum verification failed");
116+
}
117+
118+
// 创建标记文件,表示有待安装的更新
119+
createUpdateMarker(tempFile);
120+
121+
return tempFile;
122+
}
123+
}
124+
```
125+
126+
### 阶段 3: 延迟替换
127+
128+
```java
129+
public class SelfUpdateInstaller {
130+
/**
131+
* 安装待处理的更新
132+
* 这个方法在程序启动最开始执行
133+
*/
134+
public static boolean installPendingUpdate() {
135+
Path markerFile = getUpdateMarkerFile();
136+
137+
if (!Files.exists(markerFile)) {
138+
return false; // 没有待安装的更新
139+
}
140+
141+
// 读取标记文件获取新版本路径
142+
String newVersionPath = Files.readString(markerFile);
143+
Path newJar = Paths.get(newVersionPath);
144+
Path currentJar = Env.getJarPath();
145+
146+
if (!Files.exists(newJar)) {
147+
// 新版本文件不存在,清理标记
148+
Files.delete(markerFile);
149+
return false;
150+
}
151+
152+
// 替换 JAR 文件
153+
backupCurrentVersion(currentJar);
154+
Files.move(newJar, currentJar, StandardCopyOption.REPLACE_EXISTING);
155+
Files.delete(markerFile);
156+
157+
Log.info("客户端已更新到最新版本");
158+
return true;
159+
}
160+
161+
/**
162+
* 备份当前版本,以防回滚
163+
*/
164+
private static void backupCurrentVersion(Path currentJar) {
165+
Path backup = currentJar.resolveSibling(currentJar.getFileName() + ".backup");
166+
Files.copy(currentJar, backup, StandardCopyOption.REPLACE_EXISTING);
167+
}
168+
}
169+
```
170+
171+
---
172+
173+
## 更新流程图
174+
175+
```
176+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
177+
│ 程序启动 │────→│ 检查标记文件 │────→│ 有待更新? │
178+
└──────────────┘ └──────────────┘ └──────┬───────┘
179+
180+
┌─────────────────────────────┴─────┐
181+
│ Yes │ No
182+
↓ ↓
183+
┌──────────────┐ ┌──────────────┐
184+
│ 替换 JAR 文件│ │ 正常启动流程 │
185+
└──────┬───────┘ └──────────────┘
186+
187+
188+
┌──────────────┐
189+
│ 检查新版本 │
190+
└──────┬───────┘
191+
192+
├── 有新版本 ──→ 下载到临时位置 ──→ 创建标记文件 ──→ 继续启动
193+
194+
└── 无新版本 ──→ 继续正常启动流程
195+
```
196+
197+
---
198+
199+
## 文件结构
200+
201+
```
202+
.minecraft/versions/xxx/
203+
├── update.jar # 当前运行的客户端
204+
├── update.jar.backup # 备份文件(用于回滚)
205+
├── update.jar.new # 待安装的新版本(Windows)
206+
└── .mcpatch-temp/
207+
└── self-update.marker # 更新标记文件
208+
```
209+
210+
---
211+
212+
## 配置示例
213+
214+
```yaml
215+
# mcpatch.yml 完整配置示例
216+
217+
# 游戏文件更新服务器
218+
urls:
219+
- "https://update.example.com/mcpatch/"
220+
221+
# 客户端自身更新配置
222+
client-update:
223+
enabled: true
224+
version-url: "https://update.example.com/client-version.json"
225+
channel: stable
226+
auto-install: true # 自动安装更新
227+
backup-enabled: true # 是否备份旧版本
228+
rollback-on-failure: true # 更新失败时回滚
229+
230+
# 其他配置...
231+
version-file-path: "version-label.txt"
232+
window-title: "游戏更新器"
233+
```
234+
235+
---
236+
237+
## 优势
238+
239+
| 特性 | 说明 |
240+
|------|------|
241+
| ✅ 非阻塞更新 | 不影响当前游戏运行 |
242+
| ✅ 自动回滚 | 更新失败可恢复 |
243+
| ✅ 校验机制 | Checksum 防止损坏 |
244+
| ✅ 跨平台 | Windows/Linux/macOS 兼容 |
245+
| ✅ 可配置 | 支持多渠道更新 |
246+
247+
---
248+
249+
## 注意事项
250+
251+
1. **首次运行**: 需要手动部署初始版本
252+
2. **权限问题**: 确保有写入 JAR 目录的权限
253+
3. **网络超时**: 需要处理网络异常情况
254+
4. **版本回退**: 保留备份文件支持回滚

src/main/java/com/github/balloonupdate/mcpatch/client/config/AppConfig.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,46 @@ public class AppConfig {
116116
*/
117117
public boolean testMode;
118118

119+
/**
120+
* 客户端自身更新配置
121+
*/
122+
public static class ClientUpdateConfig {
123+
/**
124+
* 是否启用客户端自动更新
125+
*/
126+
public boolean enabled;
127+
128+
/**
129+
* 版本检查 URL
130+
*/
131+
public String versionUrl;
132+
133+
/**
134+
* 更新渠道: stable/beta/alpha
135+
*/
136+
public String channel;
137+
138+
/**
139+
* 是否自动安装更新
140+
*/
141+
public boolean autoInstall;
142+
143+
/**
144+
* 是否启用备份
145+
*/
146+
public boolean backupEnabled;
147+
148+
/**
149+
* 更新失败时是否回滚
150+
*/
151+
public boolean rollbackOnFailure;
152+
}
153+
154+
/**
155+
* 客户端自身更新配置
156+
*/
157+
public ClientUpdateConfig clientUpdate;
158+
119159

120160
public AppConfig(Map<String, Object> map) {
121161
List<String> urls = getList(map, "urls", null, new ArrayList<>());
@@ -156,6 +196,32 @@ public AppConfig(Map<String, Object> map) {
156196
this.ignoreSSLCertificate = ignoreSSLCertificate;
157197
this.ignoreHttpContentLength = ignoreHttpContentLength;
158198
this.testMode = testMode;
199+
200+
// 解析客户端更新配置
201+
this.clientUpdate = parseClientUpdateConfig(map);
202+
}
203+
204+
/**
205+
* 解析客户端自身更新配置
206+
*/
207+
@SuppressWarnings("unchecked")
208+
private ClientUpdateConfig parseClientUpdateConfig(Map<String, Object> map) {
209+
ClientUpdateConfig config = new ClientUpdateConfig();
210+
211+
Object clientUpdateObj = map.get("client-update");
212+
213+
if (clientUpdateObj instanceof Map) {
214+
Map<String, Object> cuMap = (Map<String, Object>) clientUpdateObj;
215+
216+
config.enabled = getBoolean(cuMap, "enabled", null, false);
217+
config.versionUrl = getString(cuMap, "version-url", null, "");
218+
config.channel = getString(cuMap, "channel", null, "stable");
219+
config.autoInstall = getBoolean(cuMap, "auto-install", null, true);
220+
config.backupEnabled = getBoolean(cuMap, "backup-enabled", null, true);
221+
config.rollbackOnFailure = getBoolean(cuMap, "rollback-on-failure", null, true);
222+
}
223+
224+
return config;
159225
}
160226

161227
@SuppressWarnings("unchecked")

0 commit comments

Comments
 (0)