You've already forked revanced-patcher
mirror of
https://github.com/revanced/revanced-patcher
synced 2025-09-03 03:43:05 +02:00
Compare commits
14 Commits
main
...
v21.1.0-de
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b160a2adc0 | ||
![]() |
33fadcbd0c | ||
![]() |
68db95b99b | ||
![]() |
4f2ef3c47c | ||
![]() |
062ae14936 | ||
![]() |
99f431897e | ||
![]() |
d80abbcd17 | ||
![]() |
509ecc81e1 | ||
![]() |
e4e66b0d8b | ||
![]() |
bb8771bb8b | ||
![]() |
754b02e4ca | ||
![]() |
fe5fb736cb | ||
![]() |
fc505a8726 | ||
![]() |
88a3252574 |
39
CHANGELOG.md
39
CHANGELOG.md
@@ -1,3 +1,42 @@
|
||||
# [21.1.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.3...v21.1.0-dev.4) (2025-07-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Correctly save XML files in UTF-8 by using a bufferedWriter ([#356](https://github.com/ReVanced/revanced-patcher/issues/356)) ([33fadcb](https://github.com/ReVanced/revanced-patcher/commit/33fadcbd0c7076b848bdca4d62a9c684d5781232))
|
||||
|
||||
# [21.1.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.2...v21.1.0-dev.3) (2025-06-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Encode XML files as UTF-8 to fix compilation of resources ([#339](https://github.com/ReVanced/revanced-patcher/issues/339)) ([4f2ef3c](https://github.com/ReVanced/revanced-patcher/commit/4f2ef3c47cea76a26c464cfb45d4bb57fe7198b5))
|
||||
|
||||
# [21.1.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.1...v21.1.0-dev.2) (2025-06-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add back missing log by naming logger correctly ([#332](https://github.com/ReVanced/revanced-patcher/issues/332)) ([e4e66b0](https://github.com/ReVanced/revanced-patcher/commit/e4e66b0d8bb0986b79fb150b9c15da35b8e11561))
|
||||
* Support UTF-8 chars when compiling instructions in Smali in non UTF-8 environments ([#331](https://github.com/ReVanced/revanced-patcher/issues/331)) ([bb8771b](https://github.com/ReVanced/revanced-patcher/commit/bb8771bb8b8ab1724d957e56f4de88c02684d87b))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Use option name as key for simplicity and consistency ([754b02e](https://github.com/ReVanced/revanced-patcher/commit/754b02e4ca66ec10764d5205c6643f2d86d0c6a2))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* Use a buffered writer to reduce IO overhead ([#347](https://github.com/ReVanced/revanced-patcher/issues/347)) ([99f4318](https://github.com/ReVanced/revanced-patcher/commit/99f431897eb9e607987fd5d09b879d7eda442f3e))
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
|
@@ -73,6 +73,8 @@ public final class app/revanced/patcher/Patcher : java/io/Closeable {
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/PatcherConfig {
|
||||
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
}
|
||||
@@ -169,9 +171,12 @@ public final class app/revanced/patcher/patch/BytecodePatchContext : app/revance
|
||||
public final class app/revanced/patcher/patch/Option {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getDefault ()Ljava/lang/Object;
|
||||
public final fun getDescription ()Ljava/lang/String;
|
||||
public final fun getKey ()Ljava/lang/String;
|
||||
public final fun getName ()Ljava/lang/String;
|
||||
public final fun getRequired ()Z
|
||||
public final fun getTitle ()Ljava/lang/String;
|
||||
public final fun getType ()Lkotlin/reflect/KType;
|
||||
|
@@ -73,26 +73,26 @@ 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.
|
||||
val showAdsFingerprint = fingerprint {
|
||||
// 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.
|
||||
showAdsMatch.method.addInstructions(
|
||||
showAdsFingerprint.method.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static {}, LDisableAdsPatch;->shouldDisableAds()Z
|
||||
@@ -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<String>(key = "string")
|
||||
|
||||
val string by option<String>(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 {
|
||||
fingerprint.match!!.mutableMethod.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V")
|
||||
|
||||
execute {
|
||||
fingerprint.method.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
|
||||
|
@@ -1,3 +1,3 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
version = 21.0.0
|
||||
version = 21.1.0-dev.4
|
||||
|
@@ -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]
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -34,7 +34,7 @@ import java.util.logging.Logger
|
||||
class BytecodePatchContext internal constructor(private val config: PatcherConfig) :
|
||||
PatchContext<Set<PatcherResult.PatchedDexFile>>,
|
||||
Closeable {
|
||||
private val logger = Logger.getLogger(this::javaClass.name)
|
||||
private val logger = Logger.getLogger(this::class.java.name)
|
||||
|
||||
/**
|
||||
* [Opcodes] of the supplied [PatcherConfig.apkFile].
|
||||
|
@@ -20,16 +20,51 @@ import kotlin.reflect.typeOf
|
||||
* @constructor Create a new [Option].
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
class Option<T> @PublishedApi internal constructor(
|
||||
class Option<T>
|
||||
@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<String, T?>? = 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>.(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<String, T?>? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
type: KType,
|
||||
validator: Option<T>.(T?) -> Boolean = { true },
|
||||
) : this(name, default, values, name, description, required, type, validator)
|
||||
|
||||
/**
|
||||
* The value of the [Option].
|
||||
*/
|
||||
@@ -109,7 +144,7 @@ class Option<T> @PublishedApi internal constructor(
|
||||
class Options internal constructor(
|
||||
private val options: Map<String, Option<*>>,
|
||||
) : Map<String, Option<*>> by options {
|
||||
internal constructor(options: Set<Option<*>>) : this(options.associateBy { it.key })
|
||||
internal constructor(options: Set<Option<*>>) : 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.
|
||||
|
@@ -87,7 +87,8 @@ sealed class Patch<C : PatchContext<*>>(
|
||||
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()}"
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
Set this to false again to prevent the ResTable from being flagged as sparse falsely.
|
||||
*/
|
||||
metadata.apkInfo.sparseResources = false
|
||||
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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
|
@@ -5,6 +5,7 @@ import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.OutputKeys
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
@@ -34,15 +35,22 @@ class Document internal constructor(
|
||||
readerCount.remove(it)
|
||||
}
|
||||
|
||||
it.outputStream().use { stream ->
|
||||
TransformerFactory.newInstance()
|
||||
.newTransformer()
|
||||
.transform(DOMSource(this), StreamResult(stream))
|
||||
val transformer = TransformerFactory.newInstance().newTransformer()
|
||||
// Set to UTF-16 to prevent surrogate pairs from being escaped to invalid numeric character references, but save as UTF-8.
|
||||
if (isAndroid) {
|
||||
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-16")
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
|
||||
it.bufferedWriter(charset = Charsets.UTF_8).use { writer ->
|
||||
transformer.transform(DOMSource(this), StreamResult(writer))
|
||||
}
|
||||
} else {
|
||||
transformer.transform(DOMSource(this), StreamResult(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val readerCount = mutableMapOf<File, Int>()
|
||||
private val isAndroid = System.getProperty("java.runtime.name") == "Android Runtime"
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user