feat(manager): root installer

This commit is contained in:
rhunk
2023-10-30 19:57:33 +01:00
parent 2047f32440
commit 407e69d184
14 changed files with 273 additions and 86 deletions

View File

@@ -1,6 +1,7 @@
[versions]
agp = "8.1.2"
apksig = "8.0.2"
libsu = "5.2.1"
guava = "32.1.3-jre"
jsoup = "1.16.2"
kotlin = "1.9.0"
@@ -40,6 +41,7 @@ apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
bcprov-jdk18on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bcprov-jdk18on" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil-compose" }
coil-video = { module = "io.coil-kt:coil-video", version.ref = "coil-compose" }
libsu = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines-android" }
dexlib2 = { group = "org.smali", name = "dexlib2", version.ref = "dexlib2" }
ffmpeg-kit = { group = "com.arthenica", name = "ffmpeg-kit-full-gpl", version.ref = "ffmpeg-kit" }

View File

@@ -72,6 +72,7 @@ configurations {
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(libs.libsu)
implementation(libs.guava)
implementation(libs.apksig)
implementation(libs.gson)

View File

@@ -1,3 +1,3 @@
-dontwarn com.google.errorprone.annotations.**
-dontwarn com.google.auto.value.**
-keep enum me.rhunk.snapenhance.manager.** { *; }
-keep class me.rhunk.snapenhance.manager.ui.tab.** { *; }

View File

@@ -18,4 +18,7 @@ class SharedConfig(
var snapEnhancePackageName get() = sharedPreferences.getString("snapEnhancePackageName", "me.rhunk.snapenhance")?.takeIf { it.isNotEmpty() } ?: "me.rhunk.snapenhance"
set(value) = sharedPreferences.edit().putString("snapEnhancePackageName", value).apply()
var useRootInstaller get() = sharedPreferences.getBoolean("useRootInstaller", false)
set(value) = sharedPreferences.edit().putBoolean("useRootInstaller", value).apply()
}

View File

@@ -9,10 +9,13 @@ import androidx.compose.material3.*
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.rememberNavController
import com.topjohnwu.superuser.Shell
import me.rhunk.snapenhance.manager.BuildConfig
import me.rhunk.snapenhance.manager.data.SharedConfig
import me.rhunk.snapenhance.manager.ui.tab.HomeTab
import me.rhunk.snapenhance.manager.ui.tab.SettingsTab
import me.rhunk.snapenhance.manager.ui.tab.download.InstallPackageTab
import me.rhunk.snapenhance.manager.ui.tab.Tab
import me.rhunk.snapenhance.manager.ui.tab.impl.HomeTab
import me.rhunk.snapenhance.manager.ui.tab.impl.SettingsTab
import me.rhunk.snapenhance.manager.ui.tab.impl.download.InstallPackageTab
class MainActivity : ComponentActivity() {
companion object{
@@ -22,6 +25,11 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Shell.enableVerboseLogging = BuildConfig.DEBUG;
Shell.setDefaultBuilder(Shell.Builder.create()
.setFlags(Shell.FLAG_REDIRECT_STDERR)
.setTimeout(10)
);
val tabs = primaryTabs.mapNotNull {
runCatching { it.java.constructors.first().newInstance() as Tab }.getOrNull()
}.toMutableList().apply {

View File

@@ -20,6 +20,7 @@ import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState
import me.rhunk.snapenhance.manager.ui.tab.Tab
import kotlin.reflect.KClass

View File

@@ -1,4 +1,4 @@
package me.rhunk.snapenhance.manager.ui
package me.rhunk.snapenhance.manager.ui.tab
import android.os.Bundle
import android.widget.Toast
@@ -8,6 +8,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import me.rhunk.snapenhance.manager.data.SharedConfig
import me.rhunk.snapenhance.manager.ui.Navigation
import kotlin.reflect.KClass
open class Tab(

View File

@@ -1,4 +1,4 @@
package me.rhunk.snapenhance.manager.ui.tab
package me.rhunk.snapenhance.manager.ui.tab.impl
import android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE
import android.content.pm.PackageInfo
@@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
@@ -26,9 +28,9 @@ import androidx.compose.ui.unit.sp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.rhunk.snapenhance.manager.lspatch.config.Constants
import me.rhunk.snapenhance.manager.ui.Tab
import me.rhunk.snapenhance.manager.ui.tab.download.SEDownloadTab
import me.rhunk.snapenhance.manager.ui.tab.download.SnapchatPatchTab
import me.rhunk.snapenhance.manager.ui.tab.Tab
import me.rhunk.snapenhance.manager.ui.tab.impl.download.SEDownloadTab
import me.rhunk.snapenhance.manager.ui.tab.impl.download.SnapchatPatchTab
class HomeTab : Tab("home", true, icon = Icons.Default.Home) {
override fun init(activity: ComponentActivity) {
@@ -106,19 +108,22 @@ class HomeTab : Tab("home", true, icon = Icons.Default.Home) {
Row(
modifier = Modifier
.weight(1f),
horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.End),
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.End),
verticalAlignment = Alignment.CenterVertically
) {
snapchatAppInfo?.let { appInfo ->
val isLSPatched = appInfo.applicationInfo.appComponentFactory == Constants.PROXY_APP_COMPONENT_FACTORY
if (isLSPatched) {
Text(text = "Patched", fontSize = 16.sp, color = MaterialTheme.colorScheme.onSurface)
Icon(imageVector = Icons.Default.Check, contentDescription = null)
Text(text = "Patched", fontSize = 16.sp)
} else {
Icon(imageVector = Icons.Default.Close, contentDescription = null)
Text(text = "Not patched", fontSize = 16.sp, color = MaterialTheme.colorScheme.onSurface)
}
} ?: run {
Text(text = "Not installed", fontSize = 16.sp, color = MaterialTheme.colorScheme.onSurface)
}
Icon(imageVector = Icons.AutoMirrored.Default.OpenInNew, contentDescription = null, Modifier.padding(10.dp))
}
}

View File

@@ -1,4 +1,4 @@
package me.rhunk.snapenhance.manager.ui.tab
package me.rhunk.snapenhance.manager.ui.tab.impl
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@@ -21,7 +21,7 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import me.rhunk.snapenhance.manager.ui.Tab
import me.rhunk.snapenhance.manager.ui.tab.Tab
class SettingsTab : Tab("settings", isPrimary = true, icon = Icons.Default.Settings) {
@Composable
@@ -98,6 +98,34 @@ class SettingsTab : Tab("settings", isPrimary = true, icon = Icons.Default.Setti
}
}
@Composable
private fun ConfigBooleanRow(getValue: () -> Boolean, setValue: (Boolean) -> Unit, label: String) {
var value by remember { mutableStateOf(getValue()) }
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
value = !value
setValue(value)
},
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.weight(1f)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(text = label, fontSize = 16.sp)
}
Checkbox(checked = value, onCheckedChange = {
value = it
setValue(it)
}, modifier = Modifier.padding(5.dp))
}
}
@Composable
override fun Content() {
Column {
@@ -105,12 +133,17 @@ class SettingsTab : Tab("settings", isPrimary = true, icon = Icons.Default.Setti
ConfigEditRow(
getValue = { sharedConfig.snapchatPackageName },
setValue = { sharedConfig.snapchatPackageName = it },
label = "Snapchat package name"
label = "Override Snapchat package name"
)
ConfigEditRow(
getValue = { sharedConfig.snapEnhancePackageName },
setValue = { sharedConfig.snapEnhancePackageName = it },
label = "SnapEnhance package name"
label = "Override SnapEnhance package name"
)
ConfigBooleanRow(
getValue = { sharedConfig.useRootInstaller },
setValue = { sharedConfig.useRootInstaller = it },
label = "Use root installer"
)
}
}

View File

@@ -1,4 +1,4 @@
package me.rhunk.snapenhance.manager.ui.tab.download
package me.rhunk.snapenhance.manager.ui.tab.impl.download
import android.content.Intent
import android.net.Uri
@@ -18,11 +18,12 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.rhunk.snapenhance.manager.data.download.InstallStage
import me.rhunk.snapenhance.manager.ui.Tab
import me.rhunk.snapenhance.manager.ui.tab.HomeTab
import me.rhunk.snapenhance.manager.ui.tab.Tab
import me.rhunk.snapenhance.manager.ui.tab.impl.HomeTab
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
@@ -31,16 +32,18 @@ import java.io.File
class InstallPackageTab : Tab("install_app") {
private lateinit var installPackageIntentLauncher: ActivityResultLauncher<Intent>
private lateinit var uninstallPackageIntentLauncher: ActivityResultLauncher<Intent>
private var uninstallPackageCallback: ((resultCode: Int, data: Intent?) -> Unit)? = null
private var installPackageCallback: ((resultCode: Int, data: Intent?) -> Unit)? = null
private var uninstallPackageCallback: ((resultCode: Int) -> Unit)? = null
private var installPackageCallback: ((resultCode: Int) -> Unit)? = null
private val hasRoot get() = sharedConfig.useRootInstaller
override fun init(activity: ComponentActivity) {
super.init(activity)
installPackageIntentLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
installPackageCallback?.invoke(it.resultCode, it.data)
installPackageCallback?.invoke(it.resultCode)
}
uninstallPackageIntentLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
uninstallPackageCallback?.invoke(it.resultCode, it.data)
uninstallPackageCallback?.invoke(it.resultCode)
}
}
@@ -135,9 +138,36 @@ class InstallPackageTab : Tab("install_app") {
}
}
fun uninstallPackageRoot(): Boolean {
val result = Shell.su("pm uninstall $appPackage").exec()
if (result.isSuccess) {
return true
}
toast("Root uninstall failed: ${result.out}")
return false
}
fun installPackageRoot(): Boolean {
val result = Shell.su(
"cp \"${downloadedFile!!.absolutePath}\" /data/local/tmp/",
"pm install -r \"/data/local/tmp/${downloadedFile!!.name}\"",
"rm /data/local/tmp/${downloadedFile!!.name}"
).exec()
if (result.isSuccess) {
installStage = InstallStage.DONE
return true
}
toast("Root install failed: ${result.out}")
return false
}
fun installPackage() {
installStage = InstallStage.INSTALLING
installPackageCallback = resultCallbacks@{ code, _ ->
if (hasRoot && installPackageRoot()) {
downloadedFile?.delete()
return
}
installPackageCallback = resultCallbacks@{ code ->
installStage = if (code != ComponentActivity.RESULT_OK) {
InstallStage.ERROR
} else {
@@ -163,11 +193,15 @@ class InstallPackageTab : Tab("install_app") {
}
if (shouldUninstall) {
installStage = InstallStage.UNINSTALLING
if (hasRoot && uninstallPackageRoot()) {
installPackage()
return@launch
}
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE).apply {
data = "package:$appPackage".toUri()
putExtra(Intent.EXTRA_RETURN_RESULT, true)
}
uninstallPackageCallback = resultCallback@{ resultCode, _ ->
uninstallPackageCallback = resultCallback@{ resultCode ->
if (resultCode != ComponentActivity.RESULT_OK) {
installStage = InstallStage.ERROR
downloadedFile?.delete()

View File

@@ -1,4 +1,4 @@
package me.rhunk.snapenhance.manager.ui.tab.download
package me.rhunk.snapenhance.manager.ui.tab.impl.download
import android.os.Bundle
import androidx.compose.foundation.layout.*
@@ -20,60 +20,64 @@ import kotlinx.coroutines.launch
import me.rhunk.snapenhance.manager.data.APKMirror
import me.rhunk.snapenhance.manager.data.DownloadItem
import me.rhunk.snapenhance.manager.lspatch.LSPatch
import me.rhunk.snapenhance.manager.ui.Tab
import me.rhunk.snapenhance.manager.ui.components.DowngradeNoticeDialog
import me.rhunk.snapenhance.manager.ui.tab.Tab
import okio.use
import java.io.File
class LSPatchTab : Tab("lspatch") {
private lateinit var downloadItem: DownloadItem
private var localItemFile: File? = null
private var downloadItem: DownloadItem? = null
private var snapEnhanceModule: File? = null
private var patchedApk by mutableStateOf<File?>(null)
private val apkMirror = APKMirror()
private fun patch(log: (Any?) -> Unit, onProgress: (Float) -> Unit) {
log("Fetching download link for ${downloadItem.title}...")
val downloadLink = apkMirror.fetchDownloadLink(downloadItem.downloadPage) ?: run {
log("== Failed to fetch download link ==")
return
}
var apkFile: File? = localItemFile
log("Downloading apk...")
val downloadResponse = apkMirror.okhttpClient.newCall(
okhttp3.Request.Builder()
.url(downloadLink)
.build()
).execute()
if (!downloadResponse.isSuccessful) {
log("== Failed to download apk ==")
log("Response code: ${downloadResponse.code}")
return
}
val apkFile = sharedConfig.apkCache.resolve("${downloadItem.hash}.apk")
apkFile.outputStream().use { outputStream ->
runCatching {
downloadResponse.body.byteStream().use { inputStream ->
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var read: Int
var totalRead = 0L
val totalSize = downloadResponse.body.contentLength()
while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
totalRead += read
onProgress(totalRead.toFloat() / totalSize.toFloat())
}
}
}.onFailure {
log("== Failed to download apk ==")
log(it)
downloadItem?.let {
log("Fetching download link for ${it.title}...")
val downloadLink = apkMirror.fetchDownloadLink(it.downloadPage) ?: run {
log("== Failed to fetch download link ==")
return
}
}
log("Downloading apk...")
apkFile.renameTo(File(activity.externalCacheDir!!, "base.apk"))
val downloadResponse = apkMirror.okhttpClient.newCall(
okhttp3.Request.Builder()
.url(downloadLink)
.build()
).execute()
if (!downloadResponse.isSuccessful) {
log("== Failed to download apk ==")
log("Response code: ${downloadResponse.code}")
return
}
apkFile = sharedConfig.apkCache.resolve("${it.hash}.apk")
apkFile!!.outputStream().use { outputStream ->
runCatching {
downloadResponse.body.byteStream().use { inputStream ->
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var read: Int
var totalRead = 0L
val totalSize = downloadResponse.body.contentLength()
while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
totalRead += read
onProgress(totalRead.toFloat() / totalSize.toFloat())
}
}
}.onFailure { throwable ->
log("== Failed to download apk ==")
log(throwable)
return
}
}
apkFile!!.renameTo(File(activity.externalCacheDir!!, "base.apk"))
}
log("== Downloaded apk ==")
snapEnhanceModule?.let { module ->
@@ -84,13 +88,13 @@ class LSPatchTab : Tab("lspatch") {
})
log("== Patching apk ==")
val outputFiles = lsPatch.patchSplits(listOf(apkFile))
val outputFiles = lsPatch.patchSplits(listOf(apkFile!!))
patchedApk = outputFiles["base.apk"] ?: run {
log("== Failed to patch apk ==")
return
}
return@let
return
}
patchedApk = apkFile
}
@@ -98,7 +102,8 @@ class LSPatchTab : Tab("lspatch") {
@Composable
@Suppress("DEPRECATION")
override fun Content() {
this.downloadItem = remember { getArguments()?.getParcelable<DownloadItem>("downloadItem") } ?: return
this.localItemFile = remember { getArguments()?.getString("localItemFile")?.let { File(it) } }
this.downloadItem = remember { getArguments()?.getParcelable("downloadItem") }
this.snapEnhanceModule = remember {
getArguments()?.getString("modulePath")?.let {
File(it)
@@ -111,7 +116,7 @@ class LSPatchTab : Tab("lspatch") {
var status by remember { mutableStateOf("") }
var progress by remember { mutableFloatStateOf(-1f) }
LaunchedEffect(this.downloadItem.hash) {
LaunchedEffect(this.snapEnhanceModule) {
patchedApk = null
coroutineScope.launch(Dispatchers.IO) {
runCatching {

View File

@@ -1,4 +1,4 @@
package me.rhunk.snapenhance.manager.ui.tab.download
package me.rhunk.snapenhance.manager.ui.tab.impl.download
import android.os.Bundle
import androidx.activity.ComponentActivity
@@ -26,8 +26,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.rhunk.snapenhance.manager.data.download.SEArtifact
import me.rhunk.snapenhance.manager.data.download.SEVersion
import me.rhunk.snapenhance.manager.ui.Tab
import me.rhunk.snapenhance.manager.ui.components.DowngradeNoticeDialog
import me.rhunk.snapenhance.manager.ui.tab.Tab
import okhttp3.OkHttpClient
import okhttp3.Request
import java.text.SimpleDateFormat

View File

@@ -1,10 +1,12 @@
package me.rhunk.snapenhance.manager.ui.tab.download
package me.rhunk.snapenhance.manager.ui.tab.impl.download
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.DeleteForever
@@ -21,8 +23,11 @@ import kotlinx.coroutines.withContext
import me.rhunk.snapenhance.manager.R
import me.rhunk.snapenhance.manager.data.APKMirror
import me.rhunk.snapenhance.manager.data.DownloadItem
import me.rhunk.snapenhance.manager.ui.Tab
import me.rhunk.snapenhance.manager.lspatch.config.Constants
import me.rhunk.snapenhance.manager.ui.components.ConfirmationDialog
import me.rhunk.snapenhance.manager.ui.tab.Tab
import java.io.File
import java.util.zip.ZipFile
@OptIn(ExperimentalMaterial3Api::class)
class SnapchatPatchTab : Tab("snapchat_download") {
@@ -145,10 +150,71 @@ class SnapchatPatchTab : Tab("snapchat_download") {
@Composable
override fun Content() {
var showSelectSnapchatVersionDialog by remember { mutableStateOf(false) }
var showRestoreMenuDialog by remember { mutableStateOf(false) }
var selectedSnapchatVersion by remember { mutableStateOf(null as DownloadItem?) }
val installedSnapEnhanceVersion = remember { runCatching { activity.packageManager.getPackageInfo(
sharedConfig.snapEnhancePackageName, 0) }.getOrNull() }
val installedSnapchatPackage = remember { runCatching { activity.packageManager.getPackageInfo(
sharedConfig.snapchatPackageName, 0) }.getOrNull() }
val isInstalledSnapchatPatched = remember { installedSnapchatPackage?.applicationInfo?.appComponentFactory == Constants.PROXY_APP_COMPONENT_FACTORY }
val isSnapchatNotSplitConfig = remember {
installedSnapchatPackage?.applicationInfo?.let { it.splitSourceDirs == null || it.splitSourceDirs?.isEmpty() == true } ?: false
}
if (showRestoreMenuDialog) {
fun triggerSnapchatInstallation(shouldUninstall: Boolean) {
val apkFile = File(installedSnapchatPackage?.applicationInfo?.sourceDir ?: return).also {
if (!it.exists()) return
}
toast("Extracting origin apk")
val originApk = File.createTempFile("origin", ".apk", activity.externalCacheDirs.first()).also {
it.deleteOnExit()
}
ZipFile(apkFile).let { zipFile ->
zipFile.getEntry("assets/lspatch/origin.apk")?.apply {
originApk.outputStream().use { output ->
zipFile.getInputStream(this).copyTo(output)
}
} ?: run {
toast("Failed to extract origin apk")
return
}
}
showRestoreMenuDialog = false
navigation.navigateTo(InstallPackageTab::class, args = Bundle().apply {
putString("downloadPath", originApk.absolutePath)
putString("appPackage", sharedConfig.snapchatPackageName)
putBoolean("uninstall", shouldUninstall)
}, noHistory = true)
}
AlertDialog(onDismissRequest = { showRestoreMenuDialog = false }) {
Card {
Column(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Button(onClick = {
triggerSnapchatInstallation(true)
}) {
Text("Uninstall & Install")
}
Button(onClick = {
triggerSnapchatInstallation(false)
}) {
Text("Update")
}
}
}
}
}
if (showSelectSnapchatVersionDialog) {
AlertDialog(onDismissRequest = { showSelectSnapchatVersionDialog = false }) {
SelectSnapchatVersionDialog {
@@ -159,11 +225,11 @@ class SnapchatPatchTab : Tab("snapchat_download") {
}
Column(
modifier = Modifier.fillMaxSize(),
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
Text("Select a version to download and patch")
Text("Patch Snapchat")
ElevatedCard(
modifier = Modifier.padding(10.dp),
@@ -203,6 +269,7 @@ class SnapchatPatchTab : Tab("snapchat_download") {
Column(
modifier = Modifier.padding(top = 10.dp, bottom = 10.dp, start = 20.dp, end = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
Button(
@@ -218,16 +285,42 @@ class SnapchatPatchTab : Tab("snapchat_download") {
Text("Download & Patch")
}
Button(
modifier = Modifier.fillMaxWidth(),
enabled = selectedSnapchatVersion != null,
onClick = {
navigation.navigateTo(LSPatchTab::class, args = Bundle().apply {
putParcelable("downloadItem", selectedSnapchatVersion)
}, noHistory = true)
if (isSnapchatNotSplitConfig) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
navigation.navigateTo(LSPatchTab::class, args = Bundle().apply {
putString("localItemFile", installedSnapchatPackage?.applicationInfo?.sourceDir ?: return@apply)
putString("modulePath", installedSnapEnhanceVersion?.applicationInfo?.sourceDir ?: return@apply)
}, noHistory = true)
}
) {
Text("Patch from existing installation")
}
}
if (isInstalledSnapchatPatched) {
Text("Restore Snapchat", modifier = Modifier.padding(20.dp))
Button(
modifier = Modifier.fillMaxWidth(),
enabled = selectedSnapchatVersion != null,
onClick = {
navigation.navigateTo(LSPatchTab::class, args = Bundle().apply {
putParcelable("downloadItem", selectedSnapchatVersion)
}, noHistory = true)
}
) {
Text("Install/Restore Original Snapchat")
}
if (isSnapchatNotSplitConfig) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { showRestoreMenuDialog = true }
) {
Text("Restore Snapchat from existing installation")
}
}
) {
Text("Install/Restore Original Snapchat")
}
}
}

View File

@@ -12,6 +12,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}