feat: patching screen

This commit is contained in:
Canny 2022-09-23 11:32:18 +03:00
parent 09e1082c5e
commit a1e172a852
No known key found for this signature in database
GPG Key ID: 395CCB0AA979F27B
18 changed files with 259 additions and 130 deletions

View File

@ -86,6 +86,7 @@ dependencies {
val koinVersion = "3.2.0" val koinVersion = "3.2.0"
implementation("io.insert-koin:koin-android:$koinVersion") implementation("io.insert-koin:koin-android:$koinVersion")
implementation("io.insert-koin:koin-androidx-compose:$koinVersion") implementation("io.insert-koin:koin-androidx-compose:$koinVersion")
implementation("io.insert-koin:koin-androidx-workmanager:3.2.1")
// Compose // Compose
val composeVersion = "1.3.0-alpha03" val composeVersion = "1.3.0-alpha03"
@ -117,7 +118,7 @@ dependencies {
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
// ReVanced // ReVanced
implementation("app.revanced:revanced-patcher:4.2.3") implementation("app.revanced:revanced-patcher:4.4.2")
// Coil for network image // Coil for network image
implementation("io.coil-kt:coil-compose:2.1.0") implementation("io.coil-kt:coil-compose:2.1.0")
@ -125,4 +126,5 @@ dependencies {
// Signing & aligning // Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
implementation("com.android.tools.build:apksig:7.2.2") implementation("com.android.tools.build:apksig:7.2.2")
} }

View File

@ -33,8 +33,19 @@
android:supportsRtl="true" android:supportsRtl="true"
android:largeHeap="true" android:largeHeap="true"
android:theme="@style/Theme.ReVancedManager" android:theme="@style/Theme.ReVancedManager"
tools:targetApi="33"> tools:targetApi="33">
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@ -15,6 +15,7 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import app.revanced.manager.preferences.PreferencesManager import app.revanced.manager.preferences.PreferencesManager
import app.revanced.manager.ui.navigation.AppDestination import app.revanced.manager.ui.navigation.AppDestination
import app.revanced.manager.ui.screen.MainDashboardScreen import app.revanced.manager.ui.screen.MainDashboardScreen
import app.revanced.manager.ui.screen.PatchingScreen
import app.revanced.manager.ui.screen.subscreens.AppSelectorSubscreen import app.revanced.manager.ui.screen.subscreens.AppSelectorSubscreen
import app.revanced.manager.ui.screen.subscreens.PatchesSelectorSubscreen import app.revanced.manager.ui.screen.subscreens.PatchesSelectorSubscreen
import app.revanced.manager.ui.theme.ReVancedManagerTheme import app.revanced.manager.ui.theme.ReVancedManagerTheme
@ -48,10 +49,9 @@ class MainActivity : ComponentActivity() {
) { destination -> ) { destination ->
when (destination) { when (destination) {
is AppDestination.Dashboard -> MainDashboardScreen(navigator = navigator) is AppDestination.Dashboard -> MainDashboardScreen(navigator = navigator)
is AppDestination.AppSelector -> AppSelectorSubscreen( is AppDestination.AppSelector -> AppSelectorSubscreen(navigator = navigator)
navigator = navigator
)
is AppDestination.PatchSelector -> PatchesSelectorSubscreen(navigator = navigator) is AppDestination.PatchSelector -> PatchesSelectorSubscreen(navigator = navigator)
is AppDestination.Patcher -> PatchingScreen(navigator = navigator)
} }
} }
} }

View File

@ -1,11 +1,9 @@
package app.revanced.manager package app.revanced.manager
import android.app.Application import android.app.Application
import app.revanced.manager.di.httpModule import app.revanced.manager.di.*
import app.revanced.manager.di.preferencesModule
import app.revanced.manager.di.repositoryModule
import app.revanced.manager.di.viewModelModule
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.androidx.workmanager.koin.workManagerFactory
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
class ManagerApplication : Application() { class ManagerApplication : Application() {
@ -14,7 +12,8 @@ class ManagerApplication : Application() {
startKoin { startKoin {
androidContext(this@ManagerApplication) androidContext(this@ManagerApplication)
modules(httpModule, preferencesModule, viewModelModule, repositoryModule) workManagerFactory()
modules(httpModule, preferencesModule, viewModelModule, repositoryModule, workerModule)
} }
} }
} }

