From 88a325257494939a79fb30dd51d60c5c52546755 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 7 Dec 2024 06:18:52 +0100 Subject: [PATCH 1/5] feat: Add identity hash code to unnamed patches --- src/main/kotlin/app/revanced/patcher/patch/Patch.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt b/src/main/kotlin/app/revanced/patcher/patch/Patch.kt index 8f0dc838..39d27b3e 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/Patch.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/Patch.kt @@ -87,7 +87,8 @@ sealed class Patch>( finalizeBlock?.invoke(context) } - override fun toString() = name ?: "Patch" + override fun toString() = name ?: + "Patch@${System.identityHashCode(this)}" } internal fun Patch<*>.anyRecursively( @@ -161,7 +162,7 @@ class BytecodePatch internal constructor( override fun finalize(context: PatcherContext) = finalize(context.bytecodeContext) - override fun toString() = name ?: "BytecodePatch" + override fun toString() = name ?: "Bytecode${super.toString()}" } /** @@ -204,7 +205,7 @@ class RawResourcePatch internal constructor( override fun finalize(context: PatcherContext) = finalize(context.resourceContext) - override fun toString() = name ?: "RawResourcePatch" + override fun toString() = name ?: "RawResource${super.toString()}" } /** @@ -247,7 +248,7 @@ class ResourcePatch internal constructor( override fun finalize(context: PatcherContext) = finalize(context.resourceContext) - override fun toString() = name ?: "ResourcePatch" + override fun toString() = name ?: "Resource${super.toString()}" } /** From fc505a87263652cf80151bd46f05ad4c2d32c4bd Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 7 Dec 2024 05:20:15 +0000 Subject: [PATCH 2/5] chore: Release v21.1.0-dev.1 [skip ci] # [21.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0...v21.1.0-dev.1) (2024-12-07) ### Features * Add identity hash code to unnamed patches ([88a3252](https://github.com/ReVanced/revanced-patcher/commit/88a325257494939a79fb30dd51d60c5c52546755)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0399165a..1d110efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [21.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0...v21.1.0-dev.1) (2024-12-07) + + +### Features + +* Add identity hash code to unnamed patches ([88a3252](https://github.com/ReVanced/revanced-patcher/commit/88a325257494939a79fb30dd51d60c5c52546755)) + # [21.0.0](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0) (2024-11-05) diff --git a/gradle.properties b/gradle.properties index df1dab35..773110d2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.parallel = true org.gradle.caching = true -version = 21.0.0 +version = 21.1.0-dev.1 From fe5fb736cb23a8e0042961bd0d23e25e5c7d075f Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 17 Dec 2024 04:20:28 +0100 Subject: [PATCH 3/5] build: Bump dependencies --- api/revanced-patcher.api | 2 + gradle.properties | 6 +- gradle/libs.versions.toml | 6 +- .../app/revanced/patcher/PatcherConfig.kt | 24 ++++- .../patcher/patch/ResourcePatchContext.kt | 102 +++++++++--------- 5 files changed, 79 insertions(+), 61 deletions(-) diff --git a/api/revanced-patcher.api b/api/revanced-patcher.api index a242881a..34ad9a23 100644 --- a/api/revanced-patcher.api +++ b/api/revanced-patcher.api @@ -73,6 +73,8 @@ public final class app/revanced/patcher/Patcher : java/io/Closeable { } public final class app/revanced/patcher/PatcherConfig { + public fun (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;)V + public synthetic fun (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } diff --git a/gradle.properties b/gradle.properties index 773110d2..a7c245f0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.parallel = true -org.gradle.caching = true -version = 21.1.0-dev.1 +org.gradle.parallel=true +org.gradle.caching=true +version=21.1.0-dev.1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 53dd7145..18adf689 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,14 +1,14 @@ [versions] android = "4.1.1.4" -apktool-lib = "2.9.3" +apktool-lib = "2.10.1.1" binary-compatibility-validator = "0.15.1" -kotlin = "2.0.0" +kotlin = "2.0.20" kotlinx-coroutines-core = "1.8.1" mockk = "1.13.10" multidexlib2 = "3.0.3.r3" # Tracking https://github.com/google/smali/issues/64. #noinspection GradleDependency -smali = "3.0.5" +smali = "3.0.8" xpp3 = "1.1.4c" [libraries] diff --git a/src/main/kotlin/app/revanced/patcher/PatcherConfig.kt b/src/main/kotlin/app/revanced/patcher/PatcherConfig.kt index 7c4b6133..037f7d34 100644 --- a/src/main/kotlin/app/revanced/patcher/PatcherConfig.kt +++ b/src/main/kotlin/app/revanced/patcher/PatcherConfig.kt @@ -16,9 +16,28 @@ import java.util.logging.Logger class PatcherConfig( internal val apkFile: File, private val temporaryFilesPath: File = File("revanced-temporary-files"), - aaptBinaryPath: String? = null, + aaptBinaryPath: File? = null, frameworkFileDirectory: String? = null, ) { + /** + * The configuration for the patcher. + * + * @param apkFile The apk file to patch. + * @param temporaryFilesPath A path to a folder to store temporary files in. + * @param aaptBinaryPath A path to a custom aapt binary. + * @param frameworkFileDirectory A path to the directory to cache the framework file in. + */ + @Deprecated( + "Use the constructor with a File for aaptBinaryPath instead.", + ReplaceWith("PatcherConfig(apkFile, temporaryFilesPath, aaptBinaryPath?.let { File(it) }, frameworkFileDirectory)"), + ) + constructor( + apkFile: File, + temporaryFilesPath: File = File("revanced-temporary-files"), + aaptBinaryPath: String? = null, + frameworkFileDirectory: String? = null, + ) : this(apkFile, temporaryFilesPath, aaptBinaryPath?.let { File(it) }, frameworkFileDirectory) + private val logger = Logger.getLogger(PatcherConfig::class.java.name) /** @@ -33,8 +52,7 @@ class PatcherConfig( */ internal val resourceConfig = Config.getDefaultConfig().apply { - useAapt2 = true - aaptPath = aaptBinaryPath ?: "" + aaptBinary = aaptBinaryPath frameworkDirectory = frameworkFileDirectory } diff --git a/src/main/kotlin/app/revanced/patcher/patch/ResourcePatchContext.kt b/src/main/kotlin/app/revanced/patcher/patch/ResourcePatchContext.kt index f1ae3ab5..a65481d1 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/ResourcePatchContext.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/ResourcePatchContext.kt @@ -10,9 +10,9 @@ import brut.androlib.ApkDecoder import brut.androlib.apk.UsesFramework import brut.androlib.res.Framework import brut.androlib.res.ResourcesDecoder +import brut.androlib.res.decoder.AndroidManifestPullStreamDecoder import brut.androlib.res.decoder.AndroidManifestResourceParser -import brut.androlib.res.decoder.XmlPullStreamDecoder -import brut.androlib.res.xml.ResXmlPatcher +import brut.androlib.res.xml.ResXmlUtils import brut.directory.ExtFile import java.io.InputStream import java.io.OutputStream @@ -51,64 +51,62 @@ class ResourcePatchContext internal constructor( * * @param mode The [ResourceMode] to use. */ - internal fun decodeResources(mode: ResourceMode) = - with(packageMetadata.apkInfo) { - config.initializeTemporaryFilesDirectories() + internal fun decodeResources(mode: ResourceMode) = with(packageMetadata.apkInfo) { + config.initializeTemporaryFilesDirectories() - // Needed to decode resources. - val resourcesDecoder = ResourcesDecoder(config.resourceConfig, this) + // Needed to decode resources. + val resourcesDecoder = ResourcesDecoder(config.resourceConfig, this) - if (mode == ResourceMode.FULL) { - logger.info("Decoding resources") + if (mode == ResourceMode.FULL) { + logger.info("Decoding resources") - resourcesDecoder.decodeResources(config.apkFiles) - resourcesDecoder.decodeManifest(config.apkFiles) + resourcesDecoder.decodeResources(config.apkFiles) + resourcesDecoder.decodeManifest(config.apkFiles) - // Needed to record uncompressed files. - val apkDecoder = ApkDecoder(config.resourceConfig, this) - apkDecoder.recordUncompressedFiles(resourcesDecoder.resFileMapping) + // Needed to record uncompressed files. + ApkDecoder(this, config.resourceConfig).recordUncompressedFiles(resourcesDecoder.resFileMapping) - usesFramework = - UsesFramework().apply { - ids = resourcesDecoder.resTable.listFramePackages().map { it.id } - } - } else { - logger.info("Decoding app manifest") - - // Decode manually instead of using resourceDecoder.decodeManifest - // because it does not support decoding to an OutputStream. - XmlPullStreamDecoder( - AndroidManifestResourceParser(resourcesDecoder.resTable), - resourcesDecoder.resXmlSerializer, - ).decodeManifest( - apkFile.directory.getFileInput("AndroidManifest.xml"), - // Older Android versions do not support OutputStream.nullOutputStream() - object : OutputStream() { - override fun write(b: Int) { // Do nothing. - } - }, - ) - - // Get the package name and version from the manifest using the XmlPullStreamDecoder. - // XmlPullStreamDecoder.decodeManifest() sets metadata.apkInfo. - packageMetadata.let { metadata -> - metadata.packageName = resourcesDecoder.resTable.packageRenamed - versionInfo.let { - metadata.packageVersion = it.versionName ?: it.versionCode + usesFramework = + UsesFramework().apply { + ids = resourcesDecoder.resTable.listFramePackages().map { it.id } + } + } else { + logger.info("Decoding app manifest") + + // Decode manually instead of using resourceDecoder.decodeManifest + // because it does not support decoding to an OutputStream. + AndroidManifestPullStreamDecoder( + AndroidManifestResourceParser(resourcesDecoder.resTable), + resourcesDecoder.newXmlSerializer(), + ).decode( + apkFile.directory.getFileInput("AndroidManifest.xml"), + // Older Android versions do not support OutputStream.nullOutputStream() + object : OutputStream() { + override fun write(b: Int) { // Do nothing. } + }, + ) + + // Get the package name and version from the manifest using the XmlPullStreamDecoder. + // AndroidManifestPullStreamDecoder.decode() sets metadata.apkInfo. + packageMetadata.let { metadata -> + metadata.packageName = resourcesDecoder.resTable.packageRenamed + versionInfo.let { + metadata.packageVersion = it.versionName ?: it.versionCode + } - /* - The ResTable if flagged as sparse if the main package is not loaded, which is the case here, - because ResourcesDecoder.decodeResources loads the main package - and not XmlPullStreamDecoder.decodeManifest. - See ARSCDecoder.readTableType for more info. + /* + The ResTable if flagged as sparse if the main package is not loaded, which is the case here, + because ResourcesDecoder.decodeResources loads the main package + and not AndroidManifestPullStreamDecoder.decode. + See ARSCDecoder.readTableType for more info. - Set this to false again to prevent the ResTable from being flagged as sparse falsely. - */ - metadata.apkInfo.sparseResources = false - } + Set this to false again to prevent the ResTable from being flagged as sparse falsely. + */ + metadata.apkInfo.sparseResources = false } } + } /** * Compile resources in [PatcherConfig.apkFiles]. @@ -130,10 +128,10 @@ class ResourcePatchContext internal constructor( AaptInvoker( config.resourceConfig, packageMetadata.apkInfo, - ).invokeAapt( + ).invoke( resources.resolve("resources.apk"), config.apkFiles.resolve("AndroidManifest.xml").also { - ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it) + ResXmlUtils.fixingPublicAttrsInProviderAttributes(it) }, config.apkFiles.resolve("res"), null, From 754b02e4ca66ec10764d5205c6643f2d86d0c6a2 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 24 Dec 2024 16:47:48 +0100 Subject: [PATCH 4/5] feat: Use option name as key for simplicity and consistency --- docs/2_2_patch_anatomy.md | 36 ++++++++-------- .../app/revanced/patcher/patch/Option.kt | 43 +++++++++++++++++-- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/docs/2_2_patch_anatomy.md b/docs/2_2_patch_anatomy.md index 42764f6c..dd78c58b 100644 --- a/docs/2_2_patch_anatomy.md +++ b/docs/2_2_patch_anatomy.md @@ -73,22 +73,22 @@ package app.revanced.patches.ads val disableAdsPatch = bytecodePatch( name = "Disable ads", description = "Disable ads in the app.", -) { +) { compatibleWith("com.some.app"("1.0.0")) - + // Patches can depend on other patches, executing them first. dependsOn(disableAdsResourcePatch) // Merge precompiled DEX files into the patched app, before the patch is executed. extendWith("disable-ads.rve") - + // Business logic of the patch to disable ads in the app. execute { // Fingerprint to find the method to patch. val showAdsMatch by showAdsFingerprint { - // More about fingerprints on the next page of the documentation. + // More about fingerprints on the next page of the documentation. } - + // In the method that shows ads, // call DisableAdsPatch.shouldDisableAds() from the extension (precompiled DEX file) // to enable or disable ads. @@ -122,11 +122,11 @@ To define an option, use the available `option` functions: ```kt val patch = bytecodePatch(name = "Patch") { // Add an inbuilt option and delegate it to a property. - val value by stringOption(key = "option") + val value by stringOption(name = "Inbuilt option") // Add an option with a custom type and delegate it to a property. - val string by option(key = "string") - + val string by option(name = "String option") + execute { println(value) println(string) @@ -139,7 +139,7 @@ Options of a patch can be set after loading the patches with `PatchLoader` by ob ```kt loadPatchesJar(patches).apply { // Type is checked at runtime. - first { it.name == "Patch" }.options["option"] = "Value" + first { it.name == "Patch" }.options["Option"] = "Value" } ``` @@ -152,7 +152,7 @@ option.type // The KType of the option. Captures the full type information of th Options can be declared outside a patch and added to a patch manually: ```kt -val option = stringOption(key = "option") +val option = stringOption(name = "Option") bytecodePatch(name = "Patch") { val value by option() @@ -183,18 +183,18 @@ and use it in a patch: ```kt val patch = bytecodePatch(name = "Complex patch") { extendWith("complex-patch.rve") - - execute { + + execute { fingerprint.match!!.mutableMethod.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V") } } ``` -ReVanced Patcher merges the classes from the extension into `context.classes` before executing the patch. +ReVanced Patcher merges the classes from the extension into `context.classes` before executing the patch. When the patch is executed, it can reference the classes and methods from the extension. > [!NOTE] -> +> > The [ReVanced Patches template](https://github.com/ReVanced/revanced-patches-template) repository > is a template project to create patches and extensions. @@ -211,9 +211,9 @@ A simple real-world example would be a patch that opens a resource file of the a Other patches that depend on this patch can write to the file, and the finalization block can close the file. ```kt -val patch = bytecodePatch(name = "Patch") { +val patch = bytecodePatch(name = "Patch") { dependsOn( - bytecodePatch(name = "Dependency") { + bytecodePatch(name = "Dependency") { execute { print("1") } @@ -249,10 +249,10 @@ The same order is followed for multiple patches depending on the patch. - A patch can declare compatibility with specific packages and versions, but patches can still be executed on any package or version. It is recommended that compatibility is specified to present known compatible packages and versions. - - If `compatibleWith` is not used, the patch is treated as compatible with any package + - If `compatibleWith` is not used, the patch is treated as compatible with any package - If a package is specified with no versions, the patch is compatible with any version of the package - If an empty array of versions is specified, the patch is not compatible with any version of the package. - This is useful for declaring incompatibility with a specific package. + This is useful for declaring incompatibility with a specific package. - A patch can raise a `PatchException` at any time of execution to indicate that the patch failed to execute. ## ⏭️ What's next diff --git a/src/main/kotlin/app/revanced/patcher/patch/Option.kt b/src/main/kotlin/app/revanced/patcher/patch/Option.kt index 659f7871..c37a1e60 100644 --- a/src/main/kotlin/app/revanced/patcher/patch/Option.kt +++ b/src/main/kotlin/app/revanced/patcher/patch/Option.kt @@ -20,16 +20,51 @@ import kotlin.reflect.typeOf * @constructor Create a new [Option]. */ @Suppress("MemberVisibilityCanBePrivate", "unused") -class Option @PublishedApi internal constructor( +class Option +@PublishedApi +@Deprecated("Use the constructor with the name instead of a key instead.") +internal constructor( + @Deprecated("Use the name property instead.") val key: String, val default: T? = null, val values: Map? = null, + @Deprecated("Use the name property instead.") val title: String? = null, val description: String? = null, val required: Boolean = false, val type: KType, val validator: Option.(T?) -> Boolean = { true }, ) { + /** + * The name. + */ + val name = key + + /** + * An option. + * + * @param T The value type of the option. + * @param name The name. + * @param default The default value. + * @param values Eligible option values mapped to a human-readable name. + * @param description A description. + * @param required Whether the option is required. + * @param type The type of the option value (to handle type erasure). + * @param validator The function to validate the option value. + * + * @constructor Create a new [Option]. + */ + @PublishedApi + internal constructor( + name: String, + default: T? = null, + values: Map? = null, + description: String? = null, + required: Boolean = false, + type: KType, + validator: Option.(T?) -> Boolean = { true }, + ) : this(name, default, values, name, description, required, type, validator) + /** * The value of the [Option]. */ @@ -109,7 +144,7 @@ class Option @PublishedApi internal constructor( class Options internal constructor( private val options: Map>, ) : Map> by options { - internal constructor(options: Set>) : this(options.associateBy { it.key }) + internal constructor(options: Set>) : this(options.associateBy { it.name }) /** * Set an option's value. @@ -856,14 +891,14 @@ sealed class OptionException(errorMessage: String) : Exception(errorMessage, nul * * @param value The value that failed validation. */ - class ValueValidationException(value: Any?, option: Option<*>) : OptionException("The option value \"$value\" failed validation for ${option.key}") + class ValueValidationException(value: Any?, option: Option<*>) : OptionException("The option value \"$value\" failed validation for ${option.name}") /** * An exception thrown when a value is required but null was passed. * * @param option The [Option] that requires a value. */ - class ValueRequiredException(option: Option<*>) : OptionException("The option ${option.key} requires a value, but the value was null") + class ValueRequiredException(option: Option<*>) : OptionException("The option ${option.name} requires a value, but the value was null") /** * An exception thrown when a [Option] is not found. From bb8771bb8b8ab1724d957e56f4de88c02684d87b Mon Sep 17 00:00:00 2001 From: Vologhat Date: Tue, 7 Jan 2025 03:30:21 +0300 Subject: [PATCH 5/5] fix: Support UTF-8 chars when compiling instructions in Smali in non UTF-8 environments (#331) --- .../app/revanced/patcher/util/smali/InlineSmaliCompiler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompiler.kt b/src/main/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompiler.kt index 12d97d75..52af6bf3 100644 --- a/src/main/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompiler.kt +++ b/src/main/kotlin/app/revanced/patcher/util/smali/InlineSmaliCompiler.kt @@ -50,7 +50,7 @@ class InlineSmaliCompiler { registers, instructions, ) - val reader = InputStreamReader(input.byteInputStream()) + val reader = InputStreamReader(input.byteInputStream(), Charsets.UTF_8) val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15) val tokens = CommonTokenStream(lexer as TokenSource) val parser = smaliParser(tokens)