mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-31 14:51:03 +00:00
migrate to patcher v22
This commit is contained in:
@@ -23,6 +23,10 @@ kotlin {
|
||||
jvmToolchain(17)
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xexplicit-backing-fields",
|
||||
"-Xcontext-parameters",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -260,6 +260,10 @@ kotlin {
|
||||
jvmToolchain(17)
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xexplicit-backing-fields",
|
||||
"-Xcontext-parameters",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import app.revanced.manager.patcher.patch.Option
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
@@ -36,7 +34,7 @@ import kotlin.reflect.typeOf
|
||||
data class Option(
|
||||
@ColumnInfo(name = "group") val group: Int,
|
||||
@ColumnInfo(name = "patch_name") val patchName: String,
|
||||
@ColumnInfo(name = "key") val key: String,
|
||||
@ColumnInfo(name = "key") val name: String,
|
||||
// Encoded as Json.
|
||||
@ColumnInfo(name = "value") val value: SerializedValue,
|
||||
) {
|
||||
|
||||
@@ -35,15 +35,15 @@ class PatchOptionsRepository(db: AppDatabase) {
|
||||
bundlePatchOptions.getOrPut(dbOption.patchName, ::mutableMapOf)
|
||||
|
||||
val option =
|
||||
bundlePatches[sourceUid]?.get(dbOption.patchName)?.options?.find { it.key == dbOption.key }
|
||||
bundlePatches[sourceUid]?.get(dbOption.patchName)?.options?.find { it.name == dbOption.name }
|
||||
if (option != null) {
|
||||
try {
|
||||
deserializedPatchOptions[option.key] =
|
||||
deserializedPatchOptions[option.name] =
|
||||
dbOption.value.deserializeFor(option)
|
||||
} catch (e: Option.SerializationException) {
|
||||
Log.w(
|
||||
tag,
|
||||
"Option ${dbOption.patchName}:${option.key} could not be deserialized",
|
||||
"Option ${dbOption.patchName}:${option.name} could not be deserialized",
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,66 +4,68 @@ import app.revanced.library.ApkUtils.applyTo
|
||||
import app.revanced.manager.patcher.Session.Companion.component1
|
||||
import app.revanced.manager.patcher.Session.Companion.component2
|
||||
import app.revanced.manager.patcher.logger.Logger
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.PatcherConfig
|
||||
import app.revanced.patcher.PatchesResult
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import app.revanced.patcher.patcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
internal typealias PatchList = List<Patch<*>>
|
||||
internal typealias PatchList = List<Patch>
|
||||
private typealias Patcher = (emit: (PatchResult) -> Unit) -> PatchesResult
|
||||
|
||||
class Session(
|
||||
cacheDir: String,
|
||||
frameworkDir: String,
|
||||
aaptPath: String,
|
||||
private val frameworkDir: String,
|
||||
private val aaptPath: String,
|
||||
private val logger: Logger,
|
||||
private val input: File,
|
||||
private val onEvent: (ProgressEvent) -> Unit,
|
||||
) : Closeable {
|
||||
private val tempDir = File(cacheDir).resolve("patcher").also { it.mkdirs() }
|
||||
private val patcher = Patcher(
|
||||
PatcherConfig(
|
||||
apkFile = input,
|
||||
temporaryFilesPath = tempDir,
|
||||
frameworkFileDirectory = frameworkDir,
|
||||
aaptBinaryPath = aaptPath
|
||||
)
|
||||
)
|
||||
|
||||
private suspend fun Patcher.applyPatchesVerbose(selectedPatches: PatchList) {
|
||||
this().collect { (patch, exception) ->
|
||||
val index = selectedPatches.indexOf(patch)
|
||||
if (index == -1) return@collect
|
||||
private suspend fun applyPatchesVerbose(patcher: Patcher, selectedPatches: PatchList) =
|
||||
withContext(
|
||||
Dispatchers.Default
|
||||
) {
|
||||
val context = currentCoroutineContext()
|
||||
patcher { (patch, exception) ->
|
||||
// Make the patching process cancelable.
|
||||
context.ensureActive()
|
||||
|
||||
val index = selectedPatches.indexOf(patch)
|
||||
if (index == -1) return@patcher
|
||||
|
||||
if (exception != null) {
|
||||
onEvent(
|
||||
ProgressEvent.Failed(
|
||||
StepId.ExecutePatch(index),
|
||||
exception.toRemoteError(),
|
||||
)
|
||||
)
|
||||
logger.error("${patch.name} failed:")
|
||||
logger.error(exception.stackTraceToString())
|
||||
throw exception
|
||||
}
|
||||
|
||||
if (exception != null) {
|
||||
onEvent(
|
||||
ProgressEvent.Failed(
|
||||
ProgressEvent.Completed(
|
||||
StepId.ExecutePatch(index),
|
||||
exception.toRemoteError(),
|
||||
)
|
||||
)
|
||||
logger.error("${patch.name} failed:")
|
||||
logger.error(exception.stackTraceToString())
|
||||
throw exception
|
||||
|
||||
logger.info("${patch.name} succeeded")
|
||||
}
|
||||
|
||||
onEvent(
|
||||
ProgressEvent.Completed(
|
||||
StepId.ExecutePatch(index),
|
||||
)
|
||||
)
|
||||
|
||||
logger.info("${patch.name} succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun run(output: File, selectedPatches: PatchList) {
|
||||
runStep(StepId.ExecutePatches, onEvent) {
|
||||
val result = runStep(StepId.ExecutePatches, onEvent) {
|
||||
java.util.logging.Logger.getLogger("").apply {
|
||||
handlers.forEach {
|
||||
it.close()
|
||||
@@ -73,18 +75,21 @@ class Session(
|
||||
addHandler(logger.handler)
|
||||
}
|
||||
|
||||
with(patcher) {
|
||||
logger.info("Merging integrations")
|
||||
this += selectedPatches.toSet()
|
||||
|
||||
logger.info("Applying patches...")
|
||||
applyPatchesVerbose(selectedPatches.sortedBy { it.name })
|
||||
val patcher = patcher(
|
||||
apkFile = input,
|
||||
temporaryFilesPath = tempDir,
|
||||
frameworkFileDirectory = frameworkDir,
|
||||
aaptBinaryPath = File(aaptPath)
|
||||
) { _packageName, _version ->
|
||||
selectedPatches.toSet()
|
||||
}
|
||||
|
||||
logger.info("Applying patches...")
|
||||
applyPatchesVerbose(patcher, selectedPatches.sortedBy { it.name })
|
||||
}
|
||||
|
||||
runStep(StepId.WriteAPK, onEvent) {
|
||||
logger.info("Writing patched files...")
|
||||
val result = patcher.get()
|
||||
|
||||
val patched = tempDir.resolve("result.apk")
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -102,7 +107,7 @@ class Session(
|
||||
|
||||
override fun close() {
|
||||
tempDir.deleteRecursively()
|
||||
patcher.close()
|
||||
//patcher.close()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -2,7 +2,7 @@ package app.revanced.manager.patcher.patch
|
||||
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import android.os.Parcelable
|
||||
import app.revanced.patcher.patch.loadPatchesFromDex
|
||||
import app.revanced.patcher.patch.loadPatches
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
@@ -55,9 +55,12 @@ data class PatchBundle(val patchesJar: String) : Parcelable {
|
||||
|
||||
object Loader {
|
||||
private fun patches(bundles: Iterable<PatchBundle>) =
|
||||
loadPatchesFromDex(
|
||||
bundles.map { File(it.patchesJar) }.toSet()
|
||||
).byPatchesFile.mapKeys { (file, _) ->
|
||||
loadPatches(
|
||||
*bundles.map { File(it.patchesJar) }.toTypedArray(),
|
||||
onFailedToLoad = { file, throwable ->
|
||||
// TODO: handle error
|
||||
}
|
||||
).patchesByFile.mapKeys { (file, _) ->
|
||||
val absPath = file.absolutePath
|
||||
bundles.single { absPath == it.patchesJar }
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ sealed class PatchBundleInfo {
|
||||
it.options.all option@{ option ->
|
||||
if (!option.required || option.default != null) return@option true
|
||||
|
||||
option.key in opts
|
||||
option.name in opts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ data class PatchInfo(
|
||||
val compatiblePackages: ImmutableList<CompatiblePackage>?,
|
||||
val options: ImmutableList<Option<*>>?
|
||||
) {
|
||||
constructor(patch: Patch<*>) : this(
|
||||
constructor(patch: Patch) : this(
|
||||
patch.name.orEmpty(),
|
||||
patch.description,
|
||||
patch.use,
|
||||
@@ -49,7 +49,7 @@ data class PatchInfo(
|
||||
* The resulting patch cannot be executed.
|
||||
* This is necessary because some functions in ReVanced Library only accept full [Patch] objects.
|
||||
*/
|
||||
fun toPatcherPatch(): Patch<*> =
|
||||
fun toPatcherPatch(): Patch =
|
||||
resourcePatch(name = name, description = description, use = include) {
|
||||
compatiblePackages?.let { pkgs ->
|
||||
compatibleWith(*pkgs.map { it.packageName to it.versions }.toTypedArray())
|
||||
@@ -65,8 +65,7 @@ data class CompatiblePackage(
|
||||
|
||||
@Immutable
|
||||
data class Option<T>(
|
||||
val title: String,
|
||||
val key: String,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val required: Boolean,
|
||||
val type: KType,
|
||||
@@ -75,8 +74,7 @@ data class Option<T>(
|
||||
val validator: (T?) -> Boolean,
|
||||
) {
|
||||
constructor(option: PatchOption<T>) : this(
|
||||
option.title ?: option.key,
|
||||
option.key,
|
||||
option.name,
|
||||
option.description.orEmpty(),
|
||||
option.required,
|
||||
option.type,
|
||||
|
||||
@@ -212,7 +212,7 @@ fun PatchItem(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = option.title,
|
||||
text = option.name,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
@@ -205,7 +205,7 @@ fun <T : Any> OptionItem(
|
||||
WithOptionEditor(editor, option, value, setValue, selectionWarningEnabled) {
|
||||
ListItem(
|
||||
modifier = Modifier.clickable(onClick = ::clickAction),
|
||||
headlineContent = { Text(option.title) },
|
||||
headlineContent = { Text(option.name) },
|
||||
supportingContent = {
|
||||
Column {
|
||||
Text(option.description)
|
||||
@@ -250,7 +250,7 @@ private object StringOptionEditor : OptionEditor<String> {
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = scope.dismissDialog,
|
||||
title = { Text(scope.option.title) },
|
||||
title = { Text(scope.option.name) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = fieldValue,
|
||||
@@ -330,7 +330,7 @@ private abstract class NumberOptionEditor<T : Number> : OptionEditor<T> {
|
||||
|
||||
@Composable
|
||||
override fun Dialog(scope: OptionEditorScope<T>) {
|
||||
NumberDialog(scope.option.title, scope.value, scope.option.validator) {
|
||||
NumberDialog(scope.option.name, scope.value, scope.option.validator) {
|
||||
if (it == null) return@NumberDialog scope.dismissDialog()
|
||||
|
||||
scope.submitDialog(it)
|
||||
@@ -455,7 +455,7 @@ private class PresetOptionEditor<T : Any>(private val innerEditor: OptionEditor<
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
title = { Text(scope.option.title) },
|
||||
title = { Text(scope.option.name) },
|
||||
textHorizontalPadding = PaddingValues(horizontal = 0.dp),
|
||||
text = {
|
||||
val presets = remember(scope.option.presets) {
|
||||
@@ -496,8 +496,7 @@ private class PresetOptionEditor<T : Any>(private val innerEditor: OptionEditor<
|
||||
private class ListOptionEditor<T : Serializable>(private val elementEditor: OptionEditor<T>) :
|
||||
OptionEditor<List<T>> {
|
||||
private fun createElementOption(option: Option<List<T>>) = Option<T>(
|
||||
option.title,
|
||||
option.key,
|
||||
option.name,
|
||||
option.description,
|
||||
option.required,
|
||||
option.type.arguments.first().type!!,
|
||||
@@ -566,7 +565,7 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
|
||||
R.plurals.selected_count,
|
||||
deletionTargets.size,
|
||||
deletionTargets.size
|
||||
) else scope.option.title,
|
||||
) else scope.option.name,
|
||||
onBackClick = back,
|
||||
backIcon = {
|
||||
if (deleteMode) {
|
||||
|
||||
@@ -640,17 +640,17 @@ private fun OptionsDialog(
|
||||
) {
|
||||
if (patch.options == null) return@LazyColumnWithScrollbar
|
||||
|
||||
items(patch.options, key = { it.key }) { option ->
|
||||
val key = option.key
|
||||
items(patch.options, key = { it.name }) { option ->
|
||||
val name = option.name
|
||||
val value =
|
||||
if (values == null || !values.contains(key)) option.default else values[key]
|
||||
if (values == null || !values.contains(name)) option.default else values[name]
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
OptionItem(
|
||||
option = option as Option<Any>,
|
||||
value = value,
|
||||
setValue = {
|
||||
set(key, it)
|
||||
set(name, it)
|
||||
},
|
||||
selectionWarningEnabled = selectionWarningEnabled
|
||||
)
|
||||
|
||||
@@ -144,16 +144,16 @@ fun RequiredOptionsScreen(
|
||||
|
||||
val values = vm.getOptions(bundle.uid, it)
|
||||
it.options?.forEach { option ->
|
||||
val key = option.key
|
||||
val name = option.name
|
||||
val value =
|
||||
if (values == null || key !in values) option.default else values[key]
|
||||
if (values == null || name !in values) option.default else values[name]
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
OptionItem(
|
||||
option = option as Option<Any>,
|
||||
value = value,
|
||||
setValue = { new ->
|
||||
vm.setOption(bundle.uid, it, key, new)
|
||||
vm.setOption(bundle.uid, it, name, new)
|
||||
},
|
||||
selectionWarningEnabled = vm.selectionWarningEnabled
|
||||
)
|
||||
|
||||
@@ -129,7 +129,7 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi
|
||||
isSelected(
|
||||
bundle.uid,
|
||||
patch
|
||||
) && patch.options?.any { it.required && it.default == null && it.key !in opts } ?: false
|
||||
) && patch.options?.any { it.required && it.default == null && it.name !in opts } ?: false
|
||||
}.toList()
|
||||
}.filter { (_, patches) -> patches.isNotEmpty() }
|
||||
}
|
||||
@@ -180,13 +180,13 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi
|
||||
|
||||
fun getOptions(bundle: Int, patch: PatchInfo) = patchOptions[bundle]?.get(patch.name)
|
||||
|
||||
fun setOption(bundle: Int, patch: PatchInfo, key: String, value: Any?) {
|
||||
fun setOption(bundle: Int, patch: PatchInfo, name: String, value: Any?) {
|
||||
// All patches
|
||||
val patchesToOpts = patchOptions.getOrElse(bundle, ::persistentMapOf)
|
||||
// The key-value options of an individual patch
|
||||
val patchToOpts = patchesToOpts
|
||||
.getOrElse(patch.name, ::persistentMapOf)
|
||||
.put(key, value)
|
||||
.put(name, value)
|
||||
|
||||
patchOptions[bundle] = patchesToOpts.put(patch.name, patchToOpts)
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ class SelectedAppInfoViewModel(
|
||||
bundleOptions.forEach patch@{ (patchName, values) ->
|
||||
// Get all valid option keys for the patch.
|
||||
val validOptionKeys =
|
||||
patches[patchName]?.options?.map { it.key }?.toSet() ?: return@patch
|
||||
patches[patchName]?.options?.map { it.name }?.toSet() ?: return@patch
|
||||
|
||||
this@bundleOptions[patchName] = values.filterKeys { key ->
|
||||
key in validOptionKeys
|
||||
|
||||
@@ -17,8 +17,8 @@ serialization = "1.9.0"
|
||||
collection = "0.4.0"
|
||||
datetime = "0.7.1"
|
||||
room-version = "2.8.4"
|
||||
revanced-patcher = "21.0.0"
|
||||
revanced-library = "3.0.2"
|
||||
revanced-patcher = "22.0.0-local"
|
||||
revanced-library = "3.2.0-dev.2-local"
|
||||
koin = "4.1.1"
|
||||
ktor = "3.3.3"
|
||||
markdown-renderer = "0.39.0"
|
||||
@@ -81,8 +81,8 @@ room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room-ver
|
||||
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room-version" }
|
||||
|
||||
# Patcher
|
||||
revanced-patcher = { group = "app.revanced", name = "revanced-patcher", version.ref = "revanced-patcher" }
|
||||
revanced-library = { group = "app.revanced", name = "revanced-library", version.ref = "revanced-library" }
|
||||
revanced-patcher = { group = "app.revanced", name = "patcher-android", version.ref = "revanced-patcher" }
|
||||
revanced-library = { group = "app.revanced", name = "revanced-library-android", version.ref = "revanced-library" }
|
||||
|
||||
# Koin
|
||||
koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
google()
|
||||
gradlePluginPortal()
|
||||
@@ -8,6 +9,7 @@ pluginManagement {
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
google()
|
||||
maven("https://jitpack.io")
|
||||
|
||||
Reference in New Issue
Block a user