feat: remove extra zipalign step (#106)

* feat: remove extra zipalign step

* remove zipfs

* remove use

* reduce compression

* put back misc.xml

* revert stupid autofix
This commit is contained in:
bogadana 2022-08-03 21:36:38 +02:00 committed by GitHub
parent 63a8aa315d
commit c8e793efab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 121 additions and 154 deletions

View File

@ -1,12 +0,0 @@
package app.revanced.cli.aligning
import app.revanced.cli.command.MainCommand.logger
import app.revanced.utils.signing.align.ZipAligner
import java.io.File
object Aligning {
fun align(inputFile: File, outputFile: File) {
logger.info("Aligning ${inputFile.name} to ${outputFile.name}")
ZipAligner.align(inputFile, outputFile)
}
}

View File

@ -1,6 +1,5 @@
package app.revanced.cli.command package app.revanced.cli.command
import app.revanced.cli.aligning.Aligning
import app.revanced.cli.logging.impl.DefaultCliLogger import app.revanced.cli.logging.impl.DefaultCliLogger
import app.revanced.cli.patcher.Patcher import app.revanced.cli.patcher.Patcher
import app.revanced.cli.patcher.logging.impl.PatcherLogger import app.revanced.cli.patcher.logging.impl.PatcherLogger
@ -161,15 +160,11 @@ internal object MainCommand : Runnable {
val cacheDirectory = File(pArgs.cacheDirectory) val cacheDirectory = File(pArgs.cacheDirectory)
// align the file
val alignedFile = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk")
Aligning.align(patchedFile, alignedFile)
// sign the file // sign the file
val finalFile = if (!pArgs.mount) { val finalFile = if (!pArgs.mount) {
val signedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_signed.apk") val signedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_signed.apk")
Signing.sign( Signing.sign(
alignedFile, patchedFile,
signedOutput, signedOutput,
SigningOptions( SigningOptions(
pArgs.cn, pArgs.cn,
@ -182,7 +177,7 @@ internal object MainCommand : Runnable {
signedOutput signedOutput
} else } else
alignedFile patchedFile
// finally copy to the specified output file // finally copy to the specified output file
logger.info("Copying ${finalFile.name} to ${outputFile.name}") logger.info("Copying ${finalFile.name} to ${outputFile.name}")

View File

@ -2,10 +2,12 @@ package app.revanced.cli.patcher
import app.revanced.cli.command.MainCommand.args import app.revanced.cli.command.MainCommand.args
import app.revanced.cli.command.MainCommand.logger import app.revanced.cli.command.MainCommand.logger
import app.revanced.utils.filesystem.ZipFileSystemUtils
import app.revanced.utils.patcher.addPatchesFiltered import app.revanced.utils.patcher.addPatchesFiltered
import app.revanced.utils.patcher.applyPatchesVerbose import app.revanced.utils.patcher.applyPatchesVerbose
import app.revanced.utils.patcher.mergeFiles import app.revanced.utils.patcher.mergeFiles
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 import java.io.File
import java.nio.file.Files import java.nio.file.Files
@ -23,26 +25,22 @@ internal object Patcher {
// write output file // write output file
if (output.exists()) Files.delete(output.toPath()) if (output.exists()) Files.delete(output.toPath())
inputFile.copyTo(output)
val result = patcher.save() val result = patcher.save()
ZipFileSystemUtils(output).use { outputFileSystem -> ZipFile(output).use { outputFile ->
// replace all dex files // replace all dex files
result.dexFiles.forEach { result.dexFiles.forEach {
logger.info("Writing dex file ${it.name}") logger.info("Writing dex file ${it.name}")
outputFileSystem.write(it.name, it.dexFileInputStream.readAllBytes()) outputFile.addEntryCompressData(ZipEntry.createWithName(it.name), it.dexFileInputStream.readAllBytes())
} }
if (!args.disableResourcePatching) { if (!args.disableResourcePatching) {
logger.info("Writing resources...") logger.info("Writing resources...")
ZipFileSystemUtils(result.resourceFile!!).use { resourceFileSystem -> outputFile.copyEntriesFromFileAligned(ZipFile(result.resourceFile!!), ZipAligner::getEntryAlignment)
val resourceFiles = resourceFileSystem.getFile(File.separator)
outputFileSystem.writePathRecursively(resourceFiles)
}
} }
result.doNotCompress?.let { outputFileSystem.uncompress(*it.toTypedArray()) } outputFile.copyEntriesFromFileAligned(ZipFile(inputFile), ZipAligner::getEntryAlignment)
} }
} }
} }

View File

@ -1,64 +0,0 @@
package app.revanced.utils.filesystem
import java.io.Closeable
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
import java.util.zip.ZipEntry
internal class ZipFileSystemUtils(
file: File
) : Closeable {
private var zipFileSystem = FileSystems.newFileSystem(file.toPath(), mapOf("noCompression" to true))
private fun Path.deleteRecursively() {
if (!Files.exists(this)) {
throw IllegalStateException("File exists in real folder but not in zip file system")
}
if (Files.isDirectory(this)) {
Files.list(this).forEach { path ->
path.deleteRecursively()
}
}
Files.delete(this)
}
internal fun getFile(path: String) = zipFileSystem.getPath(path)
internal fun writePathRecursively(path: Path) {
Files.list(path).use { fileStream ->
fileStream.forEach { filePath ->
val fileSystemPath = filePath.getRelativePath(path)
fileSystemPath.deleteRecursively()
}
}
Files.walk(path).use { fileStream ->
// don't include build directory
// by skipping the root node.
fileStream.skip(1).forEach { filePath ->
val relativePath = filePath.getRelativePath(path)
if (Files.isDirectory(filePath)) {
Files.createDirectory(relativePath)
return@forEach
}
Files.copy(filePath, relativePath)
}
}
}
internal fun write(path: String, content: ByteArray) = Files.write(zipFileSystem.getPath(path), content)
private fun Path.getRelativePath(path: Path): Path = zipFileSystem.getPath(path.relativize(this).toString())
// TODO: figure out why the file system is uncompressed by default and how to fix it
internal fun uncompress(vararg paths: String) =
paths.forEach { Files.setAttribute(zipFileSystem.getPath(it), "zip:method", ZipEntry.STORED) }
override fun close() = zipFileSystem.close()
}

View File

@ -1,28 +1,11 @@
package app.revanced.utils.signing.align package app.revanced.utils.signing.align
import app.revanced.utils.signing.align.zip.ZipFile import app.revanced.utils.signing.align.zip.structures.ZipEntry
import java.io.File
internal object ZipAligner { internal object ZipAligner {
private const val DEFAULT_ALIGNMENT = 4 private const val DEFAULT_ALIGNMENT = 4
private const val LIBRARY_ALIGNMENT = 4096 private const val LIBRARY_ALIGNMENT = 4096
fun align(input: File, output: File) { fun getEntryAlignment(entry: ZipEntry): Int? =
val inputZip = ZipFile(input) if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
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()
}
} }

View File

@ -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.ZipEndRecord
import app.revanced.utils.signing.align.zip.structures.ZipEntry import app.revanced.utils.signing.align.zip.structures.ZipEntry
import java.io.Closeable
import java.io.File import java.io.File
import java.io.RandomAccessFile import java.io.RandomAccessFile
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.channels.FileChannel import java.nio.channels.FileChannel
import java.util.zip.CRC32
import java.util.zip.Deflater
class ZipFile(val file: File) { class ZipFile(val file: File) : Closeable {
var entries: MutableList<ZipEntry> = mutableListOf() var entries: MutableList<ZipEntry> = mutableListOf()
private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw") private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
private var CDNeedsRewrite = false
private val compressionLevel = 5
init { init {
//if file isn't empty try to load entries //if file isn't empty try to load entries
@ -53,23 +59,24 @@ class ZipFile(val file: File) {
return buildList(numberOfEntries) { return buildList(numberOfEntries) {
for (i in 1..numberOfEntries) { for (i in 1..numberOfEntries) {
add(ZipEntry.fromCDE(filePointer).also add(
{ ZipEntry.fromCDE(filePointer).also
//for some reason the local extra field can be different from the central one {
it.readLocalExtra( //for some reason the local extra field can be different from the central one
filePointer.channel.map( it.readLocalExtra(
FileChannel.MapMode.READ_ONLY, filePointer.channel.map(
it.localHeaderOffset.toLong() + 28, FileChannel.MapMode.READ_ONLY,
2 it.localHeaderOffset.toLong() + 28,
2
)
) )
) })
})
} }
} }
} }
private fun writeCDE() { private fun writeCD() {
val CDEStart = filePointer.channel.position().toUInt() val CDStart = filePointer.channel.position().toUInt()
entries.forEach { entries.forEach {
filePointer.channel.write(it.toCDE()) filePointer.channel.write(it.toCDE())
@ -82,15 +89,17 @@ class ZipFile(val file: File) {
0u, 0u,
entriesCount, entriesCount,
entriesCount, entriesCount,
filePointer.channel.position().toUInt() - CDEStart, filePointer.channel.position().toUInt() - CDStart,
CDEStart, CDStart,
"" ""
) )
filePointer.channel.write(endRecord.toECD()) 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() entry.localHeaderOffset = filePointer.channel.position().toUInt()
filePointer.channel.write(entry.toLFH()) filePointer.channel.write(entry.toLFH())
@ -99,17 +108,45 @@ class ZipFile(val file: File) {
entries.add(entry) entries.add(entry)
} }
fun addEntryAligned(entry: ZipEntry, data: ByteBuffer, alignment: Int) { fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
//calculate where data would end up val compressor = Deflater(compressionLevel, true)
val dataOffset = filePointer.filePointer + entry.LFHSize compressor.setInput(data)
compressor.finish()
val mod = dataOffset % alignment val uncompressedSize = data.size
val compressedData =
ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger
//wrong alignment val compressedDataLength = compressor.deflate(compressedData)
if (mod != 0L) { val compressedBuffer =
//add padding at end of extra field ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())
entry.localExtraField =
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt()) 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)
}
fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) {
alignment?.let { alignment ->
//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) addEntry(entry, data)
@ -123,8 +160,17 @@ class ZipFile(val file: File) {
) )
} }
fun finish() { fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
writeCDE() 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() filePointer.close()
} }
} }

View File

@ -58,7 +58,8 @@ data class ZipEndRecord(
fun toECD(): ByteBuffer { fun toECD(): ByteBuffer {
val commentBytes = fileComment.toByteArray(Charsets.UTF_8) val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size).also { it.order(ByteOrder.LITTLE_ENDIAN) } val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size)
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
buffer.putUInt(ECD_SIGNATURE) buffer.putUInt(ECD_SIGNATURE)
buffer.putUShort(diskNumber) buffer.putUShort(diskNumber)

View File

@ -1,10 +1,6 @@
package app.revanced.utils.signing.align.zip.structures package app.revanced.utils.signing.align.zip.structures
import app.revanced.utils.signing.align.zip.getUShort import app.revanced.utils.signing.align.zip.*
import app.revanced.utils.signing.align.zip.putUInt
import app.revanced.utils.signing.align.zip.putUShort
import app.revanced.utils.signing.align.zip.readUIntLE
import app.revanced.utils.signing.align.zip.readUShortLE
import java.io.DataInput import java.io.DataInput
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
@ -13,12 +9,12 @@ data class ZipEntry(
val version: UShort, val version: UShort,
val versionNeeded: UShort, val versionNeeded: UShort,
val flags: UShort, val flags: UShort,
val compression: UShort, var compression: UShort,
val modificationTime: UShort, val modificationTime: UShort,
val modificationDate: UShort, val modificationDate: UShort,
val crc32: UInt, var crc32: UInt,
val compressedSize: UInt, var compressedSize: UInt,
val uncompressedSize: UInt, var uncompressedSize: UInt,
val diskNumber: UShort, val diskNumber: UShort,
val internalAttributes: UShort, val internalAttributes: UShort,
val externalAttributes: UInt, val externalAttributes: UInt,
@ -26,7 +22,7 @@ data class ZipEntry(
val fileName: String, val fileName: String,
val extraField: ByteArray, val extraField: ByteArray,
val fileComment: String, val fileComment: String,
var localExtraField: ByteArray = ByteArray(0), //seperate for alignment var localExtraField: ByteArray = ByteArray(0), //separate for alignment
) { ) {
val LFHSize: Int val LFHSize: Int
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
@ -41,6 +37,27 @@ data class ZipEntry(
const val LFH_HEADER_SIZE = 30 const val LFH_HEADER_SIZE = 30
const val LFH_SIGNATURE = 0x04034b50u 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 { fun fromCDE(input: DataInput): ZipEntry {
val signature = input.readUIntLE() val signature = input.readUIntLE()
@ -67,7 +84,8 @@ data class ZipEntry(
val externalAttributes = input.readUIntLE() val externalAttributes = input.readUIntLE()
val localHeaderOffset = 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) { if (variableFieldsLength > 0) {
val fileNameBytes = ByteArray(fileNameLength.toInt()) val fileNameBytes = ByteArray(fileNameLength.toInt())
@ -81,7 +99,8 @@ data class ZipEntry(
fileComment = fileCommentBytes.toString(Charsets.UTF_8) 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( return ZipEntry(
version, version,
@ -138,8 +157,9 @@ data class ZipEntry(
val nameBytes = fileName.toByteArray(Charsets.UTF_8) val nameBytes = fileName.toByteArray(Charsets.UTF_8)
val commentBytes = fileComment.toByteArray(Charsets.UTF_8) val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
val buffer = ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size) val buffer =
.also { it.order(ByteOrder.LITTLE_ENDIAN) } ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
buffer.putUInt(CDE_SIGNATURE) buffer.putUInt(CDE_SIGNATURE)
buffer.putUShort(version) buffer.putUShort(version)