diff --git a/src/main/kotlin/app/revanced/cli/aligning/Aligning.kt b/src/main/kotlin/app/revanced/cli/aligning/Aligning.kt index ef6d615d..d67c2ade 100644 --- a/src/main/kotlin/app/revanced/cli/aligning/Aligning.kt +++ b/src/main/kotlin/app/revanced/cli/aligning/Aligning.kt @@ -1,12 +1,37 @@ package app.revanced.cli.aligning import app.revanced.cli.command.MainCommand.logger +import app.revanced.patcher.PatcherResult import app.revanced.utils.signing.align.ZipAligner +import app.revanced.utils.signing.align.zip.ZipFile +import app.revanced.utils.signing.align.zip.structures.ZipEntry import java.io.File object Aligning { - fun align(inputFile: File, outputFile: File) { + fun align(result: PatcherResult, inputFile: File, outputFile: File) { logger.info("Aligning ${inputFile.name} to ${outputFile.name}") - ZipAligner.align(inputFile, outputFile) + + if (outputFile.exists()) outputFile.delete() + + ZipFile(outputFile).use { file -> + result.dexFiles.forEach { + file.addEntryCompressData( + ZipEntry.createWithName(it.name), + it.stream.readBytes() + ) + } + + result.resourceFile?.let { + file.copyEntriesFromFileAligned( + ZipFile(it), + ZipAligner::getEntryAlignment + ) + } + + file.copyEntriesFromFileAligned( + ZipFile(inputFile), + ZipAligner::getEntryAlignment + ) + } } } diff --git a/src/main/kotlin/app/revanced/cli/command/MainCommand.kt b/src/main/kotlin/app/revanced/cli/command/MainCommand.kt index 49f9fb4d..aff17a0f 100644 --- a/src/main/kotlin/app/revanced/cli/command/MainCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/MainCommand.kt @@ -151,16 +151,14 @@ internal object MainCommand : Runnable { Adb(outputFile, patcher.context.packageMetadata.packageName, args.deploy!!, !pArgs.mount) } - val patchedFile = File(pArgs.cacheDirectory).resolve("${outputFile.nameWithoutExtension}_raw.apk") - // start the patcher - Patcher.start(patcher, patchedFile, allPatches) + val result = Patcher.start(patcher, allPatches) val cacheDirectory = File(pArgs.cacheDirectory) // align the file val alignedFile = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk") - Aligning.align(patchedFile, alignedFile) + Aligning.align(result, args.inputFile, alignedFile) // sign the file val finalFile = if (!pArgs.mount) { diff --git a/src/main/kotlin/app/revanced/cli/patcher/Patcher.kt b/src/main/kotlin/app/revanced/cli/patcher/Patcher.kt index dfe7b3d5..d1e5333b 100644 --- a/src/main/kotlin/app/revanced/cli/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/cli/patcher/Patcher.kt @@ -1,24 +1,17 @@ package app.revanced.cli.patcher -import app.revanced.cli.command.MainCommand.args -import app.revanced.cli.command.MainCommand.logger +import app.revanced.patcher.PatcherResult import app.revanced.patcher.data.Context import app.revanced.patcher.patch.Patch -import app.revanced.utils.filesystem.ZipFileSystemUtils import app.revanced.utils.patcher.addPatchesFiltered import app.revanced.utils.patcher.applyPatchesVerbose import app.revanced.utils.patcher.mergeFiles -import java.io.File -import java.nio.file.Files internal object Patcher { internal fun start( patcher: app.revanced.patcher.Patcher, - output: File, allPatches: List>> - ) { - val inputFile = args.inputFile - + ): PatcherResult { // merge files like necessary integrations patcher.mergeFiles() // add patches, but filter incompatible or excluded patches @@ -26,28 +19,6 @@ internal object Patcher { // apply patches patcher.applyPatchesVerbose() - // write output file - if (output.exists()) Files.delete(output.toPath()) - inputFile.copyTo(output) - - val result = patcher.save() - ZipFileSystemUtils(output).use { outputFileSystem -> - // replace all dex files - result.dexFiles.forEach { - logger.info("Writing dex file ${it.name}") - outputFileSystem.write(it.name, it.stream.readAllBytes()) - } - - result.resourceFile?.let { - logger.info("Writing resources...") - - ZipFileSystemUtils(it).use { resourceFileSystem -> - val resourceFiles = resourceFileSystem.getFile(File.separator) - outputFileSystem.writePathRecursively(resourceFiles) - } - } - - result.doNotCompress?.let { outputFileSystem.uncompress(*it.toTypedArray()) } - } + return patcher.save() } } diff --git a/src/main/kotlin/app/revanced/utils/signing/align/ZipAligner.kt b/src/main/kotlin/app/revanced/utils/signing/align/ZipAligner.kt index 1a348f67..be40ba1b 100644 --- a/src/main/kotlin/app/revanced/utils/signing/align/ZipAligner.kt +++ b/src/main/kotlin/app/revanced/utils/signing/align/ZipAligner.kt @@ -1,28 +1,11 @@ package app.revanced.utils.signing.align -import app.revanced.utils.signing.align.zip.ZipFile -import java.io.File +import app.revanced.utils.signing.align.zip.structures.ZipEntry internal object ZipAligner { private const val DEFAULT_ALIGNMENT = 4 private const val LIBRARY_ALIGNMENT = 4096 - fun align(input: File, output: File) { - val inputZip = ZipFile(input) - val outputZip = ZipFile(output) - - for (entry in inputZip.entries) { - val data = inputZip.getDataForEntry(entry) - - if (entry.compression == 0.toUShort()) { - val alignment = if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT - - outputZip.addEntryAligned(entry, data, alignment) - } else { - outputZip.addEntry(entry, data) - } - } - - outputZip.finish() - } + fun getEntryAlignment(entry: ZipEntry): Int? = + if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT } diff --git a/src/main/kotlin/app/revanced/utils/signing/align/zip/ZipFile.kt b/src/main/kotlin/app/revanced/utils/signing/align/zip/ZipFile.kt index 41c63481..e64cd1a1 100644 --- a/src/main/kotlin/app/revanced/utils/signing/align/zip/ZipFile.kt +++ b/src/main/kotlin/app/revanced/utils/signing/align/zip/ZipFile.kt @@ -2,15 +2,21 @@ package app.revanced.utils.signing.align.zip import app.revanced.utils.signing.align.zip.structures.ZipEndRecord import app.revanced.utils.signing.align.zip.structures.ZipEntry +import java.io.Closeable import java.io.File import java.io.RandomAccessFile import java.nio.ByteBuffer import java.nio.channels.FileChannel +import java.util.zip.CRC32 +import java.util.zip.Deflater -class ZipFile(val file: File) { +class ZipFile(file: File) : Closeable { var entries: MutableList = mutableListOf() private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw") + private var CDNeedsRewrite = false + + private val compressionLevel = 5 init { //if file isn't empty try to load entries @@ -53,23 +59,24 @@ class ZipFile(val file: File) { return buildList(numberOfEntries) { for (i in 1..numberOfEntries) { - add(ZipEntry.fromCDE(filePointer).also - { - //for some reason the local extra field can be different from the central one - it.readLocalExtra( - filePointer.channel.map( - FileChannel.MapMode.READ_ONLY, - it.localHeaderOffset.toLong() + 28, - 2 + add( + ZipEntry.fromCDE(filePointer).also + { + //for some reason the local extra field can be different from the central one + it.readLocalExtra( + filePointer.channel.map( + FileChannel.MapMode.READ_ONLY, + it.localHeaderOffset.toLong() + 28, + 2 + ) ) - ) - }) + }) } } } - private fun writeCDE() { - val CDEStart = filePointer.channel.position().toUInt() + private fun writeCD() { + val CDStart = filePointer.channel.position().toUInt() entries.forEach { filePointer.channel.write(it.toCDE()) @@ -82,15 +89,17 @@ class ZipFile(val file: File) { 0u, entriesCount, entriesCount, - filePointer.channel.position().toUInt() - CDEStart, - CDEStart, + filePointer.channel.position().toUInt() - CDStart, + CDStart, "" ) filePointer.channel.write(endRecord.toECD()) } - fun addEntry(entry: ZipEntry, data: ByteBuffer) { + private fun addEntry(entry: ZipEntry, data: ByteBuffer) { + CDNeedsRewrite = true + entry.localHeaderOffset = filePointer.channel.position().toUInt() filePointer.channel.write(entry.toLFH()) @@ -99,17 +108,45 @@ class ZipFile(val file: File) { entries.add(entry) } - fun addEntryAligned(entry: ZipEntry, data: ByteBuffer, alignment: Int) { - //calculate where data would end up - val dataOffset = filePointer.filePointer + entry.LFHSize + fun addEntryCompressData(entry: ZipEntry, data: ByteArray) { + val compressor = Deflater(compressionLevel, true) + compressor.setInput(data) + compressor.finish() + + val uncompressedSize = data.size + val compressedData = + ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger + + val compressedDataLength = compressor.deflate(compressedData) + val compressedBuffer = + ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray()) - val mod = dataOffset % alignment + compressor.end() + + val crc = CRC32() + crc.update(data) + + entry.compression = 8u //deflate compression + entry.uncompressedSize = uncompressedSize.toUInt() + entry.compressedSize = compressedDataLength.toUInt() + entry.crc32 = crc.value.toUInt() + + addEntry(entry, compressedBuffer) + } - //wrong alignment - if (mod != 0L) { - //add padding at end of extra field - entry.localExtraField = - entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt()) + private fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) { + alignment?.let { + //calculate where data would end up + val dataOffset = filePointer.filePointer + entry.LFHSize + + val mod = dataOffset % alignment + + //wrong alignment + if (mod != 0L) { + //add padding at end of extra field + entry.localExtraField = + entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt()) + } } addEntry(entry, data) @@ -123,8 +160,17 @@ class ZipFile(val file: File) { ) } - fun finish() { - writeCDE() + fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) { + for (entry in file.entries) { + if (entries.any { it.fileName == entry.fileName }) continue //don't add duplicates + + val data = file.getDataForEntry(entry) + addEntryCopyData(entry, data, entryAlignment(entry)) + } + } + + override fun close() { + if (CDNeedsRewrite) writeCD() filePointer.close() } -} +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/utils/signing/align/zip/structures/ZipEntry.kt b/src/main/kotlin/app/revanced/utils/signing/align/zip/structures/ZipEntry.kt index 480377f6..d99a73d4 100644 --- a/src/main/kotlin/app/revanced/utils/signing/align/zip/structures/ZipEntry.kt +++ b/src/main/kotlin/app/revanced/utils/signing/align/zip/structures/ZipEntry.kt @@ -9,12 +9,12 @@ data class ZipEntry( val version: UShort, val versionNeeded: UShort, val flags: UShort, - val compression: UShort, + var compression: UShort, val modificationTime: UShort, val modificationDate: UShort, - val crc32: UInt, - val compressedSize: UInt, - val uncompressedSize: UInt, + var crc32: UInt, + var compressedSize: UInt, + var uncompressedSize: UInt, val diskNumber: UShort, val internalAttributes: UShort, val externalAttributes: UInt, @@ -22,7 +22,7 @@ data class ZipEntry( val fileName: String, val extraField: ByteArray, val fileComment: String, - var localExtraField: ByteArray = ByteArray(0), //seperate for alignment + var localExtraField: ByteArray = ByteArray(0), //separate for alignment ) { val LFHSize: Int get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size @@ -37,6 +37,27 @@ data class ZipEntry( const val LFH_HEADER_SIZE = 30 const val LFH_SIGNATURE = 0x04034b50u + fun createWithName(fileName: String): ZipEntry { + return ZipEntry( + 0x1403u, //made by unix, version 20 + 0u, + 0u, + 0u, + 0x0821u, //seems to be static time google uses, no idea + 0x0221u, //same as above + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + fileName, + ByteArray(0), + "" + ) + } + fun fromCDE(input: DataInput): ZipEntry { val signature = input.readUIntLE() @@ -55,7 +76,7 @@ data class ZipEntry( val fileNameLength = input.readUShortLE() var fileName = "" val extraFieldLength = input.readUShortLE() - var extraField = ByteArray(extraFieldLength.toInt()) + val extraField = ByteArray(extraFieldLength.toInt()) val fileCommentLength = input.readUShortLE() var fileComment = "" val diskNumber = input.readUShortLE() @@ -63,7 +84,8 @@ data class ZipEntry( val externalAttributes = input.readUIntLE() val localHeaderOffset = input.readUIntLE() - val variableFieldsLength = fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt() + val variableFieldsLength = + fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt() if (variableFieldsLength > 0) { val fileNameBytes = ByteArray(fileNameLength.toInt()) @@ -77,7 +99,8 @@ data class ZipEntry( fileComment = fileCommentBytes.toString(Charsets.UTF_8) } - flags = (flags and 0b1000u.inv().toUShort()) //disable data descriptor flag as they are not used + flags = (flags and 0b1000u.inv() + .toUShort()) //disable data descriptor flag as they are not used return ZipEntry( version, @@ -134,8 +157,9 @@ data class ZipEntry( val nameBytes = fileName.toByteArray(Charsets.UTF_8) val commentBytes = fileComment.toByteArray(Charsets.UTF_8) - val buffer = ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size) - .also { it.order(ByteOrder.LITTLE_ENDIAN) } + val buffer = + ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size) + .also { it.order(ByteOrder.LITTLE_ENDIAN) } buffer.putUInt(CDE_SIGNATURE) buffer.putUShort(version) @@ -162,5 +186,4 @@ data class ZipEntry( buffer.flip() return buffer } -} - +} \ No newline at end of file