mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2024-11-10 01:02:22 +01:00
refactor: do initial refactoring attempt
This commit is contained in:
parent
c52f0b80f2
commit
c6fdf97794
9 changed files with 183 additions and 261 deletions
|
@ -7,6 +7,29 @@ package app.revanced.arsc
|
|||
* @param throwable The corresponding [Throwable].
|
||||
*/
|
||||
sealed class ApkResourceException(message: String, throwable: Throwable? = null) : Exception(message, throwable) {
|
||||
/**
|
||||
* An exception when locking resources.
|
||||
*
|
||||
* @param message The exception message.
|
||||
* @param throwable The corresponding [Throwable].
|
||||
*/
|
||||
class Locked(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
|
||||
|
||||
/**
|
||||
* An exception when writing resources.
|
||||
*
|
||||
* @param message The exception message.
|
||||
* @param throwable The corresponding [Throwable].
|
||||
*/
|
||||
class Write(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
|
||||
|
||||
/**
|
||||
* An exception when reading resources.
|
||||
*
|
||||
* @param message The exception message.
|
||||
* @param throwable The corresponding [Throwable].
|
||||
*/
|
||||
class Read(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
|
||||
/**
|
||||
* An exception when decoding resources.
|
||||
*
|
||||
|
@ -45,5 +68,5 @@ sealed class ApkResourceException(message: String, throwable: Throwable? = null)
|
|||
/**
|
||||
* An exception thrown when the Apk file not have a resource table, but was expected to have one.
|
||||
*/
|
||||
object MissingResourceTable : ApkResourceException("Apk does not have a resource table.")
|
||||
class MissingResourceTable : ApkResourceException("Apk does not have a resource table.")
|
||||
}
|
|
@ -2,163 +2,27 @@
|
|||
|
||||
package app.revanced.arsc.archive
|
||||
|
||||
import app.revanced.arsc.ApkResourceException
|
||||
import app.revanced.arsc.logging.Logger
|
||||
import app.revanced.arsc.resource.ResourceContainer
|
||||
import app.revanced.arsc.resource.ResourceFile
|
||||
import app.revanced.arsc.xml.LazyXMLInputSource
|
||||
import com.reandroid.apk.ApkModule
|
||||
import com.reandroid.archive.ByteInputSource
|
||||
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock
|
||||
import com.reandroid.arsc.chunk.xml.ResXmlDocument
|
||||
import com.reandroid.xml.XMLDocument
|
||||
import java.io.Closeable
|
||||
import com.reandroid.apk.DexFileInputSource
|
||||
import com.reandroid.archive.InputSource
|
||||
import java.io.File
|
||||
import java.io.Flushable
|
||||
|
||||
/**
|
||||
* A class for reading/writing files in an [ApkModule].
|
||||
*
|
||||
* @param module The [ApkModule] to operate on.
|
||||
*/
|
||||
class Archive(private val module: ApkModule) {
|
||||
lateinit var resources: ResourceContainer
|
||||
class Archive(internal val module: ApkModule) : Flushable {
|
||||
val mainPackageResources = ResourceContainer(this, module.tableBlock)
|
||||
|
||||
/**
|
||||
* The zip archive for the [ApkModule] this [Archive] is operating on.
|
||||
*/
|
||||
private val moduleArchive = module.apkArchive
|
||||
|
||||
private val lockedFiles = mutableMapOf<String, ResourceFile>()
|
||||
|
||||
/**
|
||||
* Lock the [ResourceFile], preventing it from being opened again until it is unlocked.
|
||||
*/
|
||||
fun lock(file: ResourceFile) {
|
||||
val path = file.handle.archivePath
|
||||
if (lockedFiles.contains(path)) {
|
||||
throw ApkResourceException.Decode(
|
||||
"${file.handle.virtualPath} is currently being used. Close it before opening it again."
|
||||
)
|
||||
}
|
||||
lockedFiles[path] = file
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock the [ResourceFile], allowing patches to open it again.
|
||||
*/
|
||||
fun unlock(file: ResourceFile) {
|
||||
lockedFiles.remove(file.handle.archivePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all open files and encodes all XML files to binary XML.
|
||||
*
|
||||
* @param logger The [Logger] of the [app.revanced.patcher.Patcher].
|
||||
*/
|
||||
fun cleanup(logger: Logger?) {
|
||||
lockedFiles.values.toList().forEach {
|
||||
logger?.warn("${it.handle.virtualPath} was never closed!")
|
||||
it.close()
|
||||
}
|
||||
|
||||
moduleArchive.listInputSources().filterIsInstance<LazyXMLInputSource>()
|
||||
.forEach(LazyXMLInputSource::encode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the archive to disk.
|
||||
*
|
||||
* @param output The file to write the updated archive to.
|
||||
*/
|
||||
fun save(output: File) = module.writeApk(output)
|
||||
|
||||
/**
|
||||
* Read an entry from the archive.
|
||||
*
|
||||
* @param path The archive path to read from.
|
||||
* @return A [ArchiveResource] containing the contents of the entry.
|
||||
*/
|
||||
fun read(path: String) = moduleArchive.getInputSource(path)?.let { inputSource ->
|
||||
when {
|
||||
inputSource is LazyXMLInputSource -> ArchiveResource.XmlResource(inputSource.document)
|
||||
|
||||
ResXmlDocument.isResXmlBlock(inputSource.openStream()) -> ArchiveResource.XmlResource(
|
||||
module
|
||||
.loadResXmlDocument(inputSource)
|
||||
.decodeToXml(resources.resourceTable.entryStore, resources.packageBlock?.id ?: 0)
|
||||
)
|
||||
|
||||
else -> ArchiveResource.RawResource(inputSource.openStream().use { it.readAllBytes() })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the manifest from the archive as an [AndroidManifestBlock].
|
||||
*
|
||||
* @return The [AndroidManifestBlock] contained in this archive.
|
||||
*/
|
||||
fun readManifest(): AndroidManifestBlock =
|
||||
moduleArchive.getInputSource(AndroidManifestBlock.FILE_NAME).openStream().use { AndroidManifestBlock.load(it) }
|
||||
|
||||
/**
|
||||
* Reads all dex files from the archive.
|
||||
*
|
||||
* @return A [Map] containing all the dex files.
|
||||
*/
|
||||
fun readDexFiles() = module.listDexFiles().associate { file -> file.name to file.openStream() }
|
||||
|
||||
/**
|
||||
* Write the byte array to the archive entry.
|
||||
*
|
||||
* @param path The archive path to read from.
|
||||
* @param content The content of the file.
|
||||
*/
|
||||
fun writeRaw(path: String, content: ByteArray) =
|
||||
moduleArchive.add(ByteInputSource(content, path))
|
||||
|
||||
/**
|
||||
* Write the XML to the entry associated.
|
||||
*
|
||||
* @param path The archive path to read from.
|
||||
* @param document The XML document to encode.
|
||||
*/
|
||||
fun writeXml(path: String, document: XMLDocument) = moduleArchive.add(
|
||||
LazyXMLInputSource(
|
||||
path,
|
||||
document,
|
||||
resources,
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* A resource file of an [Archive].
|
||||
*/
|
||||
abstract class ArchiveResource() : Closeable {
|
||||
private var pendingWrite = false
|
||||
|
||||
override fun close() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
/**
|
||||
* An [ResXmlDocument] resource file.
|
||||
*
|
||||
* @param xmlResource The [XMLDocument] of the file.
|
||||
*/
|
||||
class XmlResource(val xmlResource: XMLDocument, archive: Archive) : ArchiveResource()
|
||||
|
||||
/**
|
||||
* A raw resource file.
|
||||
*
|
||||
* @param data The raw data of the file.
|
||||
*/
|
||||
class RawResource(val data: ByteArray, archive: Archive) : ArchiveResource()
|
||||
|
||||
/**
|
||||
* @param virtualPath The resource file path. Example: /res/drawable-hdpi/icon.png.
|
||||
* @param archivePath The actual file path in the archive. Example: res/4a.png.
|
||||
* @param onClose An action to perform when the file associated with this handle is closed
|
||||
*/
|
||||
data class Handle(val virtualPath: String, val archivePath: String, val onClose: () -> Unit)
|
||||
fun save(output: File) {
|
||||
flush()
|
||||
module.writeApk(output)
|
||||
}
|
||||
fun readDexFiles(): MutableList<DexFileInputSource> = module.listDexFiles()
|
||||
fun write(inputSource: InputSource) = module.apkArchive.add(inputSource) // Overwrites existing files.
|
||||
fun read(name: String): InputSource? = module.apkArchive.getInputSource(name)
|
||||
override fun flush() = mainPackageResources.flush()
|
||||
}
|
|
@ -20,7 +20,7 @@ sealed class Resource {
|
|||
internal abstract fun write(entry: Entry, resources: ResourceContainer)
|
||||
}
|
||||
|
||||
internal val Resource.complex get() = when (this) {
|
||||
internal val Resource.isComplex get() = when (this) {
|
||||
is Scalar -> false
|
||||
is Complex -> true
|
||||
}
|
||||
|
|
|
@ -3,54 +3,102 @@ package app.revanced.arsc.resource
|
|||
import app.revanced.arsc.ApkResourceException
|
||||
import app.revanced.arsc.archive.Archive
|
||||
import com.reandroid.apk.xmlencoder.EncodeUtil
|
||||
import com.reandroid.arsc.chunk.PackageBlock
|
||||
import com.reandroid.arsc.chunk.TableBlock
|
||||
import com.reandroid.arsc.chunk.xml.ResXmlDocument
|
||||
import com.reandroid.arsc.value.Entry
|
||||
import com.reandroid.arsc.value.ResConfig
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.Flushable
|
||||
|
||||
/**
|
||||
* A high-level API for modifying the resources contained in an APK file.
|
||||
*
|
||||
* @param archive The [Archive] containing this resource table.
|
||||
* @param tableBlock The resources file of this APK file. Typically named "resources.arsc".
|
||||
*/
|
||||
class ResourceContainer(private val archive: Archive, internal val tableBlock: TableBlock?) {
|
||||
internal val packageBlock = tableBlock?.pickOne() // Pick the main PackageBlock.
|
||||
class ResourceContainer(private val archive: Archive, internal val tableBlock: TableBlock) : Flushable {
|
||||
private val packageBlock = tableBlock.pickOne() // Pick the main package block.
|
||||
internal lateinit var resourceTable: ResourceTable // TODO: Set this.
|
||||
|
||||
internal lateinit var resourceTable: ResourceTable
|
||||
private val lockedResourceFileNames = mutableSetOf<String>()
|
||||
|
||||
init {
|
||||
archive.resources = this
|
||||
private fun lock(resourceFile: ResourceFile) {
|
||||
if (resourceFile.name in lockedResourceFileNames) {
|
||||
throw ApkResourceException.Locked("Resource file ${resourceFile.name} is already locked.")
|
||||
}
|
||||
|
||||
lockedResourceFileNames.add(resourceFile.name)
|
||||
}
|
||||
|
||||
private fun unlock(resourceFile: ResourceFile) {
|
||||
lockedResourceFileNames.remove(resourceFile.name)
|
||||
}
|
||||
|
||||
|
||||
fun <T : ResourceFile> openResource(name: String): ResourceFileEditor<T> {
|
||||
val inputSource = archive.read(name)
|
||||
?: throw ApkResourceException.Read("Resource file $name not found.")
|
||||
|
||||
val resourceFile = when {
|
||||
ResXmlDocument.isResXmlBlock(inputSource.openStream()) -> {
|
||||
val xmlDocument = archive.module
|
||||
.loadResXmlDocument(inputSource)
|
||||
.decodeToXml(resourceTable.entryStore, packageBlock.id)
|
||||
|
||||
ResourceFile.XmlResourceFile(name, xmlDocument)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val bytes = inputSource.openStream().use { it.readAllBytes() }
|
||||
|
||||
ResourceFile.BinaryResourceFile(name, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return ResourceFileEditor(resourceFile as T).also {
|
||||
lockedResourceFileNames.add(name)
|
||||
}
|
||||
} catch (e: ClassCastException) {
|
||||
throw ApkResourceException.Decode("Resource file $name is not ${resourceFile::class}.", e)
|
||||
}
|
||||
}
|
||||
|
||||
inner class ResourceFileEditor<T : ResourceFile> internal constructor(
|
||||
private val resourceFile: T,
|
||||
) : Closeable {
|
||||
fun use(block: (T) -> Unit) = block(resourceFile)
|
||||
override fun close() {
|
||||
lockedResourceFileNames.remove(resourceFile.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun flush() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a resource file, creating it if the file does not exist.
|
||||
*
|
||||
* @param path The resource file path.
|
||||
* @return The corresponding [ResourceFile],
|
||||
* @return The corresponding [ResourceFiles],
|
||||
*/
|
||||
fun openFile(path: String) = ResourceFile(createHandle(path), archive)
|
||||
fun openFile(path: String) = ResourceFiles(createHandle(path), archive)
|
||||
|
||||
private fun getPackageBlock() = packageBlock ?: throw ApkResourceException.MissingResourceTable
|
||||
|
||||
internal fun getOrCreateString(value: String) =
|
||||
tableBlock?.stringPool?.getOrCreate(value) ?: throw ApkResourceException.MissingResourceTable
|
||||
|
||||
/**
|
||||
* Set the value of the [Entry] to the one specified.
|
||||
*
|
||||
* @param value The new value.
|
||||
*/
|
||||
private fun Entry.setTo(value: Resource) {
|
||||
val savedRef = specReference
|
||||
ensureComplex(value.complex)
|
||||
if (savedRef != 0) {
|
||||
private fun Entry.set(resource: Resource) {
|
||||
val existingEntryNameReference = specReference
|
||||
|
||||
// Sets this.specReference if the entry is not yet initialized.
|
||||
// Sets this.specReference to 0 if the resource type of the existing entry changes.
|
||||
ensureComplex(resource.isComplex)
|
||||
|
||||
if (existingEntryNameReference != 0) {
|
||||
// Preserve the entry name by restoring the previous spec block reference (if present).
|
||||
specReference = savedRef
|
||||
specReference = existingEntryNameReference
|
||||
}
|
||||
|
||||
value.write(this, this@ResourceContainer)
|
||||
resource.write(this, this@ResourceContainer)
|
||||
resourceTable.registerChanged(this)
|
||||
}
|
||||
|
||||
|
@ -73,12 +121,12 @@ class ResourceContainer(private val archive: Archive, internal val tableBlock: T
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a [ResourceFile.Handle] that can be used to open a [ResourceFile].
|
||||
* Create a [ResourceFiles.Handle] that can be used to open a [ResourceFiles].
|
||||
* This may involve looking it up in the resource table to find the actual location in the archive.
|
||||
*
|
||||
* @param path The path of the resource.
|
||||
*/
|
||||
private fun createHandle(path: String): ResourceFile.Handle {
|
||||
private fun createHandle(path: String): ResourceFiles.Handle {
|
||||
if (path.startsWith("res/values")) throw ApkResourceException.Decode("Decoding the resource table as a file is not supported")
|
||||
|
||||
var onClose = {}
|
||||
|
@ -97,42 +145,23 @@ class ResourceContainer(private val archive: Archive, internal val tableBlock: T
|
|||
archivePath = it
|
||||
} ?: run {
|
||||
// An entry for this specific resource file was not found in the resource table, so we have to register it after we save.
|
||||
onClose = { getOrCreateResource(type, name, StringResource(archivePath), qualifiers) }
|
||||
onClose = { setResource(type, name, StringResource(archivePath), qualifiers) }
|
||||
}
|
||||
}
|
||||
|
||||
return ResourceFile.Handle(path, archivePath, onClose)
|
||||
return ResourceFiles.Handle(path, archivePath, onClose)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a resource.
|
||||
*
|
||||
* @param type The resource type.
|
||||
* @param name The name of the resource.
|
||||
* @param resource The resource data.
|
||||
* @param qualifiers The resource configuration.
|
||||
* @return The resource ID for the resource.
|
||||
*/
|
||||
fun getOrCreateResource(type: String, name: String, resource: Resource, qualifiers: String? = null) =
|
||||
getPackageBlock().getOrCreate(qualifiers, type, name).also { it.setTo(resource) }.resourceId
|
||||
fun setResource(type: String, entryName: String, resource: Resource, qualifiers: String? = null) =
|
||||
getPackageBlock().getOrCreate(qualifiers, type, entryName).also { it.set(resource) }.resourceId
|
||||
|
||||
/**
|
||||
* Create or update multiple resources in an ARSC type block.
|
||||
*
|
||||
* @param type The resource type.
|
||||
* @param map A map of resource names to the corresponding value.
|
||||
* @param configuration The resource configuration.
|
||||
*/
|
||||
fun setGroup(type: String, map: Map<String, Resource>, configuration: String? = null) {
|
||||
fun setResources(type: String, resources: Map<String, Resource>, configuration: String? = null) {
|
||||
getPackageBlock().getOrCreateSpecTypePair(type).getOrCreateTypeBlock(configuration).apply {
|
||||
map.forEach { (name, value) -> getOrCreateEntry(name).setTo(value) }
|
||||
resources.forEach { (entryName, resource) -> getOrCreateEntry(entryName).set(resource) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the [PackageBlock] name to match the manifest.
|
||||
*/
|
||||
fun refreshPackageName() {
|
||||
packageBlock?.name = archive.readManifest().packageName
|
||||
override fun flush() {
|
||||
packageBlock?.name = archive
|
||||
}
|
||||
}
|
|
@ -2,31 +2,25 @@ package app.revanced.arsc.resource
|
|||
|
||||
import app.revanced.arsc.ApkResourceException
|
||||
import app.revanced.arsc.archive.Archive
|
||||
import app.revanced.arsc.resource.ResourceFile.Handle
|
||||
import com.reandroid.archive.InputSource
|
||||
import com.reandroid.xml.XMLDocument
|
||||
import com.reandroid.xml.XMLException
|
||||
import java.io.*
|
||||
|
||||
/**
|
||||
* Instantiate a [ResourceFile] and lock the file which [handle] is associated with.
|
||||
*
|
||||
* @param handle The [Handle] associated with this file.
|
||||
* @param archive The [Archive] that the file resides in.
|
||||
*/
|
||||
class ResourceFile private constructor(
|
||||
internal val handle: Handle,
|
||||
private val archive: Archive,
|
||||
readResult: Archive.ArchiveResource?
|
||||
) : Closeable {
|
||||
private var pendingWrite = false
|
||||
private val isXmlResource = readResult is Archive.ArchiveResource.XmlResource
|
||||
|
||||
init {
|
||||
archive.lock(this)
|
||||
}
|
||||
abstract class ResourceFile(val name: String) {
|
||||
internal var realName: String? = null
|
||||
|
||||
class XmlResourceFile(name: String, val document: XMLDocument) : ResourceFile(name)
|
||||
class BinaryResourceFile(name: String, var bytes: ByteArray) : ResourceFile(name)
|
||||
}
|
||||
|
||||
|
||||
class ResourceFiles private constructor(
|
||||
) : Closeable {
|
||||
|
||||
/**
|
||||
* Instantiate a [ResourceFile].
|
||||
* Instantiate a [ResourceFiles].
|
||||
*
|
||||
* @param handle The [Handle] associated with this file.
|
||||
* @param archive The [Archive] that the file resides in.
|
||||
|
@ -43,6 +37,10 @@ class ResourceFile private constructor(
|
|||
}
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_BUFFER_SIZE = 1024
|
||||
}
|
||||
|
||||
var contents = readResult?.data ?: ByteArray(0)
|
||||
set(value) {
|
||||
pendingWrite = true
|
||||
|
@ -76,11 +74,18 @@ class ResourceFile private constructor(
|
|||
|
||||
fun inputStream(): InputStream = ByteArrayInputStream(contents)
|
||||
|
||||
fun outputStream(bufferSize: Int = DEFAULT_BUFFER_SIZE): OutputStream =
|
||||
object : ByteArrayOutputStream(bufferSize) {
|
||||
override fun close() {
|
||||
this@ResourceFiles.contents = if (buf.size > count) buf.copyOf(count) else buf
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param virtualPath The resource file path. Example: /res/drawable-hdpi/icon.png.
|
||||
* @param archivePath The actual file path in the archive. Example: res/4a.png.
|
||||
* @param onClose An action to perform when the file associated with this handle is closed
|
||||
*/
|
||||
internal data class Handle(val virtualPath: String, val archivePath: String, val onClose: () -> Unit)
|
||||
|
||||
}
|
|
@ -11,7 +11,7 @@ import com.reandroid.common.TableEntryStore
|
|||
* A high-level API for resolving resources in the resource table, which spans the entire ApkBundle.
|
||||
*/
|
||||
class ResourceTable(base: ResourceContainer, all: Sequence<ResourceContainer>) {
|
||||
private val packageName = base.packageBlock!!.name
|
||||
private val packageName = base.tableBlock!!.name
|
||||
|
||||
/**
|
||||
* A [TableEntryStore] used to decode XML.
|
||||
|
@ -87,7 +87,7 @@ class ResourceTable(base: ResourceContainer, all: Sequence<ResourceContainer>) {
|
|||
}
|
||||
|
||||
base.also {
|
||||
encodeMaterials.currentPackage = it.packageBlock
|
||||
encodeMaterials.currentPackage = it.tableBlock
|
||||
|
||||
it.tableBlock!!.frameWorks.forEach { fw ->
|
||||
if (fw is FrameworkTable) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package app.revanced.arsc.xml
|
||||
|
||||
import app.revanced.arsc.ApkResourceException
|
||||
import app.revanced.arsc.resource.ResourceContainer
|
||||
import app.revanced.arsc.resource.boolean
|
||||
import com.reandroid.apk.xmlencoder.EncodeException
|
||||
|
@ -17,45 +16,39 @@ import com.reandroid.xml.source.XMLDocumentSource
|
|||
* @param document The [XMLDocument] to encode.
|
||||
* @param resources The [ResourceContainer] to use for encoding.
|
||||
*/
|
||||
internal class LazyXMLInputSource(
|
||||
internal class LazyXMLEncodeSource(
|
||||
name: String,
|
||||
val document: XMLDocument,
|
||||
private val resources: ResourceContainer
|
||||
) : XMLEncodeSource(resources.resourceTable.encodeMaterials, XMLDocumentSource(name, document)) {
|
||||
private var ready = false
|
||||
|
||||
private fun XMLElement.registerIds() {
|
||||
listAttributes().forEach { attr ->
|
||||
if (attr.value.startsWith("@+id/")) {
|
||||
val name = attr.value.split('/').last()
|
||||
resources.getOrCreateResource("id", name, boolean(false))
|
||||
attr.value = "@id/$name"
|
||||
}
|
||||
}
|
||||
|
||||
listChildElements().forEach { it.registerIds() }
|
||||
}
|
||||
private var encoded = false
|
||||
|
||||
override fun getResXmlBlock(): ResXmlDocument {
|
||||
if (!ready) {
|
||||
throw ApkResourceException.Encode("$name has not been encoded yet")
|
||||
if (encoded) return super.getResXmlBlock()
|
||||
|
||||
XMLEncodeSource(resources.resourceTable.encodeMaterials, XMLDocumentSource(name, document))
|
||||
|
||||
fun XMLElement.registerIds() {
|
||||
listAttributes().forEach { attr ->
|
||||
if (!attr.value.startsWith("@+id/")) return@forEach
|
||||
|
||||
val name = attr.value.split('/').last()
|
||||
resources.setResource("id", name, boolean(false))
|
||||
attr.value = "@id/$name"
|
||||
}
|
||||
|
||||
listChildElements().forEach { it.registerIds() }
|
||||
}
|
||||
|
||||
return super.getResXmlBlock()
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the [XMLDocument] associated with this input source.
|
||||
*/
|
||||
fun encode() {
|
||||
// Handle all @+id/id_name references in the document.
|
||||
document.documentElement.registerIds()
|
||||
|
||||
ready = true
|
||||
encoded = true
|
||||
|
||||
// This will call XMLEncodeSource.getResXmlBlock(), which will encode the document if it has not already been encoded.
|
||||
// This will call XMLEncodeSource.getResXmlBlock(),
|
||||
// which will encode the document if it has not already been encoded.
|
||||
try {
|
||||
resXmlBlock
|
||||
return super.getResXmlBlock()
|
||||
} catch (e: EncodeException) {
|
||||
throw EncodeException("Failed to encode $name", e)
|
||||
}
|
|
@ -3,7 +3,7 @@ package app.revanced.patcher
|
|||
import app.revanced.arsc.resource.ResourceContainer
|
||||
import app.revanced.patcher.apk.Apk
|
||||
import app.revanced.patcher.apk.ApkBundle
|
||||
import app.revanced.arsc.resource.ResourceFile
|
||||
import app.revanced.arsc.resource.ResourceFiles
|
||||
import app.revanced.patcher.util.method.MethodWalker
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.w3c.dom.Document
|
||||
|
@ -85,7 +85,7 @@ class DomFileEditor internal constructor(
|
|||
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
|
||||
.also(Document::normalize)
|
||||
|
||||
internal constructor(file: ResourceFile) : this(
|
||||
internal constructor(file: ResourceFiles) : this(
|
||||
file.inputStream(),
|
||||
{
|
||||
file.contents = it.toByteArray()
|
||||
|
|
|
@ -4,13 +4,13 @@ package app.revanced.patcher.apk
|
|||
|
||||
import app.revanced.arsc.ApkResourceException
|
||||
import app.revanced.arsc.archive.Archive
|
||||
import app.revanced.arsc.resource.ResourceContainer
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.PatcherOptions
|
||||
import app.revanced.patcher.logging.asArscLogger
|
||||
import app.revanced.patcher.util.ProxyBackedClassList
|
||||
import com.reandroid.apk.ApkModule
|
||||
import com.reandroid.apk.xmlencoder.EncodeException
|
||||
import com.reandroid.archive.InputSource
|
||||
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock
|
||||
import com.reandroid.arsc.value.ResConfig
|
||||
import lanchon.multidexlib2.*
|
||||
|
@ -37,8 +37,6 @@ sealed class Apk private constructor(module: ApkModule) {
|
|||
*/
|
||||
val packageMetadata = PackageMetadata(module.androidManifestBlock)
|
||||
|
||||
val resources = ResourceContainer(archive, module.tableBlock)
|
||||
|
||||
/**
|
||||
* Refresh updated resources and close any open files.
|
||||
*
|
||||
|
@ -51,7 +49,7 @@ sealed class Apk private constructor(module: ApkModule) {
|
|||
throw ApkResourceException.Encode(e.message!!, e)
|
||||
}
|
||||
|
||||
resources.refreshPackageName()
|
||||
archive.mainPackageResources.refreshPackageName()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,8 +112,14 @@ sealed class Apk private constructor(module: ApkModule) {
|
|||
init {
|
||||
MultiDexContainerBackedDexFile(object : MultiDexContainer<DexBackedDexFile> {
|
||||
// Load all dex files from the apk module and create a dex entry for each of them.
|
||||
private val entries = archive.readDexFiles()
|
||||
.mapValues { (name, data) -> BasicDexEntry(this, name, RawDexIO.readRawDexFile(data, 0, null)) }
|
||||
private val entries = archive.readDexFiles().associateBy { it.name }
|
||||
.mapValues { (name, inputSource) ->
|
||||
BasicDexEntry(
|
||||
this,
|
||||
name,
|
||||
RawDexIO.readRawDexFile(inputSource.openStream(), inputSource.length, null)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getDexEntryNames() = entries.keys.toList()
|
||||
override fun getEntry(entryName: String) = entries[entryName]
|
||||
|
@ -143,7 +147,11 @@ sealed class Apk private constructor(module: ApkModule) {
|
|||
it, Patcher.dexFileNamer, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
|
||||
)
|
||||
}.forEach { (name, store) ->
|
||||
archive.writeRaw(name, store.data)
|
||||
val dexFileInputSource = object : InputSource(name) {
|
||||
override fun openStream() = store.readAt(0)
|
||||
}
|
||||
|
||||
archive.write(dexFileInputSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue