Compare commits

...

11 Commits

Author SHA1 Message Date
semantic-release-bot
1f4bc5079f chore(release): 1.8.0 [skip ci]
# [1.8.0](https://github.com/revanced/revanced-patcher/compare/v1.7.2...v1.8.0) (2022-06-22)

### Features

* logging class ([caf2745](caf2745805))
2022-06-22 23:25:29 +00:00
oSumAtrIX
caf2745805 feat: logging class 2022-06-23 01:23:35 +02:00
oSumAtrIX
a4529c3fee refactor: logging and exception strings 2022-06-22 16:47:58 +02:00
semantic-release-bot
835c0f9f7a chore(release): 1.7.2 [skip ci]
## [1.7.2](https://github.com/revanced/revanced-patcher/compare/v1.7.1...v1.7.2) (2022-06-22)

### Bug Fixes

* add execute permission to `./gradlew` file ([#46](https://github.com/revanced/revanced-patcher/issues/46)) ([34f607a](34f607aa24))
2022-06-22 14:17:16 +00:00
Oskar
34f607aa24 fix: add execute permission to ./gradlew file (#46) 2022-06-22 16:16:00 +02:00
Lucaskyy
0f38b94701 Merge remote-tracking branch 'origin/main' into main 2022-06-22 16:13:34 +02:00
Lucaskyy
39bb1b25dc refactor: add logging 2022-06-22 16:13:23 +02:00
Lucaskyy
4fc63a4d8a refactor: add callbacks for applyPatches & addFiles 2022-06-22 15:37:33 +02:00
semantic-release-bot
6037397bc2 chore(release): 1.7.1 [skip ci]
## [1.7.1](https://github.com/revanced/revanced-patcher/compare/v1.7.0...v1.7.1) (2022-06-22)

### Reverts

* revert "feat: use of `java.util.logging.Logger`" ([e8488b3](e8488b3e86))
2022-06-22 13:07:22 +00:00
Lucaskyy
273dd86b65 chore: remove ExtFile import 2022-06-22 15:04:05 +02:00
Lucaskyy
e8488b3e86 revert "feat: use of java.util.logging.Logger"
This reverts commit 9c39c9efdb.
This reverts commit 8f66f9f606.
2022-06-22 15:03:30 +02:00
11 changed files with 108 additions and 81 deletions

View File

@@ -28,8 +28,6 @@ jobs:
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: "lts/*" node-version: "lts/*"
- name: Make gradlew executable
run: chmod +x gradlew
- name: Build with Gradle - name: Build with Gradle
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,3 +1,24 @@
# [1.8.0](https://github.com/revanced/revanced-patcher/compare/v1.7.2...v1.8.0) (2022-06-22)
### Features
* logging class ([caf2745](https://github.com/revanced/revanced-patcher/commit/caf2745805ffd4b59fa81e79cc489b1a1a5c5d89))
## [1.7.2](https://github.com/revanced/revanced-patcher/compare/v1.7.1...v1.7.2) (2022-06-22)
### Bug Fixes
* add execute permission to `./gradlew` file ([#46](https://github.com/revanced/revanced-patcher/issues/46)) ([34f607a](https://github.com/revanced/revanced-patcher/commit/34f607aa24d89a777d906cc887203f343ce3fd07))
## [1.7.1](https://github.com/revanced/revanced-patcher/compare/v1.7.0...v1.7.1) (2022-06-22)
### Reverts
* revert "feat: use of `java.util.logging.Logger`" ([e8488b3](https://github.com/revanced/revanced-patcher/commit/e8488b3e86e0132011824f8ecba29e64f8db0573))
# [1.7.0](https://github.com/revanced/revanced-patcher/compare/v1.6.0...v1.7.0) (2022-06-22) # [1.7.0](https://github.com/revanced/revanced-patcher/compare/v1.6.0...v1.7.0) (2022-06-22)

View File

@@ -25,7 +25,6 @@ dependencies {
implementation("app.revanced:multidexlib2:2.5.2.r2") implementation("app.revanced:multidexlib2:2.5.2.r2")
implementation("org.smali:smali:2.5.2") implementation("org.smali:smali:2.5.2")
implementation("org.apktool:apktool-lib:2.6.5-SNAPSHOT") implementation("org.apktool:apktool-lib:2.6.5-SNAPSHOT")
implementation("org.slf4j:slf4j-api:1.7.36")
testImplementation(kotlin("test")) testImplementation(kotlin("test"))
} }

View File

@@ -1,2 +1,2 @@
kotlin.code.style = official kotlin.code.style = official
version = 1.7.0 version = 1.8.0

0
gradlew vendored Normal file → Executable file
View File

View File

@@ -32,7 +32,6 @@ import org.jf.dexlib2.Opcodes
import org.jf.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.ClassDef
import org.jf.dexlib2.iface.DexFile import org.jf.dexlib2.iface.DexFile
import org.jf.dexlib2.writer.io.MemoryDataStore import org.jf.dexlib2.writer.io.MemoryDataStore
import org.slf4j.LoggerFactory
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
@@ -43,20 +42,19 @@ val NAMER = BasicDexFileNamer()
* @param options The options for the patcher. * @param options The options for the patcher.
*/ */
class Patcher(private val options: PatcherOptions) { class Patcher(private val options: PatcherOptions) {
private val logger = LoggerFactory.getLogger(Patcher::class.java) private val logger = options.logger
private val opcodes: Opcodes
val data: PatcherData val data: PatcherData
private val opcodes: Opcodes
init { init {
val extInputFile = ExtFile(options.inputFile) val extInputFile = ExtFile(options.inputFile)
val outDir = File(options.resourceCacheDirectory) val outDir = File(options.resourceCacheDirectory)
if (outDir.exists()) { if (outDir.exists()) {
logger.info("Delete previous resource cache directory") logger.info("Deleting existing resource cache directory")
outDir.deleteRecursively() outDir.deleteRecursively()
outDir.mkdirs()
} }
outDir.mkdirs()
val androlib = Androlib(BuildOptions().also { it.setBuildOptions(options) }) val androlib = Androlib(BuildOptions().also { it.setBuildOptions(options) })
val resourceTable = androlib.getResTable(extInputFile, true) val resourceTable = androlib.getResTable(extInputFile, true)
@@ -64,7 +62,7 @@ class Patcher(private val options: PatcherOptions) {
val packageMetadata = PackageMetadata() val packageMetadata = PackageMetadata()
if (options.patchResources) { if (options.patchResources) {
logger.info("Decode resources") logger.info("Decoding resources using Androlib, this may take a long time...")
// decode resources to cache directory // decode resources to cache directory
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable) androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
@@ -80,7 +78,7 @@ class Patcher(private val options: PatcherOptions) {
} }
} else { } else {
logger.warn("Resource patching is disabled. Falling back to manually decode AndroidManifest.xml") logger.info("Decoding AndroidManifest.xml manually because resource patching is disabled")
// create decoder for the resource table // create decoder for the resource table
val decoder = ResAttrDecoder() val decoder = ResAttrDecoder()
@@ -104,7 +102,7 @@ class Patcher(private val options: PatcherOptions) {
packageMetadata.metaInfo.versionInfo = resourceTable.versionInfo packageMetadata.metaInfo.versionInfo = resourceTable.versionInfo
packageMetadata.metaInfo.sdkInfo = resourceTable.sdkInfo packageMetadata.metaInfo.sdkInfo = resourceTable.sdkInfo
logger.info("Read dex files") logger.info("Reading input as dex file")
// read dex files // read dex files
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null) val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
@@ -124,27 +122,35 @@ class Patcher(private val options: PatcherOptions) {
* @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found. * @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found.
*/ */
fun addFiles( fun addFiles(
files: List<File>, allowedOverwrites: Iterable<String> = emptyList(), throwOnDuplicates: Boolean = false files: List<File>,
allowedOverwrites: Iterable<String> = emptyList(),
throwOnDuplicates: Boolean = false,
callback: (File) -> Unit
) { ) {
for (file in files) { for (file in files) {
for (classDef in MultiDexIO.readDexFile(true, file, NAMER, null, null).classes) { for (classDef in MultiDexIO.readDexFile(true, file, NAMER, null, null).classes) {
val type = classDef.type val type = classDef.type
val e = data.bytecodeData.classes.internalClasses.findIndexed { it.type == type }
if (e != null) { val existingClass = data.bytecodeData.classes.internalClasses.findIndexed { it.type == type }
if (throwOnDuplicates) { if (existingClass == null) {
throw Exception("Class $type has already been added to the patcher.") if (throwOnDuplicates) throw Exception("Class $type has already been added to the patcher")
}
val (_, idx) = e logger.trace("Merging $type")
if (allowedOverwrites.contains(type)) { data.bytecodeData.classes.internalClasses.add(classDef)
logger.trace("Override $type")
data.bytecodeData.classes.internalClasses[idx] = classDef callback(file)
continue
}
logger.trace("Skip $type")
continue continue
} }
logger.trace("Add $type")
data.bytecodeData.classes.internalClasses.add(classDef) if (!allowedOverwrites.contains(type)) continue
logger.trace("Overwriting $type")
val index = existingClass.second
data.bytecodeData.classes.internalClasses[index] = classDef
callback(file)
} }
} }
} }
@@ -158,6 +164,7 @@ class Patcher(private val options: PatcherOptions) {
var resourceFile: File? = null var resourceFile: File? = null
if (options.patchResources) { if (options.patchResources) {
logger.info("Patching resources")
val cacheDirectory = ExtFile(options.resourceCacheDirectory) val cacheDirectory = ExtFile(options.resourceCacheDirectory)
val androlibResources = AndrolibResources().also { resources -> val androlibResources = AndrolibResources().also { resources ->
@@ -186,21 +193,19 @@ class Patcher(private val options: PatcherOptions) {
val resDirectory = cacheDirectory.resolve("res") val resDirectory = cacheDirectory.resolve("res")
val includedFiles = metaInfo.usesFramework.ids.map { id -> val includedFiles = metaInfo.usesFramework.ids.map { id ->
androlibResources.getFrameworkApk( androlibResources.getFrameworkApk(
id, id, metaInfo.usesFramework.tag
metaInfo.usesFramework.tag
) )
}.toTypedArray() }.toTypedArray()
logger.info("Building resources. This takes some time") logger.trace("Packaging using aapt")
androlibResources.aaptPackage( androlibResources.aaptPackage(
aaptFile, manifestFile, resDirectory, null, aaptFile, manifestFile, resDirectory, null, null, includedFiles
null, includedFiles
) )
resourceFile = aaptFile resourceFile = aaptFile
} }
logger.trace("Creating new dex file")
val newDexFile = object : DexFile { val newDexFile = object : DexFile {
override fun getClasses(): Set<ClassDef> { override fun getClasses(): Set<ClassDef> {
data.bytecodeData.classes.applyProxies() data.bytecodeData.classes.applyProxies()
@@ -212,9 +217,8 @@ class Patcher(private val options: PatcherOptions) {
} }
} }
logger.info("Write dex files") // write modified dex files
logger.info("Writing modified dex files")
// write dex modified files
val dexFiles = mutableMapOf<String, MemoryDataStore>() val dexFiles = mutableMapOf<String, MemoryDataStore>()
MultiDexIO.writeDexFile( MultiDexIO.writeDexFile(
true, -1, // core count true, -1, // core count
@@ -224,9 +228,7 @@ class Patcher(private val options: PatcherOptions) {
return PatcherResult( return PatcherResult(
dexFiles.map { dexFiles.map {
app.revanced.patcher.util.dex.DexFile(it.key, it.value.readAt(0)) app.revanced.patcher.util.dex.DexFile(it.key, it.value.readAt(0))
}, }, metaInfo.doNotCompress.toList(), resourceFile
metaInfo.doNotCompress.toList(),
resourceFile
) )
} }
@@ -249,9 +251,11 @@ class Patcher(private val options: PatcherOptions) {
): PatchResult { ): PatchResult {
val patchName = patch.patchName val patchName = patch.patchName
logger.trace("Applying patch $patchName")
// if the patch has already applied silently skip it // if the patch has already applied silently skip it
if (appliedPatches.contains(patchName)) { if (appliedPatches.contains(patchName)) {
logger.warn("Skip $patchName because it already has been applied") logger.trace("Skipping patch $patchName because it has already been applied")
return PatchResultSuccess() return PatchResultSuccess()
} }
@@ -272,7 +276,9 @@ class Patcher(private val options: PatcherOptions) {
// if the current patch is a resource patch but resource patching is disabled, return an error // if the current patch is a resource patch but resource patching is disabled, return an error
val isResourcePatch = patchInstance is ResourcePatch val isResourcePatch = patchInstance is ResourcePatch
if (!options.patchResources && isResourcePatch) return PatchResultError("$patchName is a resource patch, but resource patching is disabled.") if (!options.patchResources && isResourcePatch) {
return PatchResultError("$patchName is a resource patch, but resource patching is disabled.")
}
// TODO: find a solution for this // TODO: find a solution for this
val data = if (isResourcePatch) { val data = if (isResourcePatch) {
@@ -284,7 +290,7 @@ class Patcher(private val options: PatcherOptions) {
data.bytecodeData data.bytecodeData
} }
logger.warn("Execute $patchName") logger.trace("Executing patch $patchName of type: ${if (isResourcePatch) "resource" else "bytecode"}")
return try { return try {
patchInstance.execute(data) patchInstance.execute(data)
@@ -301,23 +307,22 @@ class Patcher(private val options: PatcherOptions) {
* If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object. * If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object.
*/ */
fun applyPatches( fun applyPatches(
stopOnError: Boolean = false, callback: (String) -> Unit = {} stopOnError: Boolean = false, callback: (Class<out Patch<Data>>, Boolean) -> Unit = { _, _ -> }
): Map<String, Result<PatchResultSuccess>> { ): Map<String, Result<PatchResultSuccess>> {
logger.trace("Applying all patches")
val appliedPatches = mutableListOf<String>() val appliedPatches = mutableListOf<String>()
return buildMap { return buildMap {
for (patch in data.patches) { for (patch in data.patches) {
val result = applyPatch(patch, appliedPatches) val result = applyPatch(patch, appliedPatches)
val name = patch.patchName this[patch.patchName] = if (result.isSuccess()) {
callback(name)
this[name] = if (result.isSuccess()) {
Result.success(result.success()!!) Result.success(result.success()!!)
} else { } else {
Result.failure(result.error()!!) Result.failure(result.error()!!)
} }
callback(patch, result.isSuccess())
if (stopOnError && result.isError()) break if (stopOnError && result.isError()) break
} }
} }

View File

@@ -1,19 +1,23 @@
package app.revanced.patcher package app.revanced.patcher
import app.revanced.patcher.logging.impl.NopLogger
import app.revanced.patcher.logging.Logger
import java.io.File import java.io.File
/** /**
* Options for a patcher. * Options for the [Patcher].
* @param inputFile The input file (usually an apk file). * @param inputFile The input file (usually an apk file).
* @param resourceCacheDirectory Directory to cache resources. * @param resourceCacheDirectory Directory to cache resources.
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded. * @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
* @param aaptPath Optional path to a custom aapt binary. * @param aaptPath Optional path to a custom aapt binary.
* @param frameworkFolderLocation Optional path to a custom framework folder. * @param frameworkFolderLocation Optional path to a custom framework folder.
* @param logger Custom logger implementation for the [Patcher].
*/ */
data class PatcherOptions( data class PatcherOptions(
internal val inputFile: File, internal val inputFile: File,
internal val resourceCacheDirectory: String, internal val resourceCacheDirectory: String,
internal val patchResources: Boolean = false, internal val patchResources: Boolean = false,
internal val aaptPath: String = "", internal val aaptPath: String = "",
internal val frameworkFolderLocation: String? = null internal val frameworkFolderLocation: String? = null,
internal val logger: Logger = NopLogger
) )

View File

@@ -1,7 +1,6 @@
package app.revanced.patcher package app.revanced.patcher
import app.revanced.patcher.util.dex.DexFile import app.revanced.patcher.util.dex.DexFile
import brut.directory.ExtFile
import java.io.File import java.io.File
/** /**

View File

@@ -0,0 +1,8 @@
package app.revanced.patcher.logging
interface Logger {
fun error(msg: String) {}
fun warn(msg: String) {}
fun info(msg: String) {}
fun trace(msg: String) {}
}

View File

@@ -0,0 +1,5 @@
package app.revanced.patcher.logging.impl
import app.revanced.patcher.logging.Logger
object NopLogger : Logger

View File

@@ -3,7 +3,6 @@ package app.revanced.patcher.signature.implementation.method.resolver
import app.revanced.patcher.data.PatcherData import app.revanced.patcher.data.PatcherData
import app.revanced.patcher.data.implementation.proxy import app.revanced.patcher.data.implementation.proxy
import app.revanced.patcher.extensions.MethodSignatureExtensions.fuzzyThreshold import app.revanced.patcher.extensions.MethodSignatureExtensions.fuzzyThreshold
import app.revanced.patcher.extensions.MethodSignatureExtensions.name
import app.revanced.patcher.extensions.parametersEqual import app.revanced.patcher.extensions.parametersEqual
import app.revanced.patcher.signature.implementation.method.MethodSignature import app.revanced.patcher.signature.implementation.method.MethodSignature
import org.jf.dexlib2.Opcode import org.jf.dexlib2.Opcode
@@ -12,26 +11,37 @@ import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.iface.instruction.Instruction import org.jf.dexlib2.iface.instruction.Instruction
import org.jf.dexlib2.iface.instruction.formats.Instruction21c import org.jf.dexlib2.iface.instruction.formats.Instruction21c
import org.jf.dexlib2.iface.reference.StringReference import org.jf.dexlib2.iface.reference.StringReference
import org.slf4j.Logger
import org.slf4j.LoggerFactory
internal class MethodSignatureResolver( internal class MethodSignatureResolver(
private val classes: List<ClassDef>, private val classes: List<ClassDef>,
private val methodSignatures: Iterable<MethodSignature> private val methodSignatures: Iterable<MethodSignature>
) { ) {
fun resolve(patcherData: PatcherData) {
for (signature in methodSignatures) {
for (classDef in classes) {
for (method in classDef.methods) {
val patternScanData = compareSignatureToMethod(signature, method) ?: continue
// create class proxy, in case a patch needs mutability
val classProxy = patcherData.bytecodeData.proxy(classDef)
signature.result = SignatureResolverResult(
classProxy,
patternScanData,
method,
)
}
}
}
}
// These functions do not require the constructor values, so they can be static. // These functions do not require the constructor values, so they can be static.
companion object { companion object {
private val logger: Logger = LoggerFactory.getLogger(MethodSignatureResolver::class.java)
fun resolveFromProxy( fun resolveFromProxy(
classProxy: app.revanced.patcher.util.proxy.ClassProxy, classProxy: app.revanced.patcher.util.proxy.ClassProxy,
signature: MethodSignature signature: MethodSignature
): SignatureResolverResult? { ): SignatureResolverResult? {
for (method in classProxy.immutableClass.methods) { for (method in classProxy.immutableClass.methods) {
val result = compareSignatureToMethod(signature, method) ?: continue val result = compareSignatureToMethod(signature, method) ?: continue
logger.trace("${signature.name} match to ${method.definingClass}->${method.name}")
return SignatureResolverResult( return SignatureResolverResult(
classProxy, classProxy,
result, result,
@@ -146,28 +156,6 @@ internal class MethodSignatureResolver(
} }
} }
} }
fun resolve(patcherData: PatcherData) {
for (signature in methodSignatures) {
val signatureName = signature.name
logger.trace("Resolve $signatureName")
for (classDef in classes) {
for (method in classDef.methods) {
val patternScanData = compareSignatureToMethod(signature, method) ?: continue
logger.trace("$signatureName match to ${method.definingClass}->${method.name}")
// create class proxy, in case a patch needs mutability
val classProxy = patcherData.bytecodeData.proxy(classDef)
signature.result = SignatureResolverResult(
classProxy,
patternScanData,
method,
)
}
}
}
}
} }
private operator fun ClassDef.component1() = this private operator fun ClassDef.component1() = this