View File

@ -29,7 +29,7 @@ val client = HttpClient(Android) {
class API(private val repository: GitHubRepository, private val prefs: PreferencesManager) { class API(private val repository: GitHubRepository, private val prefs: PreferencesManager) {
private suspend fun findAsset(repo: String, file: String): PatchesAsset { suspend fun findAsset(repo: String, file: String): PatchesAsset {
val release = repository.getLatestRelease(repo) val release = repository.getLatestRelease(repo)
val asset = release.assets.findAsset(file) ?: throw MissingAssetException() val asset = release.assets.findAsset(file) ?: throw MissingAssetException()
return PatchesAsset(release, asset) return PatchesAsset(release, asset)
@ -47,7 +47,6 @@ class API(private val repository: GitHubRepository, private val prefs: Preferenc
throw Exception("Failed to download patch bundle", e) throw Exception("Failed to download patch bundle", e)
} }
} }
suspend fun downloadIntegrations(workdir: File): File { suspend fun downloadIntegrations(workdir: File): File {
return try { return try {
val (_, out) = downloadAsset( val (_, out) = downloadAsset(
@ -60,7 +59,7 @@ class API(private val repository: GitHubRepository, private val prefs: Preferenc
} }
} }
private suspend fun downloadAsset( suspend fun downloadAsset(
workdir: File, workdir: File,
patchesAsset: PatchesAsset patchesAsset: PatchesAsset
): Pair<PatchesAsset, File> { ): Pair<PatchesAsset, File> {

View File

@ -1,15 +1,13 @@
package app.revanced.manager.di package app.revanced.manager.di
import app.revanced.manager.ui.viewmodel.AppSelectorViewModel import app.revanced.manager.ui.viewmodel.*
import app.revanced.manager.ui.viewmodel.DashboardViewModel
import app.revanced.manager.ui.viewmodel.PatcherViewModel
import app.revanced.manager.ui.viewmodel.SettingsViewModel
import org.koin.androidx.viewmodel.dsl.viewModelOf import org.koin.androidx.viewmodel.dsl.viewModelOf
import org.koin.dsl.module import org.koin.dsl.module
val viewModelModule = module { val viewModelModule = module {
viewModelOf(::SettingsViewModel) viewModelOf(::SettingsViewModel)
viewModelOf(::DashboardViewModel) viewModelOf(::DashboardViewModel)
viewModelOf(::PatcherViewModel) viewModelOf(::PatcherScreenViewModel)
viewModelOf(::AppSelectorViewModel) viewModelOf(::AppSelectorViewModel)
viewModelOf(::PatchingScreenViewModel)
} }

View File

@ -0,0 +1,10 @@
package app.revanced.manager.di
import app.revanced.manager.patcher.worker.PatcherWorker
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.workmanager.dsl.worker
import org.koin.dsl.module
val workerModule = module {
worker { PatcherWorker( androidContext(), get(), get())}
}

View File

@ -14,13 +14,16 @@ import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.Variables.patches import app.revanced.manager.Variables.patches
import app.revanced.manager.Variables.selectedAppPackage
import app.revanced.manager.Variables.selectedPatches import app.revanced.manager.Variables.selectedPatches
import app.revanced.manager.api.API
import app.revanced.manager.patcher.aapt.Aapt import app.revanced.manager.patcher.aapt.Aapt
import app.revanced.manager.patcher.aligning.ZipAligner import app.revanced.manager.patcher.aligning.ZipAligner
import app.revanced.manager.patcher.aligning.zip.ZipFile import app.revanced.manager.patcher.aligning.zip.ZipFile
import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry
import app.revanced.manager.patcher.signing.Signer import app.revanced.manager.patcher.signing.Signer
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
import app.revanced.manager.ui.viewmodel.Logging
import app.revanced.patcher.Patcher import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.data.Data import app.revanced.patcher.data.Data
@ -29,15 +32,19 @@ import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.logging.Logger import app.revanced.patcher.logging.Logger
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.impl.ResourcePatch import app.revanced.patcher.patch.impl.ResourcePatch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.StandardCopyOption
class PatcherWorker(context: Context, parameters: WorkerParameters, private val api: API): CoroutineWorker(context, parameters) ,KoinComponent {
class PatcherWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
val tag = "ReVanced Manager" val tag = "ReVanced Manager"
private val workdir = File(inputData.getString("workdir")!!) private val workdir = createWorkDir()
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
if (runAttemptCount > 0) { if (runAttemptCount > 0) {
return Result.failure( return Result.failure(
androidx.work.Data.Builder() androidx.work.Data.Builder()
@ -77,32 +84,41 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
} }
} }
private fun runPatcher( private suspend fun runPatcher(
workdir: File workdir: File
): Boolean { ): Boolean {
val aaptPath = Aapt.binary(applicationContext).absolutePath val aaptPath = Aapt.binary(applicationContext).absolutePath
val frameworkPath = val frameworkPath =
applicationContext.filesDir.resolve("framework").also { it.mkdirs() }.absolutePath applicationContext.filesDir.resolve("framework").also { it.mkdirs() }.absolutePath
val integrationsCacheDir =
applicationContext.filesDir.resolve("integrations-cache").also { it.mkdirs() }
val appInfo = applicationContext.packageManager.getApplicationInfo(selectedAppPackage.value.get(), 3)
Log.d(tag, "Checking prerequisites") Logging.log += "Checking prerequisites\n"
val patches = findPatchesByIds(selectedPatches) val patches = findPatchesByIds(selectedPatches)
if (patches.isEmpty()) return true if (patches.isEmpty()) return true
Log.d(tag, "Creating directories") Logging.log += "Creating directories\n"
val inputFile = File(applicationContext.filesDir, "input.apk")
File(inputData.getString("input")!!).copyTo(
applicationContext.filesDir.resolve("base.apk"),
true
)
val inputFile = File(applicationContext.filesDir, "base.apk")
val patchedFile = File(workdir, "patched.apk") val patchedFile = File(workdir, "patched.apk")
val outputFile = File(applicationContext.filesDir, "out.apk") val outputFile = File(applicationContext.filesDir, "output.apk")
val cacheDirectory = workdir.resolve("cache") val cacheDirectory = workdir.resolve("cache")
val integrations = workdir.resolve("integrations.apk")
Logging.log += "Downloading integrations\n"
val integrations = api.downloadIntegrations(integrationsCacheDir)
Logging.log += "Copying base.apk from device\n"
withContext(Dispatchers.IO) {
Files.copy(
File(appInfo.publicSourceDir).toPath(),
inputFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}
try { try {
Log.d(tag, "Creating patcher") Logging.log += "Decoding resources\n"
val patcher = Patcher( // start patcher val patcher = Patcher( // start patcher
PatcherOptions( PatcherOptions(
inputFile, inputFile,
@ -134,37 +150,42 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
Log.d(tag, "Adding ${patches.size} patch(es)") Log.d(tag, "Adding ${patches.size} patch(es)")
patcher.addPatches(patches) patcher.addPatches(patches)
Logging.log += "Merging integrations\n"
patcher.addFiles(listOf(integrations)) {} patcher.addFiles(listOf(integrations)) {}
patcher.applyPatches().forEach { (patch, result) -> patcher.applyPatches().forEach { (patch, result) ->
Logging.log += "Applying $patch\n"
if (result.isSuccess) { if (result.isSuccess) {
Log.i(tag, "[success] $patch") Logging.log += "$patch has been applied successfully\n"
return@forEach return@forEach
} }
Log.e(tag, "[error] $patch:", result.exceptionOrNull()!!) Logging.log += "Failed to apply $patch \n" + result.exceptionOrNull()!!
} }
Log.d(tag, "Saving file") Logging.log += "Saving file\n"
val result = patcher.save() // compile apk val result = patcher.save() // compile apk
if (patchedFile.exists()) Files.delete(patchedFile.toPath()) if (patchedFile.exists()) withContext(Dispatchers.IO) {
Files.delete(patchedFile.toPath())
}
ZipFile(patchedFile).use { fs -> // somehow this function is the most resource intensive ZipFile(patchedFile).use { fs -> // somehow this function is the most resource intensive
result.dexFiles.forEach { Log.d(tag, "Writing dex file ${it.name}") result.dexFiles.forEach { Logging.log += "Writing dex file ${it.name}\n"
fs.addEntryCompressData(ZipEntry.createWithName(it.name), it.stream.readBytes())} fs.addEntryCompressData(ZipEntry.createWithName(it.name), it.stream.readBytes())}
Logging.log += "Aligning apk!\n"
result.resourceFile?.let { result.resourceFile?.let {
fs.copyEntriesFromFileAligned(ZipFile(it), ZipAligner::getEntryAlignment) fs.copyEntriesFromFileAligned(ZipFile(it), ZipAligner::getEntryAlignment)
} }
fs.copyEntriesFromFileAligned(ZipFile(inputFile), ZipAligner::getEntryAlignment) fs.copyEntriesFromFileAligned(ZipFile(inputFile), ZipAligner::getEntryAlignment)
} }
Logging.log += "Signing apk\n"
Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outputFile) Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outputFile)
Log.i(tag, "Successfully patched into $outputFile") Logging.log += "Successfully patched!\n"
} finally { } finally {
Log.d(tag, "Deleting workdir") Log.d(tag, "Deleting workdir")
// workdir.deleteRecursively() workdir.deleteRecursively()
} }
return false return false
} }
@ -187,4 +208,8 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
} }
return false return false
} }
private fun createWorkDir(): File {
return applicationContext.filesDir.resolve("tmp-${System.currentTimeMillis()}")
.also { it.mkdirs() }
}
} }

View File

@ -10,14 +10,14 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.viewmodel.PatchClass import app.revanced.manager.ui.viewmodel.PatchClass
import app.revanced.manager.ui.viewmodel.PatcherViewModel import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
import app.revanced.patcher.annotation.Package import app.revanced.patcher.annotation.Package
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
@Composable @Composable
fun PatchCompatibilityDialog( fun PatchCompatibilityDialog(
patchClass: PatchClass, pvm: PatcherViewModel = getViewModel(), onClose: () -> Unit patchClass: PatchClass, pvm: PatcherScreenViewModel = getViewModel(), onClose: () -> Unit
) { ) {
val patch = patchClass.patch val patch = patchClass.patch
val packageName = pvm.getSelectedPackageInfo()?.packageName val packageName = pvm.getSelectedPackageInfo()?.packageName

View File

@ -21,6 +21,9 @@ sealed interface AppDestination : Destination {
@Parcelize @Parcelize
object PatchSelector : AppDestination object PatchSelector : AppDestination
@Parcelize
object Patcher : AppDestination
} }
@Parcelize @Parcelize

View File

@ -69,7 +69,8 @@ fun MainDashboardScreen(navigator: BackstackNavigator<AppDestination>) {
DashboardDestination.DASHBOARD -> DashboardScreen() DashboardDestination.DASHBOARD -> DashboardScreen()
DashboardDestination.PATCHER -> PatcherScreen( DashboardDestination.PATCHER -> PatcherScreen(
onClickAppSelector = { navigator.push(AppDestination.AppSelector) }, onClickAppSelector = { navigator.push(AppDestination.AppSelector) },
onClickPatchSelector = { navigator.push(AppDestination.PatchSelector) } onClickPatchSelector = { navigator.push(AppDestination.PatchSelector) },
onClickPatch = { navigator.push(AppDestination.Patcher) },
) )
DashboardDestination.SETTINGS -> SettingsScreen() DashboardDestination.SETTINGS -> SettingsScreen()
} }

View File

@ -16,7 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.revanced.manager.ui.viewmodel.PatcherViewModel import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -24,7 +24,7 @@ import org.koin.androidx.compose.getViewModel
fun NewPatcherScreen( fun NewPatcherScreen(
onClickAppSelector: () -> Unit, onClickAppSelector: () -> Unit,
onClickPatchSelector: () -> Unit, onClickPatchSelector: () -> Unit,
viewModel: PatcherViewModel = getViewModel() viewModel: PatcherScreenViewModel = getViewModel()
) { ) {
var validBundle = false var validBundle = false
Column( Column(

View File

@ -4,14 +4,12 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.Build
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.Variables.patches import app.revanced.manager.Variables.patches
@ -20,7 +18,7 @@ import app.revanced.manager.Variables.selectedPatches
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
import app.revanced.manager.ui.component.FloatingActionButton import app.revanced.manager.ui.component.FloatingActionButton
import app.revanced.manager.ui.component.SplitAPKDialog import app.revanced.manager.ui.component.SplitAPKDialog
import app.revanced.manager.ui.viewmodel.PatcherViewModel import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -28,7 +26,8 @@ import org.koin.androidx.compose.getViewModel
fun PatcherScreen( fun PatcherScreen(
onClickAppSelector: () -> Unit, onClickAppSelector: () -> Unit,
onClickPatchSelector: () -> Unit, onClickPatchSelector: () -> Unit,
viewModel: PatcherViewModel = getViewModel() onClickPatch: () -> Unit,
viewModel: PatcherScreenViewModel = getViewModel()
) { ) {
val selectedAmount = selectedPatches.size val selectedAmount = selectedPatches.size
val selectedAppPackage by selectedAppPackage val selectedAppPackage by selectedAppPackage
@ -39,9 +38,7 @@ fun PatcherScreen(
Scaffold(floatingActionButton = { Scaffold(floatingActionButton = {
FloatingActionButton( FloatingActionButton(
enabled = hasAppSelected && viewModel.anyPatchSelected(), enabled = hasAppSelected && viewModel.anyPatchSelected(),
onClick = { onClick = { if (viewModel.checkSplitApk()) { showDialog = true } else onClickPatch()},
if (viewModel.checkSplitApk()) { showDialog = true } else viewModel.startPatcher()
},
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") }, icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
text = { Text(text = "Patch") } text = { Text(text = "Patch") }
) )
@ -53,7 +50,7 @@ fun PatcherScreen(
.padding(16.dp), .padding(16.dp),
) { ) {
if (showDialog) if (showDialog)
SplitAPKDialog(onDismiss = { showDialog = false }, onConfirm = { viewModel.startPatcher() }) SplitAPKDialog(onDismiss = { showDialog = false }, onConfirm = onClickPatch)
Card( Card(
modifier = Modifier modifier = Modifier
.padding(4.dp) .padding(4.dp)

View File

@ -0,0 +1,74 @@
package app.revanced.manager.ui.screen
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import app.revanced.manager.ui.navigation.AppDestination
import app.revanced.manager.ui.viewmodel.Logging
import app.revanced.manager.ui.viewmodel.PatchingScreenViewModel
import com.xinto.taxi.BackstackNavigator
import org.koin.androidx.compose.getViewModel
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun PatchingScreen(
navigator: BackstackNavigator<AppDestination>,
vm: PatchingScreenViewModel = getViewModel()
) {
Scaffold(
topBar = {
Row {
IconButton(onClick = navigator::pop) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = null
)
}
}
}
) { paddingValues ->
Column {
Column(
modifier =
Modifier
.fillMaxWidth()
.fillMaxHeight(0.2f)
.padding(paddingValues),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
if (vm.patchingInProgress) {
CircularProgressIndicator(modifier = Modifier.padding(vertical = 16.dp).size(30.dp))
Text(text = "Patching...", fontSize = 30.sp)
}
}
}
Column(
Modifier
.fillMaxWidth()
.padding(20.dp)
) {
Card {
Text(
text = Logging.log,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 10.dp).fillMaxSize(),
fontSize = 20.sp,
lineHeight = 35.sp
)
}
}
}
}
}

View File

@ -23,7 +23,7 @@ import app.revanced.manager.ui.component.PatchCompatibilityDialog
import app.revanced.manager.ui.navigation.AppDestination import app.revanced.manager.ui.navigation.AppDestination
import app.revanced.manager.ui.theme.Typography import app.revanced.manager.ui.theme.Typography
import app.revanced.manager.ui.viewmodel.PatchClass import app.revanced.manager.ui.viewmodel.PatchClass
import app.revanced.manager.ui.viewmodel.PatcherViewModel import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
import app.revanced.patcher.extensions.PatchExtensions.description import app.revanced.patcher.extensions.PatchExtensions.description
import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.extensions.PatchExtensions.version import app.revanced.patcher.extensions.PatchExtensions.version
@ -34,7 +34,7 @@ import org.koin.androidx.compose.getViewModel
@Composable @Composable
fun PatchesSelectorSubscreen( fun PatchesSelectorSubscreen(
navigator: BackstackNavigator<AppDestination>, navigator: BackstackNavigator<AppDestination>,
pvm: PatcherViewModel = getViewModel(), pvm: PatcherScreenViewModel = getViewModel(),
) { ) {
val patches = rememberSaveable { pvm.getFilteredPatchesAndCheckOptions() } val patches = rememberSaveable { pvm.getFilteredPatchesAndCheckOptions() }
var query by mutableStateOf("") var query by mutableStateOf("")

View File

@ -6,15 +6,10 @@ import android.os.Parcelable
import android.util.Log import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import app.revanced.manager.Variables.patches import app.revanced.manager.Variables.patches
import app.revanced.manager.Variables.selectedAppPackage import app.revanced.manager.Variables.selectedAppPackage
import app.revanced.manager.Variables.selectedPatches import app.revanced.manager.Variables.selectedPatches
import app.revanced.manager.api.API import app.revanced.manager.api.API
import app.revanced.manager.patcher.worker.PatcherWorker
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
import app.revanced.patcher.data.Data import app.revanced.patcher.data.Data
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
@ -24,31 +19,22 @@ import app.revanced.patcher.patch.Patch
import app.revanced.patcher.util.patch.impl.DexPatchBundle import app.revanced.patcher.util.patch.impl.DexPatchBundle
import dalvik.system.DexClassLoader import dalvik.system.DexClassLoader
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import java.io.File
class PatcherViewModel(private val app: Application, private val api: API) : ViewModel() { class PatcherScreenViewModel(private val app: Application, private val api: API) : ViewModel() {
private val workdir = createWorkDir()
private lateinit var patchBundleFile: String private lateinit var patchBundleFile: String
private val tag = "ReVanced Manager" private val tag = "ReVanced Manager"
init { init {
runBlocking { viewModelScope.launch {
loadPatches() loadPatches()
downloadIntegrations()
} }
} }
fun selectPatch(patchId: String, state: Boolean) { fun selectPatch(patchId: String, state: Boolean) {
if (state) selectedPatches.add(patchId) if (state) selectedPatches.add(patchId)
else selectedPatches.remove(patchId) else selectedPatches.remove(patchId)
} }
private suspend fun downloadIntegrations() {
api.downloadIntegrations(workdir).renameTo(File(workdir,"integrations.apk"))
}
fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) { fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) {
patchList.forEach { patch -> patchList.forEach { patch ->
val patchId = patch.patch.patchName val patchId = patch.patch.patchName
@ -85,6 +71,38 @@ class PatcherViewModel(private val app: Application, private val api: API) : Vie
) )
else null else null
fun checkSplitApk(): Boolean {
if (getSelectedPackageInfo()!!.applicationInfo!!.metaData!!.getBoolean("com.android.vending.splits.required", false)) {
Log.d(tag, "APK is split.")
return true
}
Log.d(tag, "APK is not split.")
return false
}
private fun loadPatches() = viewModelScope.launch {
try {
val file = api.downloadPatchBundle(app.filesDir)
patchBundleFile = file.absolutePath
loadPatches0(file.absolutePath)
} catch (e: Exception) {
Log.e("ReVancedManager", "An error occurred while loading patches", e)
}
}
private fun loadPatches0(path: String) {
val patchClasses = DexPatchBundle(
path, DexClassLoader(
path,
app.codeCacheDir.absolutePath,
null,
javaClass.classLoader
)
).loadPatches()
patches.value = Resource.Success(patchClasses)
Log.d("ReVanced Manager", "Finished loading patches")
}
fun getFilteredPatchesAndCheckOptions(): List<PatchClass> { fun getFilteredPatchesAndCheckOptions(): List<PatchClass> {
return buildList { return buildList {
val selected = getSelectedPackageInfo() ?: return@buildList val selected = getSelectedPackageInfo() ?: return@buildList
@ -108,61 +126,6 @@ class PatcherViewModel(private val app: Application, private val api: API) : Vie
} }
} }
} }
fun checkSplitApk(): Boolean {
if (getSelectedPackageInfo()!!.applicationInfo!!.metaData!!.getBoolean("com.android.vending.splits.required", false)) {
Log.d(tag, "APK is split.")
return true
}
Log.d(tag, "APK is not split.")
return false
}
private fun loadPatches() = viewModelScope.launch {
try {
val file = api.downloadPatchBundle(app.filesDir)
patchBundleFile = file.absolutePath
loadPatches0(file.absolutePath)
} catch (e: Exception) {
Log.e("ReVancedManager", "An error occurred while loading patches", e)
}
}
private fun loadPatches0(path: String) {
val patchClasses = DexPatchBundle(
path, DexClassLoader(
path,
app.codeCacheDir.absolutePath,
null,
javaClass.classLoader
)
).loadPatches()
patches.value = Resource.Success(patchClasses)
Log.d("ReVanced Manager", "Finished loading patches")
}
fun startPatcher() {
WorkManager
.getInstance(app)
.enqueueUniqueWork(
"patching",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequest.Builder(PatcherWorker::class.java)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setInputData(
androidx.work.Data.Builder()
.putString("workdir", workdir.toString())
.putString("input",
getSelectedPackageInfo()?.applicationInfo?.publicSourceDir
)
.build()).build()
)
}
private fun createWorkDir(): File {
return app.filesDir.resolve("tmp-${System.currentTimeMillis()}")
.also { it.mkdirs() }
}
} }
@Parcelize @Parcelize

View File

@ -0,0 +1,46 @@
package app.revanced.manager.ui.viewmodel
import android.app.Application
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import app.revanced.manager.patcher.worker.PatcherWorker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class PatchingScreenViewModel(val app: Application) : ViewModel() {
var patchingInProgress = true
init {
viewModelScope.launch(Dispatchers.Main) {
startPatcher()
}
}
private fun startPatcher() {
WorkManager
.getInstance(app)
.enqueueUniqueWork(
"patching",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequest.Builder(PatcherWorker::class.java)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setInputData(
androidx.work.Data.Builder()
.build()
).build()
)
patchingInProgress = false
}
}
object Logging {
var log by mutableStateOf("")
}

View File

@ -5,4 +5,5 @@ const val ghOrganization = "https://github.com/$team"
const val ghPatches = "$team/revanced-patches" const val ghPatches = "$team/revanced-patches"
const val ghPatcher = "$team/revanced-patcher" const val ghPatcher = "$team/revanced-patcher"
const val ghManager = "$team/revanced-manager" const val ghManager = "$team/revanced-manager"
const val ghIntegrations = "$team/revanced-integrations" const val ghIntegrations = "$team/revanced-integrations"
const val tag = "ReVanced Manager"