You've already forked revanced-patcher
mirror of
https://github.com/revanced/revanced-patcher
synced 2025-09-06 16:38:50 +02:00
Compare commits
10 Commits
v20.0.1-de
...
v20.0.1-de
Author | SHA1 | Date | |
---|---|---|---|
![]() |
851f9c7885 | ||
![]() |
ea6fc70caa | ||
![]() |
a2875d1d64 | ||
![]() |
2be6e97817 | ||
![]() |
348d0070e7 | ||
![]() |
d53aacdad4 | ||
![]() |
f1615b7ab5 | ||
![]() |
ffb1d880d7 | ||
![]() |
e95f13ae3e | ||
![]() |
e1b984d601 |
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -10,6 +10,9 @@ on:
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -46,5 +49,5 @@ jobs:
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npm exec semantic-release
|
||||
|
@@ -23,7 +23,8 @@
|
||||
"assets": [
|
||||
"CHANGELOG.md",
|
||||
"gradle.properties"
|
||||
]
|
||||
],
|
||||
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||
}
|
||||
],
|
||||
[
|
||||
|
23
CHANGELOG.md
23
CHANGELOG.md
@@ -1,3 +1,26 @@
|
||||
## [20.0.1-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.4...v20.0.1-dev.5) (2024-10-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use non-nullable type for options ([ea6fc70](https://github.com/ReVanced/revanced-patcher/commit/ea6fc70caab055251ad4d0d3f1b5cf53865abb85))
|
||||
|
||||
## [20.0.1-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.3...v20.0.1-dev.4) (2024-10-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make it work on Android by not using APIs from JVM unavailable to Android. ([2be6e97](https://github.com/ReVanced/revanced-patcher/commit/2be6e97817437f40e17893dfff3bea2cd4c3ff9e))
|
||||
|
||||
## [20.0.1-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.2...v20.0.1-dev.3) (2024-10-03)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* Free memory earlier and remove negligible lookup maps ([d53aacd](https://github.com/ReVanced/revanced-patcher/commit/d53aacdad4ed3750ddae526fb307577ea36e6171))
|
||||
|
||||
## [20.0.1-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.1...v20.0.1-dev.2) (2024-10-01)
|
||||
|
||||
## [20.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0...v20.0.1-dev.1) (2024-09-18)
|
||||
|
||||
|
||||
|
@@ -1,3 +1,3 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
version = 20.0.1-dev.1
|
||||
version = 20.0.1-dev.5
|
||||
|
2972
package-lock.json
generated
2972
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.9.1",
|
||||
"semantic-release": "^23.0.2"
|
||||
"gradle-semantic-release-plugin": "^1.10.1",
|
||||
"semantic-release": "^24.1.2"
|
||||
}
|
||||
}
|
||||
|
@@ -66,36 +66,15 @@ class Fingerprint internal constructor(
|
||||
}
|
||||
|
||||
// TODO: If only one string is necessary, why not use a single string for every fingerprint?
|
||||
fun Fingerprint.lookupByStrings() = strings?.firstNotNullOfOrNull { lookupMaps.methodsByStrings[it] }
|
||||
if (lookupByStrings()?.let(::match) == true) {
|
||||
if (strings?.firstNotNullOfOrNull { lookupMaps.methodsByStrings[it] }?.let(::match) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
// No strings declared or none matched (partial matches are allowed).
|
||||
// Use signature matching.
|
||||
fun Fingerprint.lookupBySignature(): MethodClassPairs {
|
||||
if (accessFlags == null) return lookupMaps.allMethods
|
||||
|
||||
var returnTypeValue = returnType
|
||||
if (returnTypeValue == null) {
|
||||
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
|
||||
// Constructors always have void return type.
|
||||
returnTypeValue = "V"
|
||||
} else {
|
||||
return lookupMaps.allMethods
|
||||
}
|
||||
}
|
||||
|
||||
val signature =
|
||||
buildString {
|
||||
append(accessFlags)
|
||||
append(returnTypeValue.first())
|
||||
appendParameters(parameters ?: return@buildString)
|
||||
}
|
||||
|
||||
return lookupMaps.methodsBySignature[signature] ?: return MethodClassPairs()
|
||||
context.classes.forEach { classDef ->
|
||||
if (match(context, classDef)) return true
|
||||
}
|
||||
return match(lookupBySignature())
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -98,14 +98,12 @@ class Patcher(private val config: PatcherConfig) : Closeable {
|
||||
|
||||
logger.info("Merging extensions")
|
||||
|
||||
context.executablePatches.forEachRecursively { patch ->
|
||||
if (patch is BytecodePatch && patch.extension != null) {
|
||||
context.bytecodeContext.merge(patch.extension)
|
||||
}
|
||||
}
|
||||
with(context.bytecodeContext) {
|
||||
context.executablePatches.mergeExtensions()
|
||||
|
||||
// Initialize lookup maps.
|
||||
context.bytecodeContext.lookupMaps
|
||||
// Initialize lookup maps.
|
||||
lookupMaps
|
||||
}
|
||||
|
||||
logger.info("Executing patches")
|
||||
|
||||
|
@@ -20,6 +20,7 @@ class PatcherConfig(
|
||||
private val temporaryFilesPath: File = File("revanced-temporary-files"),
|
||||
aaptBinaryPath: String? = null,
|
||||
frameworkFileDirectory: String? = null,
|
||||
@Deprecated("This is going to be removed in the future because it is not needed anymore.")
|
||||
internal val multithreadingDexFileWriter: Boolean = false,
|
||||
) {
|
||||
private val logger = Logger.getLogger(PatcherConfig::class.java.name)
|
||||
|
@@ -21,7 +21,6 @@ import lanchon.multidexlib2.MultiDexIO
|
||||
import lanchon.multidexlib2.RawDexIO
|
||||
import java.io.Closeable
|
||||
import java.io.FileFilter
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.logging.Logger
|
||||
|
||||
@@ -60,40 +59,41 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
||||
internal val lookupMaps by lazy { LookupMaps(classes) }
|
||||
|
||||
/**
|
||||
* A map for lookup by [merge].
|
||||
* Merge the extensions for this set of patches.
|
||||
*/
|
||||
internal val classesByType = mutableMapOf<String, ClassDef>().apply {
|
||||
classes.forEach { classDef -> put(classDef.type, classDef) }
|
||||
}
|
||||
internal fun Set<Patch<*>>.mergeExtensions() {
|
||||
// Lookup map for fast checking if a class exists by its type.
|
||||
val classesByType = mutableMapOf<String, ClassDef>().apply {
|
||||
classes.forEach { classDef -> put(classDef.type, classDef) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge an extension to [classes].
|
||||
*
|
||||
* @param extensionInputStream The input stream of the extension to merge.
|
||||
*/
|
||||
internal fun merge(extensionInputStream: InputStream) {
|
||||
val extension = extensionInputStream.readAllBytes()
|
||||
forEachRecursively { patch ->
|
||||
if (patch is BytecodePatch && patch.extension != null) {
|
||||
|
||||
RawDexIO.readRawDexFile(extension, 0, null).classes.forEach { classDef ->
|
||||
val existingClass = classesByType[classDef.type] ?: run {
|
||||
logger.fine("Adding class \"$classDef\"")
|
||||
val extension = patch.extension.readAllBytes()
|
||||
|
||||
classes += classDef
|
||||
classesByType[classDef.type] = classDef
|
||||
RawDexIO.readRawDexFile(extension, 0, null).classes.forEach { classDef ->
|
||||
val existingClass = classesByType[classDef.type] ?: run {
|
||||
logger.fine("Adding class \"$classDef\"")
|
||||
|
||||
return@forEach
|
||||
}
|
||||
classes += classDef
|
||||
classesByType[classDef.type] = classDef
|
||||
|
||||
logger.fine("Class \"$classDef\" exists already. Adding missing methods and fields.")
|
||||
return@forEach
|
||||
}
|
||||
|
||||
existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
|
||||
// If the class was merged, replace the original class with the merged class.
|
||||
if (mergedClass === existingClass) {
|
||||
return@let
|
||||
logger.fine("Class \"$classDef\" exists already. Adding missing methods and fields.")
|
||||
|
||||
existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
|
||||
// If the class was merged, replace the original class with the merged class.
|
||||
if (mergedClass === existingClass) {
|
||||
return@let
|
||||
}
|
||||
|
||||
classes -= existingClass
|
||||
classes += mergedClass
|
||||
}
|
||||
}
|
||||
|
||||
classes -= existingClass
|
||||
classes += mergedClass
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,6 +145,9 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
||||
override fun get(): Set<PatcherResult.PatchedDexFile> {
|
||||
logger.info("Compiling patched dex files")
|
||||
|
||||
// Free up memory before compiling the dex files.
|
||||
lookupMaps.close()
|
||||
|
||||
val patchedDexFileResults =
|
||||
config.patchedFiles.resolve("dex").also {
|
||||
it.deleteRecursively() // Make sure the directory is empty.
|
||||
@@ -178,21 +181,6 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
||||
* @param classes The list of classes to create the lookup maps from.
|
||||
*/
|
||||
internal class LookupMaps internal constructor(classes: List<ClassDef>) : Closeable {
|
||||
/**
|
||||
* Classes associated by their type.
|
||||
*/
|
||||
internal val classesByType = classes.associateBy { it.type }.toMutableMap()
|
||||
|
||||
/**
|
||||
* All methods and the class they are a member of.
|
||||
*/
|
||||
internal val allMethods = MethodClassPairs()
|
||||
|
||||
/**
|
||||
* Methods associated by its access flags, return type and parameter.
|
||||
*/
|
||||
internal val methodsBySignature = MethodClassPairsLookupMap()
|
||||
|
||||
/**
|
||||
* Methods associated by strings referenced in it.
|
||||
*/
|
||||
@@ -203,22 +191,6 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
||||
classDef.methods.forEach { method ->
|
||||
val methodClassPair: MethodClassPair = method to classDef
|
||||
|
||||
// For fingerprints with no access or return type specified.
|
||||
allMethods += methodClassPair
|
||||
|
||||
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
|
||||
|
||||
// Add <access><returnType> as the key.
|
||||
methodsBySignature[accessFlagsReturnKey] = methodClassPair
|
||||
|
||||
// Add <access><returnType>[parameters] as the key.
|
||||
methodsBySignature[
|
||||
buildString {
|
||||
append(accessFlagsReturnKey)
|
||||
appendParameters(method.parameterTypes)
|
||||
},
|
||||
] = methodClassPair
|
||||
|
||||
// Add strings contained in the method as the key.
|
||||
method.instructionsOrNull?.forEach instructions@{ instruction ->
|
||||
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) {
|
||||
@@ -259,15 +231,12 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
allMethods.clear()
|
||||
methodsBySignature.clear()
|
||||
methodsByStrings.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
lookupMaps.close()
|
||||
classesByType.clear()
|
||||
classes.clear()
|
||||
}
|
||||
}
|
||||
|
@@ -231,7 +231,7 @@ fun intOption(
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Int?>.(Int?) -> Boolean = { true },
|
||||
validator: Option<Int>.(Int?) -> Boolean = { true },
|
||||
) = option(
|
||||
key,
|
||||
default,
|
||||
@@ -264,7 +264,7 @@ fun PatchBuilder<*>.intOption(
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Int?>.(Int?) -> Boolean = { true },
|
||||
validator: Option<Int>.(Int?) -> Boolean = { true },
|
||||
) = option(
|
||||
key,
|
||||
default,
|
||||
@@ -297,7 +297,7 @@ fun booleanOption(
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Boolean?>.(Boolean?) -> Boolean = { true },
|
||||
validator: Option<Boolean>.(Boolean?) -> Boolean = { true },
|
||||
) = option(
|
||||
key,
|
||||
default,
|
||||
@@ -330,7 +330,7 @@ fun PatchBuilder<*>.booleanOption(
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Boolean?>.(Boolean?) -> Boolean = { true },
|
||||
validator: Option<Boolean>.(Boolean?) -> Boolean = { true },
|
||||
) = option(
|
||||
key,
|
||||
default,
|
||||
@@ -363,7 +363,7 @@ fun floatOption(
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Float?>.(Float?) -> Boolean = { true },
|
||||
validator: Option<Float>.(Float?) -> Boolean = { true },
|
||||
) = option(
|
||||
key,
|
||||
default,
|
||||
@@ -396,7 +396,7 @@ fun PatchBuilder<*>.floatOption(
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Float?>.(Float?) -> Boolean = { true },
|
||||
validator: Option<Float>.(Float?) -> Boolean = { true },
|
||||
) = option(
|
||||
key,
|
||||
default,
|
||||
@@ -429,7 +429,7 @@ fun longOption(
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Long?>.(Long?) -> Boolean = { true },
|
||||
validator: Option<Long>.(Long?) -> Boolean = { true },
|
||||
) = option(
|
||||
key,
|
||||
default,
|
||||
@@ -462,7 +462,7 @@ fun PatchBuilder<*>.longOption(
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Long?>.(Long?) -> Boolean = { true },
|
||||
validator: Option<Long>.(Long?) -> Boolean = { true },
|
||||
) = option(
|
||||
key,
|
||||
default,
|
||||
|
@@ -10,6 +10,9 @@ import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.lang.reflect.Member
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Modifier
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
import kotlin.reflect.KProperty
|
||||
@@ -636,7 +639,7 @@ sealed class PatchLoader private constructor(
|
||||
*/
|
||||
private val Class<*>.patchFields
|
||||
get() = fields.filter { field ->
|
||||
field.type.isPatch && field.canAccess(null)
|
||||
field.type.isPatch && field.canAccess()
|
||||
}.map { field ->
|
||||
field.get(null) as Patch<*>
|
||||
}
|
||||
@@ -646,7 +649,7 @@ sealed class PatchLoader private constructor(
|
||||
*/
|
||||
private val Class<*>.patchMethods
|
||||
get() = methods.filter { method ->
|
||||
method.returnType.isPatch && method.parameterCount == 0 && method.canAccess(null)
|
||||
method.returnType.isPatch && method.parameterCount == 0 && method.canAccess()
|
||||
}.map { method ->
|
||||
method.invoke(null) as Patch<*>
|
||||
}
|
||||
@@ -670,6 +673,12 @@ sealed class PatchLoader private constructor(
|
||||
it.name != null
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
private fun Member.canAccess(): Boolean {
|
||||
if (this is Method && parameterCount != 0) return false
|
||||
|
||||
return Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,9 @@ import app.revanced.patcher.util.ProxyClassList
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
@@ -193,6 +195,7 @@ internal object PatcherTest {
|
||||
private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
|
||||
every { patcher.context.executablePatches } returns toMutableSet()
|
||||
every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classes)
|
||||
every { with(patcher.context.bytecodeContext) { any<Set<Patch<*>>>().mergeExtensions() } } just runs
|
||||
|
||||
return runBlocking { patcher().toList() }
|
||||
}
|
||||
|
Reference in New Issue
Block a user