From ae9be2829d3406b2089942b869bba1f95455641f Mon Sep 17 00:00:00 2001 From: Song <168455@qq.com> Date: Fri, 10 Jul 2026 07:58:59 +0800 Subject: [PATCH] fix: scramble update-source (XOR+reverse+Base64) + hide in UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - update-source in release notes now XOR-scrambled (not plain Base64) - UpdateDialog filters out update-source comment line from notes - Download progress shows "正在下载更新…" instead of percentage/source - version 1.8.1 -> 1.8.2 (code 18 -> 19) Co-Authored-By: Claude Opus 4.8 (1M context) --- app/build.gradle.kts | 4 ++-- .../com/a2i/forwarder/core/UpdateChecker.kt | 17 +++++++++++++---- .../a2i/forwarder/ui/screens/UpdateDialog.kt | 8 ++++++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c443e6e..f2aa993 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,8 +13,8 @@ android { applicationId = "com.a2i.forwarder" minSdk = 34 targetSdk = 36 - versionCode = 18 - versionName = "1.8.1" + versionCode = 19 + versionName = "1.8.2" } signingConfigs { diff --git a/app/src/main/java/com/a2i/forwarder/core/UpdateChecker.kt b/app/src/main/java/com/a2i/forwarder/core/UpdateChecker.kt index e3978f9..96f0155 100644 --- a/app/src/main/java/com/a2i/forwarder/core/UpdateChecker.kt +++ b/app/src/main/java/com/a2i/forwarder/core/UpdateChecker.kt @@ -250,17 +250,26 @@ class UpdateChecker(private val context: Context) { return false } - /** 从 release notes 的 HTML 注释里解析额外更新源(Base64 编码,兼容明文)。 */ + /** 从 release notes 的 HTML 注释里解析额外更新源(加扰编码,兼容 Base64/明文)。 */ private fun parseUpdateSource(body: String): String? { val raw = Regex("""update-source:\s*(\S+)""").find(body)?.groupValues?.getOrNull(1) ?: return null - // 尝试 Base64 解码;解码失败则原样返回(兼容明文) - return runCatching { String(java.util.Base64.getDecoder().decode(raw), Charsets.UTF_8) } - .getOrNull() ?: raw + // 加扰格式:Base64(反转(XOR(明文, key)))。尝试完整解扰,失败则依次尝试 Base64/明文。 + val b64 = runCatching { java.util.Base64.getDecoder().decode(raw) }.getOrNull() ?: return raw + val unscrambled = unscramble(b64) + return unscrambled ?: runCatching { String(b64, Charsets.UTF_8) }.getOrNull() ?: raw } + /** 解扰:反转字节 → XOR 每字节 → UTF-8 字符串。 */ + private fun unscramble(data: ByteArray): String? = runCatching { + val key = SCRAMBLE_KEY + val reversed = data.reversedArray() + String(ByteArray(reversed.size) { i -> (reversed[i].toInt() xor key[i % key.size]).toByte() }, Charsets.UTF_8) + }.getOrNull() + companion object { const val GITHUB_API = "https://api.github.com/repos/lsxf/a2i/releases/latest" const val CHECK_INTERVAL_MS = 24L * 60 * 60 * 1000 + private val SCRAMBLE_KEY = byteArrayOf(0x4B, 0x6F, 0x72, 0x6E) // "Korn" } fun formatSize(bytes: Long): String { diff --git a/app/src/main/java/com/a2i/forwarder/ui/screens/UpdateDialog.kt b/app/src/main/java/com/a2i/forwarder/ui/screens/UpdateDialog.kt index c2e1407..31d593c 100644 --- a/app/src/main/java/com/a2i/forwarder/ui/screens/UpdateDialog.kt +++ b/app/src/main/java/com/a2i/forwarder/ui/screens/UpdateDialog.kt @@ -55,7 +55,11 @@ fun UpdateDialog(info: UpdateInfo, onDismiss: () -> Unit) { .heightIn(max = 240.dp) .verticalScroll(rememberScrollState()), ) { - val notes = info.releaseNotes.ifBlank { "(作者未提供更新说明)" } + val notes = info.releaseNotes + .lineSequence() + .filterNot { it.contains("update-source") } + .joinToString("\n") + .ifBlank { "(作者未提供更新说明)" } Text(notes, style = MaterialTheme.typography.bodyMedium) } if (downloading) { @@ -66,7 +70,7 @@ fun UpdateDialog(info: UpdateInfo, onDismiss: () -> Unit) { ) Spacer(Modifier.size(4.dp)) Text( - "$progress%", + "正在下载更新…", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, )