Compare commits

..

1 Commits

Author SHA1 Message Date
Ax333l
51b6b449bf migrate to patcher v22 2026-01-30 23:43:03 +01:00
18 changed files with 95 additions and 89 deletions

View File

@@ -23,6 +23,10 @@ kotlin {
jvmToolchain(17) jvmToolchain(17)
compilerOptions { compilerOptions {
jvmTarget = JvmTarget.JVM_17 jvmTarget = JvmTarget.JVM_17
freeCompilerArgs.addAll(
"-Xexplicit-backing-fields",
"-Xcontext-parameters",
)
} }
} }

View File

@@ -1,10 +1,3 @@
# app [1.26.0-dev.20](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.19...v1.26.0-dev.20) (2026-01-09)
### Bug Fixes
* Save FAB freaking out in select patches screen ([4c0b6b0](https://github.com/ReVanced/revanced-manager/commit/4c0b6b02e95a8d6f655bcf5c25493b1f9a4a4dcd))
# app [1.26.0-dev.19](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.18...v1.26.0-dev.19) (2026-01-08) # app [1.26.0-dev.19](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.18...v1.26.0-dev.19) (2026-01-08)

View File

@@ -260,6 +260,10 @@ kotlin {
jvmToolchain(17) jvmToolchain(17)
compilerOptions { compilerOptions {
jvmTarget = JvmTarget.JVM_17 jvmTarget = JvmTarget.JVM_17
freeCompilerArgs.addAll(
"-Xexplicit-backing-fields",
"-Xcontext-parameters",
)
} }
} }

View File

@@ -1 +1 @@
version = 1.26.0-dev.20 version = 1.26.0-dev.19

View File

@@ -5,8 +5,6 @@ import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import app.revanced.manager.patcher.patch.Option import app.revanced.manager.patcher.patch.Option
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonNull
@@ -36,7 +34,7 @@ import kotlin.reflect.typeOf
data class Option( data class Option(
@ColumnInfo(name = "group") val group: Int, @ColumnInfo(name = "group") val group: Int,
@ColumnInfo(name = "patch_name") val patchName: String, @ColumnInfo(name = "patch_name") val patchName: String,
@ColumnInfo(name = "key") val key: String, @ColumnInfo(name = "key") val name: String,
// Encoded as Json. // Encoded as Json.
@ColumnInfo(name = "value") val value: SerializedValue, @ColumnInfo(name = "value") val value: SerializedValue,
) { ) {

View File

@@ -35,15 +35,15 @@ class PatchOptionsRepository(db: AppDatabase) {
bundlePatchOptions.getOrPut(dbOption.patchName, ::mutableMapOf) bundlePatchOptions.getOrPut(dbOption.patchName, ::mutableMapOf)
val option = 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) { if (option != null) {
try { try {
deserializedPatchOptions[option.key] = deserializedPatchOptions[option.name] =
dbOption.value.deserializeFor(option) dbOption.value.deserializeFor(option)
} catch (e: Option.SerializationException) { } catch (e: Option.SerializationException) {
Log.w( Log.w(
tag, tag,
"Option ${dbOption.patchName}:${option.key} could not be deserialized", "Option ${dbOption.patchName}:${option.name} could not be deserialized",
e e
) )
} }

View File

@@ -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.component1
import app.revanced.manager.patcher.Session.Companion.component2 import app.revanced.manager.patcher.Session.Companion.component2
import app.revanced.manager.patcher.logger.Logger import app.revanced.manager.patcher.logger.Logger
import app.revanced.patcher.Patcher import app.revanced.patcher.PatchesResult
import app.revanced.patcher.PatcherConfig
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
internal typealias PatchList = List<Patch<*>> internal typealias PatchList = List<Patch>
private typealias Patcher = (emit: (PatchResult) -> Unit) -> PatchesResult
class Session( class Session(
cacheDir: String, cacheDir: String,
frameworkDir: String, private val frameworkDir: String,
aaptPath: String, private val aaptPath: String,
private val logger: Logger, private val logger: Logger,
private val input: File, private val input: File,
private val onEvent: (ProgressEvent) -> Unit, private val onEvent: (ProgressEvent) -> Unit,
) : Closeable { ) : Closeable {
private val tempDir = File(cacheDir).resolve("patcher").also { it.mkdirs() } 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) { private suspend fun applyPatchesVerbose(patcher: Patcher, selectedPatches: PatchList) =
this().collect { (patch, exception) -> withContext(
val index = selectedPatches.indexOf(patch) Dispatchers.Default
if (index == -1) return@collect ) {
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( onEvent(
ProgressEvent.Failed( ProgressEvent.Completed(
StepId.ExecutePatch(index), StepId.ExecutePatch(index),
exception.toRemoteError(),
) )
) )
logger.error("${patch.name} failed:")
logger.error(exception.stackTraceToString()) logger.info("${patch.name} succeeded")
throw exception
} }
onEvent(
ProgressEvent.Completed(
StepId.ExecutePatch(index),
)
)
logger.info("${patch.name} succeeded")
} }
}
suspend fun run(output: File, selectedPatches: PatchList) { suspend fun run(output: File, selectedPatches: PatchList) {
runStep(StepId.ExecutePatches, onEvent) { val result = runStep(StepId.ExecutePatches, onEvent) {
java.util.logging.Logger.getLogger("").apply { java.util.logging.Logger.getLogger("").apply {
handlers.forEach { handlers.forEach {
it.close() it.close()
@@ -73,18 +75,21 @@ class Session(
addHandler(logger.handler) addHandler(logger.handler)
} }
with(patcher) { val patcher = patcher(
logger.info("Merging integrations") apkFile = input,
this += selectedPatches.toSet() temporaryFilesPath = tempDir,
frameworkFileDirectory = frameworkDir,
logger.info("Applying patches...") aaptBinaryPath = File(aaptPath)
applyPatchesVerbose(selectedPatches.sortedBy { it.name }) ) { _packageName, _version ->
selectedPatches.toSet()
} }
logger.info("Applying patches...")
applyPatchesVerbose(patcher, selectedPatches.sortedBy { it.name })
} }
runStep(StepId.WriteAPK, onEvent) { runStep(StepId.WriteAPK, onEvent) {
logger.info("Writing patched files...") logger.info("Writing patched files...")
val result = patcher.get()
val patched = tempDir.resolve("result.apk") val patched = tempDir.resolve("result.apk")
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@@ -102,7 +107,7 @@ class Session(
override fun close() { override fun close() {
tempDir.deleteRecursively() tempDir.deleteRecursively()
patcher.close() //patcher.close()
} }
companion object { companion object {

View File

@@ -2,7 +2,7 @@ package app.revanced.manager.patcher.patch
import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.IgnoredOnParcel
import android.os.Parcelable import android.os.Parcelable
import app.revanced.patcher.patch.loadPatchesFromDex import app.revanced.patcher.patch.loadPatches
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@@ -55,9 +55,12 @@ data class PatchBundle(val patchesJar: String) : Parcelable {
object Loader { object Loader {
private fun patches(bundles: Iterable<PatchBundle>) = private fun patches(bundles: Iterable<PatchBundle>) =
loadPatchesFromDex( loadPatches(
bundles.map { File(it.patchesJar) }.toSet() *bundles.map { File(it.patchesJar) }.toTypedArray(),
).byPatchesFile.mapKeys { (file, _) -> onFailedToLoad = { file, throwable ->
// TODO: handle error
}
).patchesByFile.mapKeys { (file, _) ->
val absPath = file.absolutePath val absPath = file.absolutePath
bundles.single { absPath == it.patchesJar } bundles.single { absPath == it.patchesJar }
} }

View File

@@ -137,7 +137,7 @@ sealed class PatchBundleInfo {
it.options.all option@{ option -> it.options.all option@{ option ->
if (!option.required || option.default != null) return@option true if (!option.required || option.default != null) return@option true
option.key in opts option.name in opts
} }
} }
} }

View File

@@ -17,7 +17,7 @@ data class PatchInfo(
val compatiblePackages: ImmutableList<CompatiblePackage>?, val compatiblePackages: ImmutableList<CompatiblePackage>?,
val options: ImmutableList<Option<*>>? val options: ImmutableList<Option<*>>?
) { ) {
constructor(patch: Patch<*>) : this( constructor(patch: Patch) : this(
patch.name.orEmpty(), patch.name.orEmpty(),
patch.description, patch.description,
patch.use, patch.use,
@@ -49,7 +49,7 @@ data class PatchInfo(
* The resulting patch cannot be executed. * The resulting patch cannot be executed.
* This is necessary because some functions in ReVanced Library only accept full [Patch] objects. * 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) { resourcePatch(name = name, description = description, use = include) {
compatiblePackages?.let { pkgs -> compatiblePackages?.let { pkgs ->
compatibleWith(*pkgs.map { it.packageName to it.versions }.toTypedArray()) compatibleWith(*pkgs.map { it.packageName to it.versions }.toTypedArray())
@@ -65,8 +65,7 @@ data class CompatiblePackage(
@Immutable @Immutable
data class Option<T>( data class Option<T>(
val title: String, val name: String,
val key: String,
val description: String, val description: String,
val required: Boolean, val required: Boolean,
val type: KType, val type: KType,
@@ -75,8 +74,7 @@ data class Option<T>(
val validator: (T?) -> Boolean, val validator: (T?) -> Boolean,
) { ) {
constructor(option: PatchOption<T>) : this( constructor(option: PatchOption<T>) : this(
option.title ?: option.key, option.name,
option.key,
option.description.orEmpty(), option.description.orEmpty(),
option.required, option.required,
option.type, option.type,

View File

@@ -212,7 +212,7 @@ fun PatchItem(
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
) { ) {
Text( Text(
text = option.title, text = option.name,
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary
) )

View File

@@ -205,7 +205,7 @@ fun <T : Any> OptionItem(
WithOptionEditor(editor, option, value, setValue, selectionWarningEnabled) { WithOptionEditor(editor, option, value, setValue, selectionWarningEnabled) {
ListItem( ListItem(
modifier = Modifier.clickable(onClick = ::clickAction), modifier = Modifier.clickable(onClick = ::clickAction),
headlineContent = { Text(option.title) }, headlineContent = { Text(option.name) },
supportingContent = { supportingContent = {
Column { Column {
Text(option.description) Text(option.description)
@@ -250,7 +250,7 @@ private object StringOptionEditor : OptionEditor<String> {
AlertDialog( AlertDialog(
onDismissRequest = scope.dismissDialog, onDismissRequest = scope.dismissDialog,
title = { Text(scope.option.title) }, title = { Text(scope.option.name) },
text = { text = {
OutlinedTextField( OutlinedTextField(
value = fieldValue, value = fieldValue,
@@ -330,7 +330,7 @@ private abstract class NumberOptionEditor<T : Number> : OptionEditor<T> {
@Composable @Composable
override fun Dialog(scope: OptionEditorScope<T>) { 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() if (it == null) return@NumberDialog scope.dismissDialog()
scope.submitDialog(it) scope.submitDialog(it)
@@ -455,7 +455,7 @@ private class PresetOptionEditor<T : Any>(private val innerEditor: OptionEditor<
Text(stringResource(R.string.cancel)) Text(stringResource(R.string.cancel))
} }
}, },
title = { Text(scope.option.title) }, title = { Text(scope.option.name) },
textHorizontalPadding = PaddingValues(horizontal = 0.dp), textHorizontalPadding = PaddingValues(horizontal = 0.dp),
text = { text = {
val presets = remember(scope.option.presets) { 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>) : private class ListOptionEditor<T : Serializable>(private val elementEditor: OptionEditor<T>) :
OptionEditor<List<T>> { OptionEditor<List<T>> {
private fun createElementOption(option: Option<List<T>>) = Option<T>( private fun createElementOption(option: Option<List<T>>) = Option<T>(
option.title, option.name,
option.key,
option.description, option.description,
option.required, option.required,
option.type.arguments.first().type!!, option.type.arguments.first().type!!,
@@ -566,7 +565,7 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
R.plurals.selected_count, R.plurals.selected_count,
deletionTargets.size, deletionTargets.size,
deletionTargets.size deletionTargets.size
) else scope.option.title, ) else scope.option.name,
onBackClick = back, onBackClick = back,
backIcon = { backIcon = {
if (deleteMode) { if (deleteMode) {

View File

@@ -640,17 +640,17 @@ private fun OptionsDialog(
) { ) {
if (patch.options == null) return@LazyColumnWithScrollbar if (patch.options == null) return@LazyColumnWithScrollbar
items(patch.options, key = { it.key }) { option -> items(patch.options, key = { it.name }) { option ->
val key = option.key val name = option.name
val value = 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") @Suppress("UNCHECKED_CAST")
OptionItem( OptionItem(
option = option as Option<Any>, option = option as Option<Any>,
value = value, value = value,
setValue = { setValue = {
set(key, it) set(name, it)
}, },
selectionWarningEnabled = selectionWarningEnabled selectionWarningEnabled = selectionWarningEnabled
) )

View File

@@ -144,16 +144,16 @@ fun RequiredOptionsScreen(
val values = vm.getOptions(bundle.uid, it) val values = vm.getOptions(bundle.uid, it)
it.options?.forEach { option -> it.options?.forEach { option ->
val key = option.key val name = option.name
val value = 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") @Suppress("UNCHECKED_CAST")
OptionItem( OptionItem(
option = option as Option<Any>, option = option as Option<Any>,
value = value, value = value,
setValue = { new -> setValue = { new ->
vm.setOption(bundle.uid, it, key, new) vm.setOption(bundle.uid, it, name, new)
}, },
selectionWarningEnabled = vm.selectionWarningEnabled selectionWarningEnabled = vm.selectionWarningEnabled
) )

View File

@@ -129,7 +129,7 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi
isSelected( isSelected(
bundle.uid, bundle.uid,
patch 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() }.toList()
}.filter { (_, patches) -> patches.isNotEmpty() } }.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 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 // All patches
val patchesToOpts = patchOptions.getOrElse(bundle, ::persistentMapOf) val patchesToOpts = patchOptions.getOrElse(bundle, ::persistentMapOf)
// The key-value options of an individual patch // The key-value options of an individual patch
val patchToOpts = patchesToOpts val patchToOpts = patchesToOpts
.getOrElse(patch.name, ::persistentMapOf) .getOrElse(patch.name, ::persistentMapOf)
.put(key, value) .put(name, value)
patchOptions[bundle] = patchesToOpts.put(patch.name, patchToOpts) patchOptions[bundle] = patchesToOpts.put(patch.name, patchToOpts)
} }

View File

@@ -325,7 +325,7 @@ class SelectedAppInfoViewModel(
bundleOptions.forEach patch@{ (patchName, values) -> bundleOptions.forEach patch@{ (patchName, values) ->
// Get all valid option keys for the patch. // Get all valid option keys for the patch.
val validOptionKeys = 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 -> this@bundleOptions[patchName] = values.filterKeys { key ->
key in validOptionKeys key in validOptionKeys

View File

@@ -17,8 +17,8 @@ serialization = "1.9.0"
collection = "0.4.0" collection = "0.4.0"
datetime = "0.7.1" datetime = "0.7.1"
room-version = "2.8.4" room-version = "2.8.4"
revanced-patcher = "21.0.0" revanced-patcher = "22.0.0-local"
revanced-library = "3.0.2" revanced-library = "3.2.0-dev.2-local"
koin = "4.1.1" koin = "4.1.1"
ktor = "3.3.3" ktor = "3.3.3"
markdown-renderer = "0.39.0" 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" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room-version" }
# Patcher # Patcher
revanced-patcher = { group = "app.revanced", name = "revanced-patcher", version.ref = "revanced-patcher" } revanced-patcher = { group = "app.revanced", name = "patcher-android", version.ref = "revanced-patcher" }
revanced-library = { group = "app.revanced", name = "revanced-library", version.ref = "revanced-library" } revanced-library = { group = "app.revanced", name = "revanced-library-android", version.ref = "revanced-library" }
# Koin # Koin
koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" } koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }

View File

@@ -1,5 +1,6 @@
pluginManagement { pluginManagement {
repositories { repositories {
mavenLocal()
mavenCentral() mavenCentral()
google() google()
gradlePluginPortal() gradlePluginPortal()
@@ -8,6 +9,7 @@ pluginManagement {
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {
mavenLocal()
mavenCentral() mavenCentral()
google() google()
maven("https://jitpack.io") maven("https://jitpack.io")