diff --git a/arsclib-utils/src/main/kotlin/app/revanced/arsc/ApkResourceException.kt b/arsclib-utils/src/main/kotlin/app/revanced/arsc/ApkResourceException.kt index fc660c7..517bbbb 100644 --- a/arsclib-utils/src/main/kotlin/app/revanced/arsc/ApkResourceException.kt +++ b/arsclib-utils/src/main/kotlin/app/revanced/arsc/ApkResourceException.kt @@ -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.") } \ No newline at end of file diff --git a/arsclib-utils/src/main/kotlin/app/revanced/arsc/archive/Archive.kt b/arsclib-utils/src/main/kotlin/app/revanced/arsc/archive/Archive.kt index 17d10b7..120bbd3 100644 --- a/arsclib-utils/src/main/kotlin/app/revanced/arsc/archive/Archive.kt +++ b/arsclib-utils/src/main/kotlin/app/revanced/arsc/archive/Archive.kt @@ -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() - - /** - * 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() - .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 = 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() } \ No newline at end of file diff --git a/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/Resource.kt b/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/Resource.kt index e1a942a..d8cdf66 100644 --- a/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/Resource.kt +++ b/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/Resource.kt @@ -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 } diff --git a/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceContainer.kt b/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceContainer.kt index 7a34a4a..581b419 100644 --- a/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceContainer.kt +++ b/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceContainer.kt @@ -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() - 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 openResource(name: String): ResourceFileEditor { + 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 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, configuration: String? = null) { + fun setResources(type: String, resources: Map, 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 } } \ No newline at end of file diff --git a/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceFile.kt b/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceFiles.kt similarity index 72% rename from arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceFile.kt rename to arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceFiles.kt index db7962f..a9bf1e2 100644 --- a/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceFile.kt +++ b/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceFiles.kt @@ -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) - } \ No newline at end of file diff --git a/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceTable.kt b/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceTable.kt index b2c5574..4b9b7b8 100644 --- a/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceTable.kt +++ b/arsclib-utils/src/main/kotlin/app/revanced/arsc/resource/ResourceTable.kt @@ -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) { - 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) { } base.also { - encodeMaterials.currentPackage = it.packageBlock + encodeMaterials.currentPackage = it.tableBlock it.tableBlock!!.frameWorks.forEach { fw -> if (fw is FrameworkTable) { diff --git a/arsclib-utils/src/main/kotlin/app/revanced/arsc/xml/LazyXMLInputSource.kt b/arsclib-utils/src/main/kotlin/app/revanced/arsc/xml/LazyXMLEncodeSource.kt similarity index 61% rename from arsclib-utils/src/main/kotlin/app/revanced/arsc/xml/LazyXMLInputSource.kt rename to arsclib-utils/src/main/kotlin/app/revanced/arsc/xml/LazyXMLEncodeSource.kt index 2019a64..0348db2 100644 --- a/arsclib-utils/src/main/kotlin/app/revanced/arsc/xml/LazyXMLInputSource.kt +++ b/arsclib-utils/src/main/kotlin/app/revanced/arsc/xml/LazyXMLEncodeSource.kt @@ -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) } diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/Context.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/Context.kt index 909bee9..3ad5a5b 100644 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/Context.kt +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/Context.kt @@ -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() diff --git a/revanced-patcher/src/main/kotlin/app/revanced/patcher/apk/Apk.kt b/revanced-patcher/src/main/kotlin/app/revanced/patcher/apk/Apk.kt index f35fdab..95e747b 100644 --- a/revanced-patcher/src/main/kotlin/app/revanced/patcher/apk/Apk.kt +++ b/revanced-patcher/src/main/kotlin/app/revanced/patcher/apk/Apk.kt @@ -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 { // 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) } } }