mirror of
https://github.com/revanced/revanced-patcher.git
synced 2025-05-06 15:14:26 +02:00
fix: resource patcher
This commit is contained in:
parent
1ce6098cad
commit
31815ca9ea
@ -1,5 +1,6 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.PackageMetadata
|
||||||
import app.revanced.patcher.data.PatcherData
|
import app.revanced.patcher.data.PatcherData
|
||||||
import app.revanced.patcher.data.base.Data
|
import app.revanced.patcher.data.base.Data
|
||||||
import app.revanced.patcher.data.implementation.findIndexed
|
import app.revanced.patcher.data.implementation.findIndexed
|
||||||
@ -16,11 +17,13 @@ import app.revanced.patcher.signature.implementation.method.resolver.MethodSigna
|
|||||||
import app.revanced.patcher.util.ListBackedSet
|
import app.revanced.patcher.util.ListBackedSet
|
||||||
import brut.androlib.Androlib
|
import brut.androlib.Androlib
|
||||||
import brut.androlib.meta.UsesFramework
|
import brut.androlib.meta.UsesFramework
|
||||||
|
import brut.androlib.options.BuildOptions
|
||||||
import brut.androlib.res.AndrolibResources
|
import brut.androlib.res.AndrolibResources
|
||||||
import brut.androlib.res.data.ResPackage
|
import brut.androlib.res.data.ResPackage
|
||||||
import brut.androlib.res.decoder.AXmlResourceParser
|
import brut.androlib.res.decoder.AXmlResourceParser
|
||||||
import brut.androlib.res.decoder.ResAttrDecoder
|
import brut.androlib.res.decoder.ResAttrDecoder
|
||||||
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
||||||
|
import brut.androlib.res.xml.ResXmlPatcher
|
||||||
import brut.directory.ExtFile
|
import brut.directory.ExtFile
|
||||||
import lanchon.multidexlib2.BasicDexFileNamer
|
import lanchon.multidexlib2.BasicDexFileNamer
|
||||||
import lanchon.multidexlib2.DexIO
|
import lanchon.multidexlib2.DexIO
|
||||||
@ -40,32 +43,36 @@ val NAMER = BasicDexFileNamer()
|
|||||||
class Patcher(
|
class Patcher(
|
||||||
private val options: PatcherOptions
|
private val options: PatcherOptions
|
||||||
) {
|
) {
|
||||||
val packageVersion: String
|
val data: PatcherData
|
||||||
val packageName: String
|
|
||||||
|
|
||||||
private lateinit var usesFramework: UsesFramework
|
|
||||||
private val patcherData: PatcherData
|
|
||||||
private val opcodes: Opcodes
|
private val opcodes: Opcodes
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val extFileInput = ExtFile(options.inputFile)
|
val extInputFile = ExtFile(options.inputFile)
|
||||||
val outDir = File(options.resourceCacheDirectory)
|
val outDir = File(options.resourceCacheDirectory)
|
||||||
|
|
||||||
if (outDir.exists()) outDir.deleteRecursively()
|
if (outDir.exists()) outDir.deleteRecursively()
|
||||||
outDir.mkdir()
|
outDir.mkdirs()
|
||||||
|
|
||||||
// load the resource table from the input file
|
|
||||||
val androlib = Androlib()
|
val androlib = Androlib()
|
||||||
val resourceTable = androlib.getResTable(extFileInput, true)
|
val resourceTable = androlib.getResTable(extInputFile, true)
|
||||||
|
|
||||||
|
val packageMetadata = PackageMetadata()
|
||||||
|
|
||||||
if (options.patchResources) {
|
if (options.patchResources) {
|
||||||
// 1. decode resources to cache directory
|
// decode resources to cache directory
|
||||||
androlib.decodeManifestWithResources(extFileInput, outDir, resourceTable)
|
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
|
||||||
androlib.decodeResourcesFull(extFileInput, outDir, resourceTable)
|
androlib.decodeResourcesFull(extInputFile, outDir, resourceTable)
|
||||||
|
|
||||||
// 2. read framework ids from the resource table
|
// read additional metadata from the resource table
|
||||||
usesFramework = UsesFramework()
|
packageMetadata.metaInfo.usesFramework = UsesFramework().let { usesFramework ->
|
||||||
usesFramework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
|
usesFramework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
|
||||||
|
|
||||||
|
usesFramework
|
||||||
|
}
|
||||||
|
packageMetadata.metaInfo.doNotCompress = buildList {
|
||||||
|
androlib.recordUncompressedFiles(extInputFile, this)
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// create decoder for the resource table
|
// create decoder for the resource table
|
||||||
val decoder = ResAttrDecoder()
|
val decoder = ResAttrDecoder()
|
||||||
@ -80,19 +87,27 @@ class Patcher(
|
|||||||
XmlPullStreamDecoder(
|
XmlPullStreamDecoder(
|
||||||
axmlParser, AndrolibResources().resXmlSerializer
|
axmlParser, AndrolibResources().resXmlSerializer
|
||||||
).decodeManifest(
|
).decodeManifest(
|
||||||
extFileInput.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
|
extInputFile.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set package information
|
packageMetadata.packageName = resourceTable.currentResPackage.name
|
||||||
packageVersion = resourceTable.versionInfo.versionName
|
packageMetadata.packageVersion = resourceTable.versionInfo.versionName
|
||||||
packageName = resourceTable.currentResPackage.name
|
packageMetadata.metaInfo.versionInfo = resourceTable.versionInfo
|
||||||
|
packageMetadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
||||||
|
|
||||||
// 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).let { dexFile ->
|
||||||
|
// get the opcodes
|
||||||
opcodes = dexFile.opcodes
|
opcodes = dexFile.opcodes
|
||||||
|
|
||||||
// save to patcher data
|
dexFile
|
||||||
patcherData = PatcherData(dexFile.classes.toMutableList(), options.resourceCacheDirectory)
|
}
|
||||||
|
|
||||||
|
// finally create patcher data
|
||||||
|
data = PatcherData(
|
||||||
|
dexFile.classes.toMutableList(), options.resourceCacheDirectory, packageMetadata
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,23 +117,25 @@ class Patcher(
|
|||||||
* @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: Iterable<File>, allowedOverwrites: Iterable<String> = emptyList(), throwOnDuplicates: Boolean = false
|
files: List<File>, allowedOverwrites: Iterable<String> = emptyList(), throwOnDuplicates: Boolean = false
|
||||||
) {
|
) {
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
val dexFile = MultiDexIO.readDexFile(true, file, NAMER, null, null)
|
MultiDexIO.readDexFile(true, file, NAMER, null, null).let { dexFile ->
|
||||||
for (classDef in dexFile.classes) {
|
for (classDef in dexFile.classes) {
|
||||||
val e = patcherData.bytecodeData.classes.internalClasses.findIndexed { it.type == classDef.type }
|
val e =
|
||||||
|
data.bytecodeData.classes.internalClasses.findIndexed { internalClass -> internalClass.type == classDef.type }
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
if (throwOnDuplicates) {
|
if (throwOnDuplicates) {
|
||||||
throw Exception("Class ${classDef.type} has already been added to the patcher.")
|
throw Exception("Class ${classDef.type} has already been added to the patcher.")
|
||||||
}
|
}
|
||||||
val (_, idx) = e
|
val (_, idx) = e
|
||||||
if (allowedOverwrites.contains(classDef.type)) {
|
if (allowedOverwrites.contains(classDef.type)) {
|
||||||
patcherData.bytecodeData.classes.internalClasses[idx] = classDef
|
data.bytecodeData.classes.internalClasses[idx] = classDef
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
patcherData.bytecodeData.classes.internalClasses.add(classDef)
|
data.bytecodeData.classes.internalClasses.add(classDef)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,11 +143,58 @@ class Patcher(
|
|||||||
/**
|
/**
|
||||||
* Save the patched dex file.
|
* Save the patched dex file.
|
||||||
*/
|
*/
|
||||||
fun save(): Map<String, MemoryDataStore> {
|
fun save(): PatcherResult {
|
||||||
|
val packageMetadata = data.packageMetadata
|
||||||
|
val metaInfo = packageMetadata.metaInfo
|
||||||
|
|
||||||
|
if (options.patchResources) {
|
||||||
|
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
|
||||||
|
|
||||||
|
val androlibResources = AndrolibResources().let { resources ->
|
||||||
|
resources.buildOptions = BuildOptions().let { options ->
|
||||||
|
// TODO: options.useAapt2 = true
|
||||||
|
// TODO: options.aaptPath = ""
|
||||||
|
options.isFramework = metaInfo.isFrameworkApk
|
||||||
|
options.resourcesAreCompressed = metaInfo.compressionType
|
||||||
|
options.doNotCompress = metaInfo.doNotCompress
|
||||||
|
|
||||||
|
options
|
||||||
|
}
|
||||||
|
|
||||||
|
resources.setSdkInfo(metaInfo.sdkInfo)
|
||||||
|
resources.setVersionInfo(metaInfo.versionInfo)
|
||||||
|
resources.setSharedLibrary(metaInfo.sharedLibrary)
|
||||||
|
resources.setSparseResources(metaInfo.sparseResources)
|
||||||
|
|
||||||
|
resources
|
||||||
|
}
|
||||||
|
|
||||||
|
val manifestFile = cacheDirectory.resolve("AndroidManifest.xml")
|
||||||
|
|
||||||
|
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifestFile)
|
||||||
|
|
||||||
|
cacheDirectory.resolve("aapt_temp_file").let { temporalFile ->
|
||||||
|
val resDirectory = cacheDirectory.resolve("res")
|
||||||
|
val includedFiles =
|
||||||
|
metaInfo.usesFramework.ids.map { id -> androlibResources.getFrameworkApk(id, metaInfo.usesFramework.tag) }
|
||||||
|
.toTypedArray()
|
||||||
|
|
||||||
|
androlibResources.aaptPackage(
|
||||||
|
temporalFile, manifestFile, resDirectory, null,
|
||||||
|
null, includedFiles
|
||||||
|
)
|
||||||
|
|
||||||
|
// write packaged resources to cache directory
|
||||||
|
// TODO: consider returning a list of the files instead of extracting them to the cache directory,
|
||||||
|
// less disk but more ram usage
|
||||||
|
ExtFile(temporalFile).directory.copyToDir(cacheDirectory.resolve("build/"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val newDexFile = object : DexFile {
|
val newDexFile = object : DexFile {
|
||||||
override fun getClasses(): Set<ClassDef> {
|
override fun getClasses(): Set<ClassDef> {
|
||||||
patcherData.bytecodeData.classes.applyProxies()
|
data.bytecodeData.classes.applyProxies()
|
||||||
return ListBackedSet(patcherData.bytecodeData.classes.internalClasses)
|
return ListBackedSet(data.bytecodeData.classes.internalClasses)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOpcodes(): Opcodes {
|
override fun getOpcodes(): Opcodes {
|
||||||
@ -138,21 +202,19 @@ class Patcher(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build modified resources
|
|
||||||
if (options.patchResources) {
|
|
||||||
val extDir = ExtFile(options.resourceCacheDirectory)
|
|
||||||
|
|
||||||
// TODO: figure out why a new instance of Androlib is necessary here
|
|
||||||
Androlib().buildResources(extDir, usesFramework)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write dex modified files
|
// write dex modified files
|
||||||
val output = mutableMapOf<String, MemoryDataStore>()
|
val dexFiles = mutableMapOf<String, MemoryDataStore>()
|
||||||
MultiDexIO.writeDexFile(
|
MultiDexIO.writeDexFile(
|
||||||
true, -1, // core count
|
true, -1, // core count
|
||||||
output, NAMER, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
|
dexFiles, NAMER, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
|
||||||
|
)
|
||||||
|
|
||||||
|
return PatcherResult(
|
||||||
|
dexFiles.map {
|
||||||
|
app.revanced.patcher.util.dex.DexFile(it.key, it.value)
|
||||||
|
},
|
||||||
|
metaInfo.doNotCompress.toList()
|
||||||
)
|
)
|
||||||
return output
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,7 +222,7 @@ class Patcher(
|
|||||||
* @param patches [Patch]es The patches to add.
|
* @param patches [Patch]es The patches to add.
|
||||||
*/
|
*/
|
||||||
fun addPatches(patches: Iterable<Class<out Patch<Data>>>) {
|
fun addPatches(patches: Iterable<Class<out Patch<Data>>>) {
|
||||||
patcherData.patches.addAll(patches)
|
data.patches.addAll(patches)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -197,12 +259,12 @@ class Patcher(
|
|||||||
|
|
||||||
// TODO: find a solution for this
|
// TODO: find a solution for this
|
||||||
val data = if (isResourcePatch) {
|
val data = if (isResourcePatch) {
|
||||||
patcherData.resourceData
|
data.resourceData
|
||||||
} else {
|
} else {
|
||||||
MethodSignatureResolver(
|
MethodSignatureResolver(
|
||||||
patcherData.bytecodeData.classes.internalClasses, (patchInstance as BytecodePatch).signatures
|
data.bytecodeData.classes.internalClasses, (patchInstance as BytecodePatch).signatures
|
||||||
).resolve(patcherData)
|
).resolve(data)
|
||||||
patcherData.bytecodeData
|
data.bytecodeData
|
||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
@ -225,7 +287,7 @@ class Patcher(
|
|||||||
val appliedPatches = mutableListOf<String>()
|
val appliedPatches = mutableListOf<String>()
|
||||||
|
|
||||||
return buildMap {
|
return buildMap {
|
||||||
for (patch in patcherData.patches) {
|
for (patch in data.patches) {
|
||||||
val result = applyPatch(patch, appliedPatches)
|
val result = applyPatch(patch, appliedPatches)
|
||||||
|
|
||||||
val name = patch.patchName
|
val name = patch.patchName
|
||||||
|
@ -3,6 +3,7 @@ package app.revanced.patcher
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Options for a 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.
|
||||||
|
13
src/main/kotlin/app/revanced/patcher/PatcherResult.kt
Normal file
13
src/main/kotlin/app/revanced/patcher/PatcherResult.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.util.dex.DexFile
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a patcher.
|
||||||
|
* @param dexFiles The patched dex files.
|
||||||
|
* @param doNotCompress List of relative paths to files to exclude from compressing.
|
||||||
|
*/
|
||||||
|
data class PatcherResult(
|
||||||
|
val dexFiles: List<DexFile>,
|
||||||
|
val doNotCompress: List<String>? = null
|
||||||
|
)
|
13
src/main/kotlin/app/revanced/patcher/data/PackageMetadata.kt
Normal file
13
src/main/kotlin/app/revanced/patcher/data/PackageMetadata.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.patcher.data
|
||||||
|
|
||||||
|
import brut.androlib.meta.MetaInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata about a package.
|
||||||
|
*/
|
||||||
|
class PackageMetadata {
|
||||||
|
lateinit var packageName: String
|
||||||
|
lateinit var packageVersion: String
|
||||||
|
|
||||||
|
internal val metaInfo: MetaInfo = MetaInfo()
|
||||||
|
}
|
@ -7,10 +7,12 @@ import app.revanced.patcher.patch.base.Patch
|
|||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
internal data class PatcherData(
|
data class PatcherData(
|
||||||
val internalClasses: MutableList<ClassDef>,
|
internal val internalClasses: MutableList<ClassDef>,
|
||||||
val resourceCacheDirectory: String
|
internal val resourceCacheDirectory: String,
|
||||||
|
val packageMetadata: PackageMetadata
|
||||||
) {
|
) {
|
||||||
|
|
||||||
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
||||||
|
|
||||||
internal val bytecodeData = BytecodeData(internalClasses)
|
internal val bytecodeData = BytecodeData(internalClasses)
|
||||||
|
@ -41,13 +41,15 @@ object PatchExtensions {
|
|||||||
val Class<out Patch<Data>>.patchName: String
|
val Class<out Patch<Data>>.patchName: String
|
||||||
get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||||
val Class<out Patch<Data>>.version get() = recursiveAnnotation(Version::class)?.version
|
val Class<out Patch<Data>>.version get() = recursiveAnnotation(Version::class)?.version
|
||||||
|
val Class<out Patch<Data>>.excludeByDefault get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)!!.excludeByDefault
|
||||||
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
|
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
|
||||||
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Dependencies::class)?.dependencies
|
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Dependencies::class)?.dependencies
|
||||||
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||||
}
|
}
|
||||||
|
|
||||||
object MethodSignatureExtensions {
|
object MethodSignatureExtensions {
|
||||||
val MethodSignature.name: String get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
val MethodSignature.name: String
|
||||||
|
get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||||
val MethodSignature.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
val MethodSignature.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
||||||
val MethodSignature.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
val MethodSignature.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
||||||
val MethodSignature.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
val MethodSignature.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||||
|
@ -10,7 +10,7 @@ import kotlin.reflect.KClass
|
|||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@MustBeDocumented
|
@MustBeDocumented
|
||||||
annotation class Patch
|
annotation class Patch(val excludeByDefault: Boolean = false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation for dependencies of [Patch]es .
|
* Annotation for dependencies of [Patch]es .
|
||||||
|
10
src/main/kotlin/app/revanced/patcher/util/dex/DexFile.kt
Normal file
10
src/main/kotlin/app/revanced/patcher/util/dex/DexFile.kt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package app.revanced.patcher.util.dex
|
||||||
|
|
||||||
|
import org.jf.dexlib2.writer.io.MemoryDataStore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for dex files.
|
||||||
|
* @param name The original name of the dex file
|
||||||
|
* @param memoryDataStore The data store for the dex file.
|
||||||
|
*/
|
||||||
|
data class DexFile(val name: String, val memoryDataStore: MemoryDataStore)
|
Loading…
x
Reference in New Issue
Block a user