package org.lsposed.npatch import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.lsposed.npatch.config.Configs import org.lsposed.npatch.config.MyKeyStore import org.lsposed.npatch.share.Constants import org.lsposed.npatch.share.PatchConfig import org.lsposed.patch.NPatch import org.lsposed.patch.util.Logger import java.io.File import java.io.IOException object Patcher { class Options( val newPackageName: String, private val injectDex: Boolean, private val config: PatchConfig, private val apkPaths: List, private val embeddedModules: List? ) { fun toStringArray(): Array { return buildList { add("-o"); add(lspApp.tmpApkDir.absolutePath) add("-p"); add(config.newPackage) if (config.debuggable) add("-d") add("-l"); add(config.sigBypassLevel.toString()) if (config.useManager) add("--manager") if (config.overrideVersionCode) add("-r") if (Configs.detailPatchLogs) add("-v") embeddedModules?.forEach { add("-m"); add(it) } if (config.injectProvider) add("--provider") if(injectDex) add("--injectdex") if (config.useMicroG) add("--useMicroG") if (!MyKeyStore.useDefault) { addAll(arrayOf("-k", MyKeyStore.file.path, Configs.keyStorePassword, Configs.keyStoreAlias, Configs.keyStoreAliasPassword)) } addAll(apkPaths) }.toTypedArray() } } suspend fun patch(logger: Logger, options: Options) { withContext(Dispatchers.IO) { NPatch(logger, *options.toStringArray()).doCommandLine() val uri = Configs.storageDirectory?.toUri() ?: throw IOException("Uri is null") val root = DocumentFile.fromTreeUri(lspApp, uri) ?: throw IOException("DocumentFile is null") root.listFiles().forEach { if (it.name?.endsWith(Constants.PATCH_FILE_SUFFIX) == true) it.delete() } lspApp.targetApkFiles?.clear() val apkFileList = arrayListOf() lspApp.tmpApkDir.walk() .filter { it.isFile && it.name.endsWith(Constants.PATCH_FILE_SUFFIX) } .forEach { tempApkFile -> val cachedApkFile = File(lspApp.externalCacheDir, tempApkFile.name) if (tempApkFile.renameTo(cachedApkFile).not()) { tempApkFile.copyTo(cachedApkFile, overwrite = true) tempApkFile.delete() } apkFileList.add(cachedApkFile) val finalFile = root.createFile("application/vnd.android.package-archive", cachedApkFile.name) ?: throw IOException("無法建立輸出檔案: ${cachedApkFile.name}") lspApp.contentResolver.openOutputStream(finalFile.uri)?.use { output -> cachedApkFile.inputStream().use { input -> input.copyTo(output) } } ?: throw IOException("Unable to open an output stream: ${finalFile.uri}") } lspApp.targetApkFiles = apkFileList logger.i("Patched files are saved to ${root.uri.lastPathSegment}") } } }