diff --git a/.gitignore b/.gitignore
index 30ec341..05285f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,4 @@ android/build/
*.jks
*.keystore
local.properties
+screen.png
diff --git a/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.bin b/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.bin
index 9a7f9a1..244c4b2 100644
Binary files a/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.bin and b/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.bin differ
diff --git a/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.lock b/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.lock
index a6e723e..78f074c 100644
Binary files a/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.lock and b/packages/client-android/android/.gradle/8.10.2/executionHistory/executionHistory.lock differ
diff --git a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin
index f8df620..253d375 100644
Binary files a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin and b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.bin differ
diff --git a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock
index 2d08ec6..76da75c 100644
Binary files a/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock and b/packages/client-android/android/.gradle/8.10.2/fileHashes/fileHashes.lock differ
diff --git a/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin b/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin
index d99a43a..844cabd 100644
Binary files a/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin and b/packages/client-android/android/.gradle/8.10.2/fileHashes/resourceHashesCache.bin differ
diff --git a/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock
index 58c8a91..8739b5d 100644
Binary files a/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/packages/client-android/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
diff --git a/packages/client-android/android/.gradle/file-system.probe b/packages/client-android/android/.gradle/file-system.probe
index 1626093..f2ab096 100644
Binary files a/packages/client-android/android/.gradle/file-system.probe and b/packages/client-android/android/.gradle/file-system.probe differ
diff --git a/packages/client-android/android/app/build.gradle b/packages/client-android/android/app/build.gradle
index c006649..11694b6 100644
--- a/packages/client-android/android/app/build.gradle
+++ b/packages/client-android/android/app/build.gradle
@@ -1,6 +1,8 @@
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
+archivesBaseName = "gca-v0.1.0"
+
android {
namespace "dev.yuzu.gca"
compileSdk 35
@@ -9,7 +11,7 @@ android {
applicationId "dev.yuzu.gca"
minSdk 24
targetSdk 35
- versionCode 5
+ versionCode 6
versionName "0.1.0"
}
diff --git a/packages/client-android/android/app/src/main/AndroidManifest.xml b/packages/client-android/android/app/src/main/AndroidManifest.xml
index 10f6838..98bbd93 100644
--- a/packages/client-android/android/app/src/main/AndroidManifest.xml
+++ b/packages/client-android/android/app/src/main/AndroidManifest.xml
@@ -5,7 +5,7 @@
diff --git a/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt
index f829d17..de91edc 100644
--- a/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt
+++ b/packages/client-android/android/app/src/main/java/dev/yuzu/gca/MainActivity.kt
@@ -60,7 +60,7 @@ class MainActivity : AppCompatActivity() {
appendLog("[1/4] 拉取 manifest...")
try {
- val manifestJson = httpGet("https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/manifest/manifest.json")
+ val manifestJson = httpGet("https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-6/manifest.json")
appendLog(" manifest: ${manifestJson.take(200)}...")
} catch (e: Exception) {
appendLog(" ⚠️ 网络不可用: ${e.message}")
@@ -69,25 +69,50 @@ class MainActivity : AppCompatActivity() {
return@withContext
}
- appendLog("[2/4] 解析版本")
- appendLog(" 远程: v0.2.0-dav-android-1 > 本地: 0.1.0 → 需要更新 ✅")
+ appendLog("[2/4] 解析 manifest JSON...")
+ var manifestSha256 = ""
+ var manifestApkUrl = ""
+ try {
+ val manifestJson = httpGet("https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-6/manifest.json")
+ // 简易 JSON 解析(不用 Gson,直接字符串提取)
+ val sha256Field = "\"sha256\": \""
+ val sha256Start = manifestJson.indexOf(sha256Field) + sha256Field.length
+ val sha256End = manifestJson.indexOf("\"", sha256Start)
+ manifestSha256 = manifestJson.substring(sha256Start, sha256End)
- appendLog("[3/4] 下载 APK...")
+ val urlField = "\"url\": \""
+ val urlStart = manifestJson.indexOf(urlField) + urlField.length
+ val urlEnd = manifestJson.indexOf("\"", urlStart)
+ manifestApkUrl = manifestJson.substring(urlStart, urlEnd)
+
+ appendLog(" SHA256: ${manifestSha256.take(16)}...")
+ appendLog(" APK URL: ${manifestApkUrl.take(50)}...")
+ appendLog(" 需要更新 ✅")
+ } catch (e: Exception) {
+ appendLog(" ⚠️ manifest 解析失败: ${e.message}")
+ appendLog("=== OTA 测试中止 ===")
+ return@withContext
+ }
+
+ appendLog("[3/4] 下载 APK: ${manifestApkUrl.take(40)}...")
val apkFile = File(cacheDir, "test-update.apk")
try {
- val apkUrl = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-1/app-debug.apk"
- downloadFile(apkUrl, apkFile)
- appendLog(" 下载完成: ${apkFile.length()} bytes")
+ downloadFile(manifestApkUrl, apkFile)
+ appendLog(" 下载完成: ${apkFile.length()} bytes ✅")
} catch (e: Exception) {
- appendLog(" ⚠️ APK未上传: ${e.message}")
- appendLog(" 但下载+校验代码路径已验证")
+ appendLog(" ❌ 下载失败: ${e.message}")
+ return@withContext
}
appendLog("[4/4] SHA256 校验...")
- if (apkFile.exists()) {
- val sha256 = computeSha256(apkFile)
- appendLog(" SHA256: $sha256")
- appendLog(" ✅ 校验通过(原始字节计算,与Kotlin MessageDigest一致)")
+ val actualSha256 = computeSha256(apkFile)
+ if (actualSha256 == manifestSha256) {
+ appendLog(" ✅ SHA256 校验通过!")
+ appendLog(" ${actualSha256.take(32)}...")
+ } else {
+ appendLog(" ❌ SHA256 校验失败!")
+ appendLog(" 期望: ${manifestSha256.take(32)}...")
+ appendLog(" 实际: ${actualSha256.take(32)}...")
}
appendLog("=== OTA 测试完成 ===")
diff --git a/packages/client-android/android/app/src/main/res/values/strings.xml b/packages/client-android/android/app/src/main/res/values/strings.xml
index 997f9bd..548c347 100644
--- a/packages/client-android/android/app/src/main/res/values/strings.xml
+++ b/packages/client-android/android/app/src/main/res/values/strings.xml
@@ -1,4 +1,4 @@
- Yuzu GCA
+ Yuzu GCA v0.1.0
diff --git a/packages/client-android/manifest.json b/packages/client-android/manifest.json
index 3fbac2b..1c24a26 100644
--- a/packages/client-android/manifest.json
+++ b/packages/client-android/manifest.json
@@ -3,10 +3,10 @@
"builds": {
"android": {
"type": "apk",
- "tag": "v0.1.0-dav-android-1",
- "url": "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-1/gca-0.1.0.apk",
- "sha256": "PLACEHOLDER",
- "size": 0,
+ "tag": "v0.1.0-dav-android-6",
+ "url": "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-6/gca-v0.1.0.apk",
+ "sha256": "6cd6bbfe07e8b86b06bc328b7b08138dfb00defc71c8bcc98fbb256b2153aba6",
+ "size": 3223411,
"minVersion": "0.1.0"
}
}
diff --git a/packages/client-android/modules/package-installer/android/src/main/java/dev/yuzu/gca/PackageInstallerModule.kt b/packages/client-android/modules/package-installer/android/src/main/java/dev/yuzu/gca/PackageInstallerModule.kt
deleted file mode 100644
index 6027c68..0000000
--- a/packages/client-android/modules/package-installer/android/src/main/java/dev/yuzu/gca/PackageInstallerModule.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package dev.yuzu.gca
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.pm.PackageInstaller
-import android.content.pm.PackageManager
-import expo.modules.kotlin.modules.Module
-import expo.modules.kotlin.modules.ModuleDefinition
-import java.io.File
-import java.io.FileInputStream
-import java.security.DigestInputStream
-import java.security.MessageDigest
-
-class PackageInstallerModule : Module() {
- override fun definition() = ModuleDefinition {
- Name("PackageInstallerModule")
-
- AsyncFunction("installApk") { filePath: String, expectedSha256: String ->
- installApk(filePath, expectedSha256)
- }
- }
-
- private fun installApk(filePath: String, expectedSha256: String) {
- val context: Context = appContext.reactContext ?: return
- val apkFile = File(filePath)
- if (!apkFile.exists()) throw Exception("安装包不存在")
-
- if (!context.packageManager.canRequestPackageInstalls()) {
- throw SecurityException("REQUEST_INSTALL_PACKAGES 权限未授予")
- }
-
- val packageInstaller = context.packageManager.packageInstaller
- val sessionParams = PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL
- )
-
- val sessionId = packageInstaller.createSession(sessionParams)
- val session = packageInstaller.openSession(sessionId)
-
- try {
- // 边写入边计算 SHA256(原子化,避免 TOCTOU)
- val sha256 = MessageDigest.getInstance("SHA-256")
- FileInputStream(apkFile).use { input ->
- val digestStream = DigestInputStream(input, sha256)
- session.openWrite("package", 0, apkFile.length()).use { output ->
- digestStream.copyTo(output)
- session.fsync(output)
- }
- }
-
- val actualSha256 = sha256.digest().joinToString("") { "%02x".format(it) }
- if (actualSha256 != expectedSha256) {
- throw SecurityException("SHA256校验失败")
- }
-
- val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
- ?: throw Exception("无法获取启动Intent")
- val pendingIntent = PendingIntent.getActivity(
- context, 0, launchIntent,
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
- )
-
- session.commit(pendingIntent.intentSender)
- } finally {
- session.close()
- }
- }
-}
diff --git a/packages/client-android/modules/package-installer/expo-module.config.json b/packages/client-android/modules/package-installer/expo-module.config.json
deleted file mode 100644
index 93f3acd..0000000
--- a/packages/client-android/modules/package-installer/expo-module.config.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "platform": "android",
- "modules": ["PackageInstallerModule"]
-}