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"] -}