Repackager.kt (3321B) - raw


      1 package me.rhunk.snapenhance.manager.patch
      2 
      3 import android.content.Context
      4 import com.android.tools.build.apkzlib.zip.AlignmentRules
      5 import com.android.tools.build.apkzlib.zip.ZFile
      6 import com.android.tools.build.apkzlib.zip.ZFileOptions
      7 import com.wind.meditor.core.ManifestEditor
      8 import com.wind.meditor.property.AttributeItem
      9 import com.wind.meditor.property.ModificationProperty
     10 import me.rhunk.snapenhance.manager.BuildConfig
     11 import me.rhunk.snapenhance.manager.patch.util.ApkSignatureHelper.provideSigningExtension
     12 import me.rhunk.snapenhance.manager.patch.util.obfuscateDexFile
     13 import java.io.ByteArrayInputStream
     14 import java.io.ByteArrayOutputStream
     15 import java.io.File
     16 
     17 class Repackager(
     18     private val context: Context,
     19     private val cacheFolder: File,
     20     private val packageName: String,
     21 ) {
     22     private fun patchManifest(data: ByteArray): ByteArray {
     23         val property = ModificationProperty()
     24 
     25         property.addManifestAttribute(AttributeItem("package", packageName).apply {
     26             type = 3
     27             namespace = null
     28         })
     29 
     30         return ByteArrayOutputStream().apply {
     31             ManifestEditor(ByteArrayInputStream(data), this, property).processManifest()
     32             flush()
     33             close()
     34         }.toByteArray()
     35     }
     36 
     37     fun patch(apkFile: File): File {
     38         val outputFile = File(cacheFolder, "patched-${apkFile.name}")
     39         runCatching {
     40             patch(apkFile, outputFile)
     41         }.onFailure {
     42             outputFile.delete()
     43             throw it
     44         }
     45         return outputFile
     46     }
     47 
     48     fun patch(apkFile: File, outputFile: File) {
     49         val dstZFile = ZFile.openReadWrite(outputFile, ZFileOptions().setAlignmentRule(
     50             AlignmentRules.compose(AlignmentRules.constantForSuffix(".so", 4096))
     51         ))
     52         provideSigningExtension(context.assets.open("lspatch/keystore.jks")).register(dstZFile)
     53         val srcZFile = ZFile.openReadOnly(apkFile)
     54         val dexFiles = mutableListOf<File>()
     55 
     56         for (entry in srcZFile.entries()) {
     57             val name = entry.centralDirectoryHeader.name
     58             if (name.startsWith("AndroidManifest.xml")) {
     59                 dstZFile.add(name, ByteArrayInputStream(
     60                     patchManifest(entry.read())
     61                 ), false)
     62                 continue
     63             }
     64             if (name.startsWith("classes") && name.endsWith(".dex")) {
     65                 println("obfuscating $name")
     66                 val inputStream = entry.open() ?: continue
     67                 val obfuscatedDexFile = inputStream.obfuscateDexFile(cacheFolder, { dexFile ->
     68                     dexFile.classes.firstOrNull { it.type == "Lme/rhunk/snapenhance/common/Constants;" } != null
     69                 }, mapOf(
     70                     BuildConfig.APPLICATION_ID to packageName
     71                 ))?.also { dexFiles.add(it) }
     72 
     73                 if (obfuscatedDexFile == null) {
     74                     inputStream.close()
     75                     dstZFile.add(name, entry.open(), false)
     76                     continue
     77                 }
     78 
     79                 dstZFile.add(name, obfuscatedDexFile.inputStream(), false)
     80                 continue
     81             }
     82             dstZFile.add(name, entry.open(), false)
     83         }
     84         dstZFile.realign()
     85         dstZFile.close()
     86         srcZFile.close()
     87         dexFiles.forEach { it.delete() }
     88     }
     89 }