From 5e9392818b6699bd810b046ae218302909c64033 Mon Sep 17 00:00:00 2001 From: Shakir Choudhary Date: Tue, 14 Jul 2026 13:38:07 +0530 Subject: [PATCH 1/2] Hide the manager's package name from install detection Some apps flag a device by enumerating installed packages for org.lsposed.lspatch. The manager can now reinstall itself under a custom or random package id: it rewrites and re-signs its own apk, migrates its settings and keystore, retargets every manager-mode app at the new package (recorded per-patch in the config and read by the loader), then removes the old one -- and can revert to the original from GitHub. The entry is the Package row in the System status card. Manager mode only; integrated patches bind no manager and are unaffected. Defeats known-package-name checks only -- not the signing certificate, exported components, or Play Integrity. Co-authored-by: JingMatrix --- manager/build.gradle.kts | 2 + manager/src/main/AndroidManifest.xml | 2 +- .../org/lsposed/lspatch/LSPApplication.kt | 3 + .../main/java/org/lsposed/lspatch/Patcher.kt | 3 + .../org/lsposed/lspatch/ui/page/HomeScreen.kt | 135 ++++++++++++ .../lspatch/util/GithubReleaseDownloader.kt | 126 +++++++++++ .../lsposed/lspatch/util/LocalAppsUpdater.kt | 142 ++++++++++++ .../org/lsposed/lspatch/util/ManagerCloak.kt | 202 ++++++++++++++++++ .../lsposed/lspatch/util/ManagerCloakFlow.kt | 190 ++++++++++++++++ .../lsposed/lspatch/util/ManagerMigrate.kt | 114 ++++++++++ .../lspatch/util/PackageNameValidator.kt | 19 ++ manager/src/main/res/values/strings.xml | 21 ++ .../LSPAppComponentFactoryStub.java | 13 +- .../lspatch/loader/LSPApplication.java | 6 +- .../service/RemoteApplicationService.java | 8 +- .../java/org/lsposed/patch/ApkPatcher.java | 3 +- .../main/java/org/lsposed/patch/LSPatch.java | 4 + .../java/org/lsposed/patch/PatchSpec.java | 18 ++ .../org/lsposed/lspatch/share/Constants.java | 2 + .../lsposed/lspatch/share/PatchConfig.java | 27 ++- 20 files changed, 1029 insertions(+), 11 deletions(-) create mode 100644 manager/src/main/java/org/lsposed/lspatch/util/GithubReleaseDownloader.kt create mode 100644 manager/src/main/java/org/lsposed/lspatch/util/LocalAppsUpdater.kt create mode 100644 manager/src/main/java/org/lsposed/lspatch/util/ManagerCloak.kt create mode 100644 manager/src/main/java/org/lsposed/lspatch/util/ManagerCloakFlow.kt create mode 100644 manager/src/main/java/org/lsposed/lspatch/util/ManagerMigrate.kt create mode 100644 manager/src/main/java/org/lsposed/lspatch/util/PackageNameValidator.kt diff --git a/manager/build.gradle.kts b/manager/build.gradle.kts index b29b1e168..5349af3d3 100644 --- a/manager/build.gradle.kts +++ b/manager/build.gradle.kts @@ -83,6 +83,8 @@ afterEvaluate { dependencies { implementation(projects.patch) + implementation(projects.apkzlib) + implementation("vector:axml") implementation("vector:daemon-service") implementation("vector:manager-ui") implementation(projects.share.android) diff --git a/manager/src/main/AndroidManifest.xml b/manager/src/main/AndroidManifest.xml index c71158597..3d72d9cbc 100644 --- a/manager/src/main/AndroidManifest.xml +++ b/manager/src/main/AndroidManifest.xml @@ -61,7 +61,7 @@ Unit) { + val scope = rememberCoroutineScope() + val isCloaked = lspApp.packageName != Constants.MANAGER_PACKAGE_NAME + val granted = ShizukuApi.isPermissionGranted + + var running by remember { mutableStateOf(false) } + var status by remember { mutableStateOf(null) } + var newName by remember { mutableStateOf(PackageNameValidator.randomPackageName()) } + var invalid by remember { mutableStateOf(false) } + + AlertDialog( + onDismissRequest = { if (!running) onDismiss() }, + title = { + Text(stringResource( + if (isCloaked) R.string.settings_revert_dialog_title + else R.string.settings_cloak_dialog_title + )) + }, + text = { + Column { + Text(stringResource( + if (isCloaked) R.string.settings_revert_warning + else R.string.settings_cloak_warning + )) + if (!isCloaked) { + Spacer(Modifier.height(12.dp)) + OutlinedTextField( + value = newName, + onValueChange = { newName = it; invalid = false }, + singleLine = true, + isError = invalid, + enabled = !running, + label = { Text(stringResource(R.string.settings_cloak_package)) }, + trailingIcon = { + TextButton( + enabled = !running, + onClick = { newName = PackageNameValidator.randomPackageName(); invalid = false }, + ) { Text(stringResource(R.string.settings_cloak_randomize)) } + }, + ) + if (invalid) Text( + stringResource(R.string.settings_cloak_invalid), + color = MaterialTheme.colorScheme.error, + ) + } + if (!granted) Text( + stringResource(R.string.settings_cloak_need_shizuku), + color = MaterialTheme.colorScheme.error, + ) + status?.let { + Spacer(Modifier.height(12.dp)) + Text(it) + } + } + }, + confirmButton = { + TextButton( + enabled = !running && granted, + onClick = { + if (!isCloaked && !PackageNameValidator.isValid(newName)) { + invalid = true + return@TextButton + } + running = true + status = null + val onProgress: (ManagerCloakFlow.Progress) -> Unit = { p -> + when (p) { + is ManagerCloakFlow.Progress.Message -> status = p.text + is ManagerCloakFlow.Progress.Error -> { + status = p.message + running = false + } + // On success the flow launches the new package and this process is about + // to be uninstalled; close the dialog so nothing lingers behind it. + is ManagerCloakFlow.Progress.Success -> { + running = false + onDismiss() + } + } + } + scope.launch { + if (isCloaked) ManagerCloakFlow.revertToOriginal(onProgress) + else ManagerCloakFlow.run(newName, onProgress) + } + }, + ) { + Text(stringResource( + if (isCloaked) R.string.settings_revert_confirm + else R.string.settings_cloak_confirm + )) + } + }, + dismissButton = { + TextButton(enabled = !running, onClick = onDismiss) { + Text(stringResource(android.R.string.cancel)) + } + }, + ) } private data class SystemProperty( diff --git a/manager/src/main/java/org/lsposed/lspatch/util/GithubReleaseDownloader.kt b/manager/src/main/java/org/lsposed/lspatch/util/GithubReleaseDownloader.kt new file mode 100644 index 000000000..0565776d2 --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/util/GithubReleaseDownloader.kt @@ -0,0 +1,126 @@ +package org.lsposed.lspatch.util + +import android.util.Log +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.net.HttpURLConnection +import java.net.URL + +object GithubReleaseDownloader { + + private const val TAG = "GithubReleaseDownloader" + private const val RELEASES_API = + "https://api.github.com/repos/JingMatrix/LSPatch/releases/latest" + private const val USER_AGENT = "LSPatch-Manager" + + data class Result(val tagName: String, val assetName: String, val file: File) + + /** + * Downloads the latest manager APK from GitHub Releases. + * Prefers `manager.apk`, falls back to `manager-debug.apk`. + */ + fun downloadLatestManager(dest: File): Result { + dest.parentFile?.mkdirs() + if (dest.exists()) dest.delete() + + val releaseJson = httpGetString(RELEASES_API) + val root = JSONObject(releaseJson) + val tag = root.optString("tag_name", "unknown") + val assets = root.getJSONArray("assets") + + var preferredUrl: String? = null + var preferredName: String? = null + var fallbackUrl: String? = null + var fallbackName: String? = null + + for (i in 0 until assets.length()) { + val asset = assets.getJSONObject(i) + val name = asset.getString("name") + val url = asset.getString("browser_download_url") + when { + name.equals("manager.apk", ignoreCase = true) -> { + preferredUrl = url + preferredName = name + } + name.equals("manager-debug.apk", ignoreCase = true) -> { + fallbackUrl = url + fallbackName = name + } + } + } + + val downloadUrl = preferredUrl ?: fallbackUrl + ?: throw IllegalStateException("No manager APK found in latest GitHub release") + val assetName = preferredName ?: fallbackName!! + + Log.i(TAG, "Downloading $assetName ($tag) from $downloadUrl") + httpDownload(downloadUrl, dest) + if (!dest.isFile || dest.length() == 0L) { + throw IllegalStateException("Downloaded APK is empty") + } + return Result(tag, assetName, dest) + } + + private fun httpGetString(url: String): String { + val conn = (URL(url).openConnection() as HttpURLConnection).apply { + requestMethod = "GET" + setRequestProperty("Accept", "application/vnd.github+json") + setRequestProperty("User-Agent", USER_AGENT) + connectTimeout = 30_000 + readTimeout = 60_000 + instanceFollowRedirects = true + } + try { + val code = conn.responseCode + val stream = if (code in 200..299) conn.inputStream else conn.errorStream + val body = stream?.bufferedReader()?.use { it.readText() }.orEmpty() + if (code !in 200..299) { + throw IllegalStateException("GitHub API HTTP $code: $body") + } + return body + } finally { + conn.disconnect() + } + } + + private fun httpDownload(url: String, dest: File) { + var current = url + // Follow a few redirects manually for CDN links if needed + repeat(5) { + val conn = (URL(current).openConnection() as HttpURLConnection).apply { + requestMethod = "GET" + setRequestProperty("User-Agent", USER_AGENT) + setRequestProperty("Accept", "application/octet-stream") + connectTimeout = 30_000 + readTimeout = 300_000 + instanceFollowRedirects = false + } + try { + val code = conn.responseCode + when (code) { + in 200..299 -> { + conn.inputStream.use { input -> + FileOutputStream(dest).use { output -> input.copyTo(output) } + } + return + } + HttpURLConnection.HTTP_MOVED_PERM, + HttpURLConnection.HTTP_MOVED_TEMP, + HttpURLConnection.HTTP_SEE_OTHER, + 307, 308 -> { + current = conn.getHeaderField("Location") + ?: throw IllegalStateException("Redirect without Location") + } + else -> { + val err = conn.errorStream?.bufferedReader()?.use { it.readText() } + throw IllegalStateException("Download HTTP $code: $err") + } + } + } finally { + conn.disconnect() + } + } + throw IllegalStateException("Too many redirects while downloading release APK") + } +} diff --git a/manager/src/main/java/org/lsposed/lspatch/util/LocalAppsUpdater.kt b/manager/src/main/java/org/lsposed/lspatch/util/LocalAppsUpdater.kt new file mode 100644 index 000000000..6c6e2db10 --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/util/LocalAppsUpdater.kt @@ -0,0 +1,142 @@ +package org.lsposed.lspatch.util + +import android.content.pm.PackageInstaller +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.lsposed.lspatch.config.Configs +import org.lsposed.lspatch.config.MyKeyStore +import org.lsposed.lspatch.data.repository.PatchInputs +import org.lsposed.lspatch.data.repository.PatchOutputStore +import org.lsposed.lspatch.share.PatchConfig +import org.lsposed.lspatch.util.LSPPackageManager.AppInfo +import org.lsposed.patch.ApkPatcher +import org.lsposed.patch.KeystoreSpec +import org.lsposed.patch.ManifestOverrides +import org.lsposed.patch.PatchSpec +import org.lsposed.patch.util.Logger + +/** + * Retargets every installed manager-mode app at a (possibly renamed) manager package. + * + * A manager-mode app binds its manager by package name, so reinstalling the manager under a new id + * would orphan every app patched against the old one. Each patched app carries the original it was + * built from, so it is recovered and patched again with nothing changed but the recorded manager + * package -- the same recovery a re-patch uses ([PatchInputs]), and the same engine ([ApkPatcher]). + * + * Best-effort per app: one app that cannot be recovered or reinstalled is reported and skipped, not + * allowed to abort the rest -- a half-retargeted set is the situation this exists to get out of, not + * one to leave behind on the first error. + */ +object LocalAppsUpdater { + + private const val TAG = "LocalAppsUpdater" + + /** Which packages were retargeted and which failed, with a reason, so a partial run is legible. */ + data class Result(val updated: List, val failed: List>) + + private val logger = object : Logger() { + override fun d(msg: String) { + if (verbose) Log.d(TAG, msg) + } + + override fun i(msg: String) { + Log.i(TAG, msg) + } + + override fun e(msg: String) { + Log.e(TAG, msg) + } + } + + /** Installed manager-mode apps -- the only ones that bind a manager package at runtime. */ + fun managerModeApps(): List = + LSPPackageManager.appList.filter { PatchInputs.configOf(it)?.useManager == true } + + /** + * Re-patches every manager-mode app so its config binds [managerPackageName]. + * + * @return the packages updated and the ones that failed, so the caller can surface a partial + * outcome rather than a bare success or a single aborting throw. + */ + suspend fun updateAllForManager(managerPackageName: String): Result = withContext(Dispatchers.IO) { + val apps = managerModeApps() + Log.i(TAG, "Retargeting ${apps.size} manager-mode app(s) at $managerPackageName") + val updated = mutableListOf() + val failed = mutableListOf>() + for (app in apps) { + val pkg = app.app.packageName + runCatching { updateOne(app, managerPackageName) } + .onSuccess { updated.add(pkg) } + .onFailure { e -> + Log.e(TAG, "Failed to retarget $pkg", e) + failed.add(pkg to (e.message ?: e.javaClass.simpleName)) + } + } + Result(updated, failed) + } + + private suspend fun updateOne(app: AppInfo, managerPackageName: String) { + val pkg = app.app.packageName + Log.i(TAG, "Retarget loader for $pkg") + val config = PatchInputs.configOf(app) + ?: throw IllegalStateException("$pkg carries no patch config") + try { + val recovered = PatchInputs.fromInstalledPatchedApp(app) + if (recovered.originApks.isEmpty()) { + throw IllegalStateException("no original apk recovered for $pkg") + } + val spec = specFor(pkg, recovered.originApks, config, managerPackageName) + val produced = ApkPatcher(logger, spec).patch() + if (produced.isEmpty()) throw java.io.IOException("patcher produced no apk for $pkg") + val (status, message) = LSPPackageManager.installFiles(produced, ShizukuApi.isPermissionGranted) + if (status != PackageInstaller.STATUS_SUCCESS) { + throw java.io.IOException("install failed: $message") + } + } finally { + PatchInputs.discard(pkg) + } + } + + /** + * Rebuilds the spec the app was patched with, changed only in the manager it binds. + * + * Restores what the runtime config records -- version code, added permissions, dex injection, + * documents provider -- but not the patch-time-only manifest overrides (label, target sdk), + * which a re-patch cannot recover for any installed app; this matches the manager's own + * loader-update path. + */ + private suspend fun specFor( + packageName: String, + originApks: List, + config: PatchConfig, + managerPackageName: String, + ): PatchSpec = + PatchSpec.builder() + .apks(originApks) + .outputDir(PatchOutputStore.prepare(packageName)) + .useManager(true) + .debuggable(config.debuggable) + .sigBypassLevel(config.sigBypassLevel) + .injectDex(config.injectDex) + .forceOverwrite(true) + .verbose(Configs.detailPatchLogs) + .managerPackageName(managerPackageName) + .manifestOverrides( + ManifestOverrides.builder() + .versionCode(config.versionCode) + .permissions(config.addedPermissions?.toList() ?: emptyList()) + .injectDocumentsProvider(config.injectDocumentsProvider) + .build() + ) + .keystore( + if (MyKeyStore.useDefault) KeystoreSpec.builtIn() + else KeystoreSpec.of( + MyKeyStore.file, + Configs.keyStorePassword, + Configs.keyStoreAlias, + Configs.keyStoreAliasPassword, + ) + ) + .build() +} diff --git a/manager/src/main/java/org/lsposed/lspatch/util/ManagerCloak.kt b/manager/src/main/java/org/lsposed/lspatch/util/ManagerCloak.kt new file mode 100644 index 000000000..10ff61c88 --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/util/ManagerCloak.kt @@ -0,0 +1,202 @@ +package org.lsposed.lspatch.util + +import android.util.Log +import com.android.tools.build.apkzlib.sign.SigningExtension +import com.android.tools.build.apkzlib.sign.SigningOptions +import com.android.tools.build.apkzlib.zip.AlignmentRules +import com.android.tools.build.apkzlib.zip.ZFile +import com.android.tools.build.apkzlib.zip.ZFileOptions +import com.wind.meditor.core.ManifestEditor +import com.wind.meditor.property.AttributeItem +import com.wind.meditor.property.ModificationProperty +import com.wind.meditor.utils.NodeValue +import org.lsposed.lspatch.config.Configs +import org.lsposed.lspatch.config.MyKeyStore +import org.lsposed.lspatch.lspApp +import org.lsposed.lspatch.share.Constants +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.InputStream +import java.security.KeyStore +import java.security.cert.X509Certificate +import java.util.zip.ZipFile + +object ManagerCloak { + + private const val TAG = "ManagerCloak" + private const val MANIFEST = "AndroidManifest.xml" + private const val RESOURCES_ARSC = "resources.arsc" + + // Android R+ requires resources.arsc STORED and 4-byte aligned. + private val zFileOptions = ZFileOptions().setAlignmentRule( + AlignmentRules.compose( + AlignmentRules.constantForSuffix(".so", 4096), + AlignmentRules.constantForSuffix(RESOURCES_ARSC, 4) + ) + ) + + /** + * Rewrites the current manager APK to [newPackageName], embeds optional migrate zip, + * and signs the result. Returns the signed APK file. + */ + fun cloak(newPackageName: String, migrateZip: File?, output: File): File { + val splits = lspApp.applicationInfo.splitSourceDirs + if (!splits.isNullOrEmpty()) { + throw IllegalStateException("Split APK managers cannot be cloaked; install a single-APK build") + } + + val src = File(lspApp.applicationInfo.sourceDir) + if (!src.isFile) throw IllegalStateException("Manager source APK not found") + + return rebuild( + src = src, + output = output, + migrateZip = migrateZip, + rewritePackageTo = newPackageName + ) + } + + /** + * Takes a stock manager APK (e.g. from GitHub), embeds migrate data, and re-signs it. + * Package id is forced to [Constants.MANAGER_PACKAGE_NAME]. + */ + fun prepareStockWithMigrate(src: File, migrateZip: File?, output: File): File { + if (!src.isFile) throw IllegalStateException("Source APK not found") + return rebuild( + src = src, + output = output, + migrateZip = migrateZip, + rewritePackageTo = Constants.MANAGER_PACKAGE_NAME + ) + } + + private fun rebuild( + src: File, + output: File, + migrateZip: File?, + rewritePackageTo: String + ): File { + output.parentFile?.mkdirs() + if (output.exists()) output.delete() + + Log.i(TAG, "Rebuilding manager ${lspApp.packageName} -> $rewritePackageTo") + + ZFile.openReadWrite(output, zFileOptions).use { dst -> + registerSigner(dst) + + ZipFile(src).use { zip -> + for (entry in zip.entries()) { + if (entry.isDirectory) continue + val name = entry.name + if (isSignatureFile(name)) continue + // Drop any previous migrate payload; we add a fresh one below. + if (name == Constants.MIGRATE_ASSET_PATH) continue + + zip.getInputStream(entry).use { input -> + when { + name == MANIFEST -> { + val modified = modifyManifest(input, rewritePackageTo) + ByteArrayInputStream(modified).use { + dst.add(name, it, true) + } + } + name == RESOURCES_ARSC || name.endsWith(".so") -> { + dst.add(name, input, false) + } + else -> { + dst.add(name, input, entry.method != java.util.zip.ZipEntry.STORED) + } + } + } + } + } + + if (migrateZip != null && migrateZip.isFile) { + FileInputStream(migrateZip).use { + dst.add(Constants.MIGRATE_ASSET_PATH, it, true) + } + } + dst.realign() + } + + return output + } + + private fun isSignatureFile(name: String): Boolean { + if (!name.startsWith("META-INF/")) return false + return name.endsWith(".SF") || + name.endsWith(".MF") || + name.endsWith(".RSA") || + name.endsWith(".DSA") || + name.endsWith(".EC") || + name.endsWith(".IMG") + } + + private fun modifyManifest(input: InputStream, newPackageName: String): ByteArray { + val oldPackages = listOf( + lspApp.packageName, + Constants.MANAGER_PACKAGE_NAME + ).distinct() + + fun remapPrefixed(value: String): String { + for (old in oldPackages) { + if (value == old || value.startsWith("$old.")) { + return newPackageName + value.removePrefix(old) + } + } + return value + } + + val property = ModificationProperty() + property.addManifestAttribute( + AttributeItem(NodeValue.Manifest.PACKAGE, newPackageName).setNamespace(null) + ) + // Rewrite app-scoped custom permissions (e.g. DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION) + // so install does not clash with the still-installed original package. + property.setPermissionMapper { _, permission -> remapPrefixed(permission) } + property.setAuthorityMapper { authority -> remapPrefixed(authority) } + + val os = ByteArrayOutputStream() + ManifestEditor(input, os, property).processManifest() + return os.toByteArray() + } + + private fun registerSigner(dst: ZFile) { + val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()) + if (MyKeyStore.useDefault) { + val stream = lspApp.classLoader.getResourceAsStream("assets/keystore") + ?: throw IllegalStateException("Default keystore missing") + stream.use { keyStore.load(it, "123456".toCharArray()) } + val entry = keyStore.getEntry("key0", KeyStore.PasswordProtection("123456".toCharArray())) + as KeyStore.PrivateKeyEntry + @Suppress("UNCHECKED_CAST") + val certs = entry.certificateChain as Array + SigningExtension( + SigningOptions.builder() + .setMinSdkVersion(28) + .setV2SigningEnabled(true) + .setCertificates(*certs) + .setKey(entry.privateKey) + .build() + ).register(dst) + } else { + FileInputStream(MyKeyStore.file).use { keyStore.load(it, Configs.keyStorePassword.toCharArray()) } + val entry = keyStore.getEntry( + Configs.keyStoreAlias, + KeyStore.PasswordProtection(Configs.keyStoreAliasPassword.toCharArray()) + ) as KeyStore.PrivateKeyEntry + @Suppress("UNCHECKED_CAST") + val certs = entry.certificateChain as Array + SigningExtension( + SigningOptions.builder() + .setMinSdkVersion(28) + .setV2SigningEnabled(true) + .setCertificates(*certs) + .setKey(entry.privateKey) + .build() + ).register(dst) + } + } +} diff --git a/manager/src/main/java/org/lsposed/lspatch/util/ManagerCloakFlow.kt b/manager/src/main/java/org/lsposed/lspatch/util/ManagerCloakFlow.kt new file mode 100644 index 000000000..5a0631af6 --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/util/ManagerCloakFlow.kt @@ -0,0 +1,190 @@ +package org.lsposed.lspatch.util + +import android.content.Intent +import android.content.pm.PackageInstaller +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.lsposed.lspatch.lspApp +import org.lsposed.lspatch.share.Constants + +object ManagerCloakFlow { + + private const val TAG = "ManagerCloakFlow" + + sealed class Progress { + data class Message(val text: String) : Progress() + data class Success(val newPackageName: String) : Progress() + data class Error(val message: String) : Progress() + } + + /** + * Full cloak flow: export data → rebuild APK → install → update Local apps → launch new → uninstall old. + * [onProgress] is always invoked on the Main thread. + */ + suspend fun run( + newPackageName: String, + onProgress: (Progress) -> Unit + ) { + suspend fun emit(progress: Progress) = withContext(Dispatchers.Main.immediate) { + onProgress(progress) + } + + val oldPackage = lspApp.packageName + try { + if (!PackageNameValidator.isValid(newPackageName)) { + emit(Progress.Error("Invalid package name")) + return + } + if (newPackageName == oldPackage) { + emit(Progress.Error("Package name is unchanged")) + return + } + if (!ShizukuApi.isPermissionGranted) { + emit(Progress.Error("Shizuku is required")) + return + } + + withContext(Dispatchers.IO) { + emit(Progress.Message("Preparing migrate data…")) + val workDir = lspApp.cacheDir.resolve("cloak").also { + it.deleteRecursively() + it.mkdirs() + } + val migrateZip = ManagerMigrate.createMigrateZip(lspApp, workDir.resolve("migrate.zip")) + + emit(Progress.Message("Rebuilding manager APK…")) + val cloakedApk = ManagerCloak.cloak( + newPackageName, + migrateZip, + workDir.resolve("manager-$newPackageName.apk") + ) + + emit(Progress.Message("Installing cloaked manager…")) + val (installStatus, installMessage) = + LSPPackageManager.installFiles(listOf(cloakedApk), ShizukuApi.isPermissionGranted) + if (installStatus != PackageInstaller.STATUS_SUCCESS) { + emit(Progress.Error("Install failed: $installMessage")) + return@withContext + } + + // Retarget before removing the old package, so an app is never left pointing at a + // manager that no longer exists. Failures are reported, not fatal: the new manager + // is already installed, and a missed app can be retargeted again from it. + emit(Progress.Message("Updating manager-mode apps…")) + val retarget = LocalAppsUpdater.updateAllForManager(newPackageName) + if (retarget.failed.isNotEmpty()) { + emit(Progress.Message( + "Retargeted ${retarget.updated.size}, ${retarget.failed.size} failed: " + + retarget.failed.joinToString { it.first } + )) + } + + emit(Progress.Message("Launching new manager…")) + val launch = LSPPackageManager.getLaunchIntentForPackage(newPackageName) + if (launch != null) { + launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + withContext(Dispatchers.Main) { + lspApp.startActivity(launch) + } + } + + emit(Progress.Message("Removing old package…")) + val (uninstallStatus, uninstallMessage) = LSPPackageManager.uninstall(oldPackage) + if (uninstallStatus != PackageInstaller.STATUS_SUCCESS) { + Log.w(TAG, "Uninstall old package failed: $uninstallMessage") + } + + workDir.deleteRecursively() + emit(Progress.Success(newPackageName)) + } + } catch (t: Throwable) { + Log.e(TAG, "Cloak failed", t) + emit(Progress.Error(t.message ?: t.toString())) + } + } + + /** + * Revert a cloaked manager back to [Constants.MANAGER_PACKAGE_NAME] using the latest + * GitHub release APK, then uninstall this (random-package) app. + */ + suspend fun revertToOriginal(onProgress: (Progress) -> Unit) { + suspend fun emit(progress: Progress) = withContext(Dispatchers.Main.immediate) { + onProgress(progress) + } + + val currentPackage = lspApp.packageName + val original = Constants.MANAGER_PACKAGE_NAME + try { + if (currentPackage == original) { + emit(Progress.Error("Already using the original package name")) + return + } + if (!ShizukuApi.isPermissionGranted) { + emit(Progress.Error("Shizuku is required")) + return + } + + withContext(Dispatchers.IO) { + val workDir = lspApp.cacheDir.resolve("cloak-revert").also { + it.deleteRecursively() + it.mkdirs() + } + + emit(Progress.Message("Downloading latest manager from GitHub…")) + val downloaded = GithubReleaseDownloader.downloadLatestManager( + workDir.resolve("manager-github.apk") + ) + emit(Progress.Message("Downloaded ${downloaded.assetName} (${downloaded.tagName})")) + + emit(Progress.Message("Preparing migrate data…")) + val migrateZip = ManagerMigrate.createMigrateZip(lspApp, workDir.resolve("migrate.zip")) + + emit(Progress.Message("Preparing original package APK…")) + val stockApk = ManagerCloak.prepareStockWithMigrate( + downloaded.file, + migrateZip, + workDir.resolve("manager-original.apk") + ) + + emit(Progress.Message("Installing original manager…")) + val (installStatus, installMessage) = + LSPPackageManager.installFiles(listOf(stockApk), ShizukuApi.isPermissionGranted) + if (installStatus != PackageInstaller.STATUS_SUCCESS) { + emit(Progress.Error("Install failed: $installMessage")) + return@withContext + } + + emit(Progress.Message("Updating manager-mode apps…")) + val retarget = LocalAppsUpdater.updateAllForManager(original) + if (retarget.failed.isNotEmpty()) { + emit(Progress.Message( + "Retargeted ${retarget.updated.size}, ${retarget.failed.size} failed: " + + retarget.failed.joinToString { it.first } + )) + } + + emit(Progress.Message("Launching original manager…")) + val launch = LSPPackageManager.getLaunchIntentForPackage(original) + if (launch != null) { + launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + withContext(Dispatchers.Main) { + lspApp.startActivity(launch) + } + } + + emit(Progress.Message("Removing cloaked package…")) + val (uninstallStatus, uninstallMessage) = LSPPackageManager.uninstall(currentPackage) + if (uninstallStatus != PackageInstaller.STATUS_SUCCESS) { + Log.w(TAG, "Uninstall cloaked package failed: $uninstallMessage") + } + + workDir.deleteRecursively() + emit(Progress.Success(original)) + } + } catch (t: Throwable) { + Log.e(TAG, "Revert failed", t) + emit(Progress.Error(t.message ?: t.toString())) + } + } +} diff --git a/manager/src/main/java/org/lsposed/lspatch/util/ManagerMigrate.kt b/manager/src/main/java/org/lsposed/lspatch/util/ManagerMigrate.kt new file mode 100644 index 000000000..a70761a42 --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/util/ManagerMigrate.kt @@ -0,0 +1,114 @@ +package org.lsposed.lspatch.util + +import android.content.Context +import android.util.Log +import org.lsposed.lspatch.config.MyKeyStore +import org.lsposed.lspatch.share.Constants +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream + +/** + * Embeds / restores manager settings across a package-name change. + * Migrate payload is written into the cloaked APK as [Constants.MIGRATE_ASSET_PATH] + * so the new package can import without Shizuku on first launch. + */ +object ManagerMigrate { + + private const val TAG = "ManagerMigrate" + private const val MARKER = ".cloak_migrate_done" + private const val ENTRY_PREFS = "settings.xml" + private const val ENTRY_KEYSTORE = "keystore.bks" + private const val ENTRY_DB = "modules_config.db" + private const val ENTRY_DB_WAL = "modules_config.db-wal" + private const val ENTRY_DB_SHM = "modules_config.db-shm" + + fun createMigrateZip(context: Context, dest: File): File { + dest.parentFile?.mkdirs() + if (dest.exists()) dest.delete() + + runCatching { + val dbFile = context.getDatabasePath("modules_config.db") + if (dbFile.exists()) { + android.database.sqlite.SQLiteDatabase.openDatabase( + dbFile.path, + null, + android.database.sqlite.SQLiteDatabase.OPEN_READONLY + ).use { it.rawQuery("PRAGMA wal_checkpoint(FULL)", null).close() } + } + } + + ZipOutputStream(BufferedOutputStream(FileOutputStream(dest))).use { zos -> + fun putFile(entryName: String, file: File) { + if (!file.exists()) return + zos.putNextEntry(ZipEntry(entryName)) + FileInputStream(file).use { it.copyTo(zos) } + zos.closeEntry() + } + + putFile(ENTRY_PREFS, File(context.applicationInfo.dataDir, "shared_prefs/settings.xml")) + putFile(ENTRY_KEYSTORE, MyKeyStore.file) + putFile(ENTRY_DB, context.getDatabasePath("modules_config.db")) + putFile(ENTRY_DB_WAL, context.getDatabasePath("modules_config.db-wal")) + putFile(ENTRY_DB_SHM, context.getDatabasePath("modules_config.db-shm")) + } + return dest + } + + fun importIfNeeded(context: Context) { + val marker = File(context.filesDir, MARKER) + if (marker.exists()) return + + val apk = File(context.applicationInfo.sourceDir) + if (!apk.exists()) return + + try { + ZipFile(apk).use { zip -> + val entry = zip.getEntry(Constants.MIGRATE_ASSET_PATH) ?: run { + // Stock / non-cloaked install: mark done so we don't keep checking. + context.filesDir.mkdirs() + marker.createNewFile() + return + } + Log.i(TAG, "Importing migrate payload from manager APK") + ZipInputStream(zip.getInputStream(entry)).use { zis -> + var zipEntry = zis.nextEntry + while (zipEntry != null) { + when (zipEntry.name) { + ENTRY_PREFS -> { + val out = File(context.applicationInfo.dataDir, "shared_prefs/settings.xml") + out.parentFile?.mkdirs() + FileOutputStream(out).use { zis.copyTo(it) } + } + ENTRY_KEYSTORE -> { + context.filesDir.mkdirs() + FileOutputStream(File(context.filesDir, "keystore.bks")).use { zis.copyTo(it) } + } + ENTRY_DB -> writeDb(context, "modules_config.db", zis) + ENTRY_DB_WAL -> writeDb(context, "modules_config.db-wal", zis) + ENTRY_DB_SHM -> writeDb(context, "modules_config.db-shm", zis) + } + zis.closeEntry() + zipEntry = zis.nextEntry + } + } + } + context.filesDir.mkdirs() + marker.createNewFile() + Log.i(TAG, "Migrate import completed") + } catch (t: Throwable) { + Log.e(TAG, "Failed to import migrate payload", t) + } + } + + private fun writeDb(context: Context, name: String, zis: ZipInputStream) { + val out = context.getDatabasePath(name) + out.parentFile?.mkdirs() + FileOutputStream(out).use { zis.copyTo(it) } + } +} diff --git a/manager/src/main/java/org/lsposed/lspatch/util/PackageNameValidator.kt b/manager/src/main/java/org/lsposed/lspatch/util/PackageNameValidator.kt new file mode 100644 index 000000000..a6e4e6455 --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/util/PackageNameValidator.kt @@ -0,0 +1,19 @@ +package org.lsposed.lspatch.util + +object PackageNameValidator { + + private val SEGMENT = Regex("^[a-zA-Z][a-zA-Z0-9_]*$") + + fun isValid(packageName: String): Boolean { + if (packageName.isBlank() || packageName.length > 255) return false + val parts = packageName.split('.') + if (parts.size < 2) return false + return parts.all { SEGMENT.matches(it) } + } + + fun randomPackageName(): String { + val alphabet = ('a'..'z') + fun word(len: Int) = (1..len).map { alphabet.random() }.joinToString("") + return "org.${word(8)}.${word(6)}" + } +} diff --git a/manager/src/main/res/values/strings.xml b/manager/src/main/res/values/strings.xml index 2bedb5b84..e63e51462 100644 --- a/manager/src/main/res/values/strings.xml +++ b/manager/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ Copied to clipboard Device System + Package Manage @@ -220,6 +221,26 @@ Wrong alias name Wrong alias password Detail patch logs + Hide package name + Current: %1$s\nChange the installed package id so detectors looking for org.lsposed.lspatch fail. Requires Shizuku. Local patched apps will be updated. + Change package name + New package name + Randomize + This installs a new copy of LSPatch Manager under a different package name, updates Local-mode apps, then uninstalls the current one. Integrated patches are unaffected. Shizuku must stay available. Grant Shizuku to the new package after install. + Enter a valid package name (e.g. org.example.app) + Package name is the same as the current one + Shizuku is required to change the package name + Configure storage directory first (used when re-patching Local apps) + Working… + Installed as %1$s. Open the new manager and grant Shizuku if prompted. + Failed: %1$s + Continue + Revert to original + Current: %1$s\nDownload the latest official manager from GitHub (org.lsposed.lspatch) and remove this cloaked install. + Revert to original package + Downloads the latest manager.apk from JingMatrix/LSPatch releases, installs it as org.lsposed.lspatch (settings and Local app links are restored), then uninstalls this app. Requires Shizuku and network. + Revert + Restored org.lsposed.lspatch. Grant Shizuku to it if prompted. Inject loader dex Injects the loader dex directly into the app. Turn this on for apps with isolated processes, such as a browser\'s rendering engine, so their hooks still load. diff --git a/meta-loader/src/main/java/org/lsposed/lspatch/metaloader/LSPAppComponentFactoryStub.java b/meta-loader/src/main/java/org/lsposed/lspatch/metaloader/LSPAppComponentFactoryStub.java index 869437295..323fb0d84 100644 --- a/meta-loader/src/main/java/org/lsposed/lspatch/metaloader/LSPAppComponentFactoryStub.java +++ b/meta-loader/src/main/java/org/lsposed/lspatch/metaloader/LSPAppComponentFactoryStub.java @@ -60,6 +60,7 @@ private static void bootstrap() { String libName = archToLib.get(arch); boolean useManager = false; + String managerPackageName = Constants.MANAGER_PACKAGE_NAME; String soPath; try (var is = cl.getResourceAsStream(Constants.CONFIG_ASSET_PATH); @@ -69,7 +70,11 @@ private static void bootstrap() { var name = reader.nextName(); if (name.equals("useManager")) { useManager = reader.nextBoolean(); - break; + } else if (name.equals("managerPackageName")) { + var value = reader.nextString(); + if (value != null && !value.isEmpty()) { + managerPackageName = value; + } } else { reader.skipValue(); } @@ -77,13 +82,13 @@ private static void bootstrap() { } if (useManager) { - Log.i(TAG, "Bootstrap loader from manager"); + Log.i(TAG, "Bootstrap loader from manager: " + managerPackageName); var ipm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); ApplicationInfo manager; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - manager = (ApplicationInfo) HiddenApiBypass.invoke(IPackageManager.class, ipm, "getApplicationInfo", Constants.MANAGER_PACKAGE_NAME, 0L, Process.myUid() / 100000); + manager = (ApplicationInfo) HiddenApiBypass.invoke(IPackageManager.class, ipm, "getApplicationInfo", managerPackageName, 0L, Process.myUid() / 100000); } else { - manager = ipm.getApplicationInfo(Constants.MANAGER_PACKAGE_NAME, 0, Process.myUid() / 100000); + manager = ipm.getApplicationInfo(managerPackageName, 0, Process.myUid() / 100000); } try (var zip = new ZipFile(new File(manager.sourceDir)); var is = zip.getInputStream(zip.getEntry(Constants.LOADER_DEX_ASSET_PATH)); diff --git a/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java b/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java index 39a02b5ff..06d0b13d6 100644 --- a/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java +++ b/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java @@ -19,6 +19,7 @@ import org.lsposed.lspatch.service.EmbeddedRemoteServices; import org.lsposed.lspatch.service.LocalApplicationService; import org.lsposed.lspatch.service.RemoteApplicationService; +import org.lsposed.lspatch.share.Constants; import org.lsposed.lspatch.share.LSPConfig; import org.lsposed.lspatch.share.remote.FrameworkInfo; import org.lsposed.lspatch.share.remote.LSPatchXposedService; @@ -92,7 +93,10 @@ public static void onLoad() throws RemoteException, IOException { Log.d(TAG, "Initialize service client"); IFrameworkService service; if (config.optBoolean("useManager")) { - service = new RemoteApplicationService(context); + var managerPackage = config.optString("managerPackageName", Constants.MANAGER_PACKAGE_NAME); + if (managerPackage.isEmpty()) managerPackage = Constants.MANAGER_PACKAGE_NAME; + Log.i(TAG, "Manager package: " + managerPackage); + service = new RemoteApplicationService(context, managerPackage); } else { service = new LocalApplicationService(context); } diff --git a/patch-loader/src/main/java/org/lsposed/lspatch/service/RemoteApplicationService.java b/patch-loader/src/main/java/org/lsposed/lspatch/service/RemoteApplicationService.java index 31891bc3f..1292e84c0 100644 --- a/patch-loader/src/main/java/org/lsposed/lspatch/service/RemoteApplicationService.java +++ b/patch-loader/src/main/java/org/lsposed/lspatch/service/RemoteApplicationService.java @@ -37,15 +37,17 @@ public class RemoteApplicationService implements IFrameworkService { private static final String TAG = "LSPatch"; - private static final String MODULE_SERVICE = Constants.MANAGER_PACKAGE_NAME + ".manager.ModuleService"; private volatile IFrameworkService service; @SuppressLint("DiscouragedPrivateApi") - public RemoteApplicationService(Context context) throws RemoteException { + public RemoteApplicationService(Context context, String managerPackageName) throws RemoteException { + var packageName = (managerPackageName == null || managerPackageName.isEmpty()) + ? Constants.MANAGER_PACKAGE_NAME + : managerPackageName; try { var intent = new Intent() - .setComponent(new ComponentName(Constants.MANAGER_PACKAGE_NAME, MODULE_SERVICE)) + .setComponent(new ComponentName(packageName, Constants.MANAGER_SERVICE_NAME)) .putExtra("packageName", context.getPackageName()); // TODO: Authentication var latch = new CountDownLatch(1); diff --git a/patch/src/main/java/org/lsposed/patch/ApkPatcher.java b/patch/src/main/java/org/lsposed/patch/ApkPatcher.java index 6511049b1..2a92ad7e3 100644 --- a/patch/src/main/java/org/lsposed/patch/ApkPatcher.java +++ b/patch/src/main/java/org/lsposed/patch/ApkPatcher.java @@ -186,7 +186,8 @@ private void patchOne(File srcApkFile, File outputFile, int index, int total) th manifest.appComponentFactory, spec.injectDex(), spec.manifestOverrides().addedPermissions.toArray(new String[0]), - spec.manifestOverrides().injectDocumentsProvider); + spec.manifestOverrides().injectDocumentsProvider, + spec.useManager() ? spec.managerPackageName() : null); byte[] configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8); rewriteManifest(srcZFile, dstZFile, configBytes, manifest.packageName, manifest.minSdkVersion, index, total); diff --git a/patch/src/main/java/org/lsposed/patch/LSPatch.java b/patch/src/main/java/org/lsposed/patch/LSPatch.java index 486e499a5..1486304b2 100644 --- a/patch/src/main/java/org/lsposed/patch/LSPatch.java +++ b/patch/src/main/java/org/lsposed/patch/LSPatch.java @@ -53,6 +53,9 @@ public class LSPatch { @Parameter(names = {"--manager"}, description = "Use manager (Cannot work with embedding modules)") private boolean useManager = false; + @Parameter(names = {"--manager-package"}, description = "The manager package a manager-mode app binds to at runtime (defaults to the built-in manager). Set this to match a manager reinstalled under a custom package name.") + private String managerPackageName = null; + @Parameter(names = {"--version-code"}, description = "Set the patched app's versionCode to this value (e.g. 1, so a later build can be installed over it)") private Integer versionCodeOverride = null; @@ -148,6 +151,7 @@ public PatchSpec toSpec() throws PatchException { .modules(modules.stream().map(File::new).collect(Collectors.toList())) .keystore(keystore) .manifestOverrides(overrides) + .managerPackageName(managerPackageName) .build(); } } diff --git a/patch/src/main/java/org/lsposed/patch/PatchSpec.java b/patch/src/main/java/org/lsposed/patch/PatchSpec.java index fe75a2c0f..7f294cb61 100644 --- a/patch/src/main/java/org/lsposed/patch/PatchSpec.java +++ b/patch/src/main/java/org/lsposed/patch/PatchSpec.java @@ -32,6 +32,7 @@ public final class PatchSpec { private final List modules; private final KeystoreSpec keystore; private final ManifestOverrides manifestOverrides; + private final String managerPackageName; private PatchSpec(Builder b) { this.apks = Collections.unmodifiableList(new ArrayList<>(b.apks)); @@ -45,6 +46,7 @@ private PatchSpec(Builder b) { this.modules = Collections.unmodifiableList(new ArrayList<>(b.modules)); this.keystore = b.keystore == null ? KeystoreSpec.builtIn() : b.keystore; this.manifestOverrides = b.manifestOverrides == null ? ManifestOverrides.none() : b.manifestOverrides; + this.managerPackageName = b.managerPackageName; } public List apks() { @@ -99,6 +101,15 @@ public ManifestOverrides manifestOverrides() { return manifestOverrides; } + /** + * The manager package a manager-mode app should bind to at runtime, or null to accept the + * built-in default. Lets the manager record its own -- possibly cloaked -- package name so an + * app patched now keeps reaching the manager after it is reinstalled under a different id. + */ + public String managerPackageName() { + return managerPackageName; + } + public static Builder builder() { return new Builder(); } @@ -116,6 +127,7 @@ public static final class Builder { private boolean verbose; private KeystoreSpec keystore; private ManifestOverrides manifestOverrides; + private String managerPackageName; public Builder apk(File apk) { this.apks.add(apk); @@ -182,6 +194,12 @@ public Builder manifestOverrides(ManifestOverrides manifestOverrides) { return this; } + /** The manager package a manager-mode app binds to; null accepts the built-in default. */ + public Builder managerPackageName(String managerPackageName) { + this.managerPackageName = managerPackageName; + return this; + } + /** * @throws PatchException if the request contradicts itself -- refused here, where the caller * can still be told why, rather than half-executed later. diff --git a/share/java/src/main/java/org/lsposed/lspatch/share/Constants.java b/share/java/src/main/java/org/lsposed/lspatch/share/Constants.java index 46520af1a..8e087a099 100644 --- a/share/java/src/main/java/org/lsposed/lspatch/share/Constants.java +++ b/share/java/src/main/java/org/lsposed/lspatch/share/Constants.java @@ -18,6 +18,8 @@ public class Constants { final static public String DOCUMENTS_PROVIDER_CLASS = "org.lsposed.lspatch.loader.LSPatchDocumentsProvider"; final static public String DOCUMENTS_PROVIDER_AUTHORITY_SUFFIX = ".lspatch.documents"; final static public String MANAGER_PACKAGE_NAME = "org.lsposed.lspatch"; + final static public String MANAGER_SERVICE_NAME = "org.lsposed.lspatch.manager.ModuleService"; + final static public String MIGRATE_ASSET_PATH = "assets/lspatch/migrate.zip"; final static public int MIN_ROLLING_VERSION_CODE = 348; final static public int SIGBYPASS_LV_DISABLE = 0; diff --git a/share/java/src/main/java/org/lsposed/lspatch/share/PatchConfig.java b/share/java/src/main/java/org/lsposed/lspatch/share/PatchConfig.java index 6ed1ead07..2f2f34726 100644 --- a/share/java/src/main/java/org/lsposed/lspatch/share/PatchConfig.java +++ b/share/java/src/main/java/org/lsposed/lspatch/share/PatchConfig.java @@ -32,6 +32,15 @@ public class PatchConfig { * never carried it, so without this the option would silently turn itself off on a loader update. */ public final boolean injectDocumentsProvider; + + /** + * The manager package a manager-mode app should bind to for module loading, or null for an + * apk patched before this was recorded (and for integrated apps, which never bind a manager). + * Recorded so the manager can be reinstalled under a different package name without orphaning + * already-patched apps: a loader update rewrites this field, and older apks fall back to the + * built-in {@link Constants#MANAGER_PACKAGE_NAME} via {@link #resolveManagerPackageName()}. + */ + public final String managerPackageName; public final LSPConfig lspConfig; public PatchConfig( @@ -43,7 +52,8 @@ public PatchConfig( String appComponentFactory, boolean injectDex, String[] addedPermissions, - boolean injectDocumentsProvider + boolean injectDocumentsProvider, + String managerPackageName ) { this.useManager = useManager; this.debuggable = debuggable; @@ -54,6 +64,21 @@ public PatchConfig( this.injectDex = injectDex; this.addedPermissions = addedPermissions; this.injectDocumentsProvider = injectDocumentsProvider; + if (useManager) { + this.managerPackageName = (managerPackageName != null && !managerPackageName.isEmpty()) + ? managerPackageName + : Constants.MANAGER_PACKAGE_NAME; + } else { + this.managerPackageName = null; + } this.lspConfig = LSPConfig.instance; } + + /** Resolved manager package for a manager-mode app; falls back for configs created before this field existed. */ + public String resolveManagerPackageName() { + if (managerPackageName != null && !managerPackageName.isEmpty()) { + return managerPackageName; + } + return Constants.MANAGER_PACKAGE_NAME; + } } From 2190d819c11f1e478b48c77ddba68b8ca020f8a4 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Mon, 17 Aug 2026 13:51:56 +0200 Subject: [PATCH 2/2] Translate the package-name strings into every locale Adds the manager package-name / cloak strings to all 18 locales, and drops the strings left unused when the entry moved to the System status card (the old Settings-page copy and the obsolete storage-directory notice). --- manager/src/main/res/values-ar/strings.xml | 11 +++++++++++ manager/src/main/res/values-de/strings.xml | 11 +++++++++++ manager/src/main/res/values-es/strings.xml | 11 +++++++++++ manager/src/main/res/values-fa/strings.xml | 11 +++++++++++ manager/src/main/res/values-fr/strings.xml | 11 +++++++++++ manager/src/main/res/values-in/strings.xml | 11 +++++++++++ manager/src/main/res/values-it/strings.xml | 11 +++++++++++ manager/src/main/res/values-iw/strings.xml | 11 +++++++++++ manager/src/main/res/values-ja/strings.xml | 11 +++++++++++ manager/src/main/res/values-ko/strings.xml | 11 +++++++++++ manager/src/main/res/values-pl/strings.xml | 11 +++++++++++ manager/src/main/res/values-pt-rBR/strings.xml | 11 +++++++++++ manager/src/main/res/values-ru/strings.xml | 11 +++++++++++ manager/src/main/res/values-tr/strings.xml | 11 +++++++++++ manager/src/main/res/values-uk/strings.xml | 11 +++++++++++ manager/src/main/res/values-vi/strings.xml | 11 +++++++++++ manager/src/main/res/values-zh-rCN/strings.xml | 11 +++++++++++ manager/src/main/res/values-zh-rTW/strings.xml | 11 +++++++++++ manager/src/main/res/values/strings.xml | 10 ---------- 19 files changed, 198 insertions(+), 10 deletions(-) diff --git a/manager/src/main/res/values-ar/strings.xml b/manager/src/main/res/values-ar/strings.xml index b08214982..e5a138a5e 100644 --- a/manager/src/main/res/values-ar/strings.xml +++ b/manager/src/main/res/values-ar/strings.xml @@ -247,4 +247,15 @@ التوقيع بمخزن مفاتيحك الخاص (BKS). إعادة تثبيت هذا الإصدار تعذّر التحقق من التحديثات + الحزمة + تغيير اسم الحزمة + اسم الحزمة الجديد + عشوائي + أدخل اسم حزمة صالحًا (مثال: org.example.app) + ‏Shizuku مطلوب لتغيير اسم الحزمة + متابعة + يؤدي هذا إلى تثبيت نسخة جديدة من LSPatch Manager باسم حزمة مختلف، وتحديث تطبيقات الوضع المحلي، ثم إلغاء تثبيت النسخة الحالية. لا تتأثر الترقيعات المدمجة. يجب أن يظل Shizuku متاحًا. امنح Shizuku للحزمة الجديدة بعد التثبيت. + الرجوع إلى الحزمة الأصلية + رجوع + يقوم بتنزيل أحدث manager.apk من إصدارات JingMatrix/LSPatch، وتثبيته باسم org.lsposed.lspatch (تتم استعادة الإعدادات وروابط التطبيقات المحلية)، ثم إلغاء تثبيت هذا التطبيق. يتطلب Shizuku واتصالًا بالشبكة. diff --git a/manager/src/main/res/values-de/strings.xml b/manager/src/main/res/values-de/strings.xml index 86c73d6bd..e6fe4c413 100644 --- a/manager/src/main/res/values-de/strings.xml +++ b/manager/src/main/res/values-de/strings.xml @@ -247,4 +247,15 @@ Mit deinem eigenen Keystore (BKS) signieren. Diesen Build neu installieren Suche nach Updates fehlgeschlagen + Paket + Paketnamen ändern + Neuer Paketname + Zufällig + Gültigen Paketnamen eingeben (z. B. org.example.app) + Zum Ändern des Paketnamens wird Shizuku benötigt + Fortfahren + Dadurch wird eine neue Kopie von LSPatch Manager unter einem anderen Paketnamen installiert, Apps im lokalen Modus werden aktualisiert und die aktuelle Kopie wird deinstalliert. Integrierte Patches sind nicht betroffen. Shizuku muss verfügbar bleiben. Erteile Shizuku nach der Installation die Berechtigung für das neue Paket. + Auf ursprüngliches Paket zurücksetzen + Zurücksetzen + Lädt die neueste manager.apk aus den JingMatrix/LSPatch-Releases herunter, installiert sie als org.lsposed.lspatch (Einstellungen und Verknüpfungen der lokalen Apps werden wiederhergestellt) und deinstalliert anschließend diese App. Erfordert Shizuku und eine Netzwerkverbindung. diff --git a/manager/src/main/res/values-es/strings.xml b/manager/src/main/res/values-es/strings.xml index 38edba758..9a34d1881 100644 --- a/manager/src/main/res/values-es/strings.xml +++ b/manager/src/main/res/values-es/strings.xml @@ -247,4 +247,15 @@ Firma con tu propio almacén de claves (BKS). Reinstalar esta compilación No se pudieron buscar actualizaciones + Paquete + Cambiar el nombre del paquete + Nuevo nombre de paquete + Aleatorio + Introduce un nombre de paquete válido (p. ej. org.example.app) + Se necesita Shizuku para cambiar el nombre del paquete + Continuar + Esto instala una nueva copia de LSPatch Manager con un nombre de paquete diferente, actualiza las apps en modo local y luego desinstala la copia actual. Los parches integrados no se ven afectados. Shizuku debe seguir disponible. Concede Shizuku al nuevo paquete tras la instalación. + Volver al paquete original + Revertir + Descarga el manager.apk más reciente de las publicaciones de JingMatrix/LSPatch, lo instala como org.lsposed.lspatch (se restauran los ajustes y los enlaces de las apps locales) y luego desinstala esta app. Requiere Shizuku y conexión de red. diff --git a/manager/src/main/res/values-fa/strings.xml b/manager/src/main/res/values-fa/strings.xml index b054303c6..f44fd66ab 100644 --- a/manager/src/main/res/values-fa/strings.xml +++ b/manager/src/main/res/values-fa/strings.xml @@ -247,4 +247,15 @@ امضا با keystore خودتان (BKS). نصب دوبارهٔ این ساخت بررسی به‌روزرسانی‌ها ممکن نشد + بسته + تغییر نام بسته + نام بستهٔ جدید + تصادفی + یک نام بستهٔ معتبر وارد کنید (مثلاً org.example.app) + برای تغییر نام بسته به Shizuku نیاز است + ادامه + این کار نسخهٔ جدیدی از LSPatch Manager را با نام بستهٔ متفاوت نصب می‌کند، برنامه‌های حالت محلی را به‌روزرسانی می‌کند و سپس نسخهٔ فعلی را حذف می‌کند. وصله‌های یکپارچه تأثیری نمی‌پذیرند. Shizuku باید در دسترس بماند. پس از نصب، Shizuku را به بستهٔ جدید بدهید. + بازگردانی به بستهٔ اصلی + بازگردانی + آخرین manager.apk را از انتشارهای JingMatrix/LSPatch دانلود می‌کند، آن را با نام org.lsposed.lspatch نصب می‌کند (تنظیمات و پیوندهای برنامه‌های محلی بازیابی می‌شوند) و سپس این برنامه را حذف می‌کند. به Shizuku و شبکه نیاز دارد. diff --git a/manager/src/main/res/values-fr/strings.xml b/manager/src/main/res/values-fr/strings.xml index 7935b7da2..565fd781e 100644 --- a/manager/src/main/res/values-fr/strings.xml +++ b/manager/src/main/res/values-fr/strings.xml @@ -247,4 +247,15 @@ Signer avec votre propre keystore (BKS). Réinstaller cette version Impossible de rechercher les mises à jour + Paquet + Changer le nom du paquet + Nouveau nom de paquet + Aléatoire + Saisissez un nom de paquet valide (par ex. org.example.app) + Shizuku est requis pour changer le nom du paquet + Continuer + Cela installe une nouvelle copie de LSPatch Manager sous un nom de paquet différent, met à jour les applications en mode local, puis désinstalle la copie actuelle. Les correctifs intégrés ne sont pas affectés. Shizuku doit rester disponible. Accordez Shizuku au nouveau paquet après l\'installation. + Revenir au paquet d\'origine + Rétablir + Télécharge le dernier manager.apk depuis les versions JingMatrix/LSPatch, l\'installe en tant que org.lsposed.lspatch (les paramètres et les liens des applications locales sont restaurés), puis désinstalle cette application. Nécessite Shizuku et le réseau. diff --git a/manager/src/main/res/values-in/strings.xml b/manager/src/main/res/values-in/strings.xml index 5667891d7..52d7dfe21 100644 --- a/manager/src/main/res/values-in/strings.xml +++ b/manager/src/main/res/values-in/strings.xml @@ -247,4 +247,15 @@ Tandatangani dengan keystore Anda sendiri (BKS). Pasang ulang build ini Tidak dapat memeriksa pembaruan + Paket + Ubah nama paket + Nama paket baru + Acak + Masukkan nama paket yang valid (mis. org.example.app) + Shizuku diperlukan untuk mengubah nama paket + Lanjutkan + Ini memasang salinan baru LSPatch Manager dengan nama paket berbeda, memperbarui aplikasi mode lokal, lalu mencopot salinan saat ini. Tambalan terintegrasi tidak terpengaruh. Shizuku harus tetap tersedia. Berikan Shizuku ke paket baru setelah pemasangan. + Kembalikan ke paket asli + Kembalikan + Mengunduh manager.apk terbaru dari rilis JingMatrix/LSPatch, memasangnya sebagai org.lsposed.lspatch (pengaturan dan tautan aplikasi lokal dipulihkan), lalu mencopot aplikasi ini. Memerlukan Shizuku dan jaringan. diff --git a/manager/src/main/res/values-it/strings.xml b/manager/src/main/res/values-it/strings.xml index f7453ee46..652d143d8 100644 --- a/manager/src/main/res/values-it/strings.xml +++ b/manager/src/main/res/values-it/strings.xml @@ -247,4 +247,15 @@ Firma con il tuo keystore (BKS). Reinstalla questa build Impossibile controllare gli aggiornamenti + Pacchetto + Cambia nome del pacchetto + Nuovo nome del pacchetto + Casuale + Inserisci un nome del pacchetto valido (es. org.example.app) + Shizuku è necessario per cambiare il nome del pacchetto + Continua + Questo installa una nuova copia di LSPatch Manager con un nome di pacchetto diverso, aggiorna le app in modalità locale, quindi disinstalla quella attuale. Le patch integrate non sono interessate. Shizuku deve restare disponibile. Concedi Shizuku al nuovo pacchetto dopo l\'installazione. + Ripristina il pacchetto originale + Ripristina + Scarica l\'ultimo manager.apk dalle release di JingMatrix/LSPatch, lo installa come org.lsposed.lspatch (impostazioni e collegamenti delle app locali vengono ripristinati), quindi disinstalla questa app. Richiede Shizuku e la rete. diff --git a/manager/src/main/res/values-iw/strings.xml b/manager/src/main/res/values-iw/strings.xml index 18a04be49..ffff30495 100644 --- a/manager/src/main/res/values-iw/strings.xml +++ b/manager/src/main/res/values-iw/strings.xml @@ -247,4 +247,15 @@ חתימה עם מאגר מפתחות משלכם (BKS). התקנה מחדש של גרסה זו לא ניתן היה לבדוק עדכונים + חבילה + שינוי שם החבילה + שם חבילה חדש + אקראי + יש להזין שם חבילה תקין (למשל org.example.app) + ‏Shizuku נדרש כדי לשנות את שם החבילה + המשך + פעולה זו מתקינה עותק חדש של LSPatch Manager תחת שם חבילה אחר, מעדכנת את אפליקציות המצב המקומי, ולאחר מכן מסירה את העותק הנוכחי. תיקונים משולבים אינם מושפעים. Shizuku חייב להישאר זמין. יש להעניק את Shizuku לחבילה החדשה לאחר ההתקנה. + שחזור לחבילה המקורית + שחזור + מוריד את manager.apk העדכני ביותר מגרסאות JingMatrix/LSPatch, מתקין אותו בשם org.lsposed.lspatch (ההגדרות וקישורי האפליקציות המקומיות משוחזרים), ולאחר מכן מסיר אפליקציה זו. דורש Shizuku ורשת. diff --git a/manager/src/main/res/values-ja/strings.xml b/manager/src/main/res/values-ja/strings.xml index 27b77c3bc..b375bed0f 100644 --- a/manager/src/main/res/values-ja/strings.xml +++ b/manager/src/main/res/values-ja/strings.xml @@ -247,4 +247,15 @@ 独自のキーストア(BKS)で署名します。 このビルドを再インストール 更新を確認できませんでした + パッケージ + パッケージ名を変更 + 新しいパッケージ名 + ランダム + 有効なパッケージ名を入力してください(例: org.example.app) + パッケージ名の変更には Shizuku が必要です + 続行 + LSPatch Manager を別のパッケージ名で新しくインストールし、ローカルモードのアプリを更新してから、現在のものをアンインストールします。統合パッチには影響しません。Shizuku は利用可能なままにしてください。インストール後、新しいパッケージに Shizuku を許可してください。 + 元のパッケージに戻す + 元に戻す + JingMatrix/LSPatch のリリースから最新の manager.apk をダウンロードし、org.lsposed.lspatch としてインストールして(設定とローカルアプリのリンクが復元されます)、このアプリをアンインストールします。Shizuku とネットワークが必要です。 diff --git a/manager/src/main/res/values-ko/strings.xml b/manager/src/main/res/values-ko/strings.xml index a46995b63..1c024c84e 100644 --- a/manager/src/main/res/values-ko/strings.xml +++ b/manager/src/main/res/values-ko/strings.xml @@ -247,4 +247,15 @@ 자신의 키스토어(BKS)로 서명합니다. 이 빌드 다시 설치 업데이트를 확인하지 못했습니다 + 패키지 + 패키지 이름 변경 + 새 패키지 이름 + 무작위 + 올바른 패키지 이름을 입력하세요 (예: org.example.app) + 패키지 이름을 변경하려면 Shizuku가 필요합니다 + 계속 + LSPatch Manager를 다른 패키지 이름으로 새로 설치하고 로컬 모드 앱을 업데이트한 다음 현재 앱을 제거합니다. 통합 패치는 영향을 받지 않습니다. Shizuku를 계속 사용할 수 있어야 합니다. 설치 후 새 패키지에 Shizuku를 허용하세요. + 원래 패키지로 되돌리기 + 되돌리기 + JingMatrix/LSPatch 릴리스에서 최신 manager.apk를 다운로드하여 org.lsposed.lspatch로 설치하고(설정과 로컬 앱 링크가 복원됨) 이 앱을 제거합니다. Shizuku와 네트워크가 필요합니다. diff --git a/manager/src/main/res/values-pl/strings.xml b/manager/src/main/res/values-pl/strings.xml index c1158b8ba..9a3cdeba1 100644 --- a/manager/src/main/res/values-pl/strings.xml +++ b/manager/src/main/res/values-pl/strings.xml @@ -247,4 +247,15 @@ Podpisz własnym magazynem kluczy (BKS). Zainstaluj ponownie tę wersję Nie udało się sprawdzić aktualizacji + Pakiet + Zmień nazwę pakietu + Nowa nazwa pakietu + Losowo + Wprowadź prawidłową nazwę pakietu (np. org.example.app) + Do zmiany nazwy pakietu wymagane jest Shizuku + Kontynuuj + Spowoduje to zainstalowanie nowej kopii LSPatch Manager pod inną nazwą pakietu, zaktualizowanie aplikacji w trybie lokalnym, a następnie odinstalowanie bieżącej. Łatki zintegrowane nie są naruszane. Shizuku musi pozostać dostępne. Po instalacji przyznaj Shizuku nowemu pakietowi. + Przywróć oryginalny pakiet + Przywróć + Pobiera najnowszy plik manager.apk z wydań JingMatrix/LSPatch, instaluje go jako org.lsposed.lspatch (ustawienia i powiązania aplikacji lokalnych zostają przywrócone), a następnie odinstalowuje tę aplikację. Wymaga Shizuku i sieci. diff --git a/manager/src/main/res/values-pt-rBR/strings.xml b/manager/src/main/res/values-pt-rBR/strings.xml index f77463c37..6378cf176 100644 --- a/manager/src/main/res/values-pt-rBR/strings.xml +++ b/manager/src/main/res/values-pt-rBR/strings.xml @@ -247,4 +247,15 @@ Assine com o seu próprio keystore (BKS). Reinstalar esta versão Não foi possível verificar atualizações + Pacote + Alterar o nome do pacote + Novo nome do pacote + Aleatório + Insira um nome de pacote válido (ex.: org.example.app) + O Shizuku é necessário para alterar o nome do pacote + Continuar + Isso instala uma nova cópia do LSPatch Manager com um nome de pacote diferente, atualiza os apps no modo local e depois desinstala a cópia atual. Patches integrados não são afetados. O Shizuku deve continuar disponível. Conceda o Shizuku ao novo pacote após a instalação. + Reverter para o pacote original + Reverter + Baixa o manager.apk mais recente das versões do JingMatrix/LSPatch, instala-o como org.lsposed.lspatch (as configurações e os vínculos dos apps locais são restaurados) e depois desinstala este app. Requer Shizuku e rede. diff --git a/manager/src/main/res/values-ru/strings.xml b/manager/src/main/res/values-ru/strings.xml index 3b712e40c..a8b48f5eb 100644 --- a/manager/src/main/res/values-ru/strings.xml +++ b/manager/src/main/res/values-ru/strings.xml @@ -247,4 +247,15 @@ Подпись вашим собственным хранилищем ключей (BKS). Переустановить эту сборку Не удалось проверить обновления + Пакет + Изменить имя пакета + Новое имя пакета + Случайно + Введите допустимое имя пакета (например, org.example.app) + Для изменения имени пакета требуется Shizuku + Продолжить + При этом устанавливается новая копия LSPatch Manager под другим именем пакета, обновляются приложения в локальном режиме, а затем удаляется текущая копия. Встроенные патчи не затрагиваются. Shizuku должен оставаться доступным. После установки предоставьте Shizuku новому пакету. + Вернуть исходный пакет + Вернуть + Загружает последний manager.apk из релизов JingMatrix/LSPatch, устанавливает его как org.lsposed.lspatch (настройки и связи локальных приложений восстанавливаются), затем удаляет это приложение. Требуются Shizuku и сеть. diff --git a/manager/src/main/res/values-tr/strings.xml b/manager/src/main/res/values-tr/strings.xml index d15d18ea4..867544610 100644 --- a/manager/src/main/res/values-tr/strings.xml +++ b/manager/src/main/res/values-tr/strings.xml @@ -247,4 +247,15 @@ Kendi anahtar deponuzla (BKS) imzalayın. Bu yapıyı yeniden kur Güncellemeler denetlenemedi + Paket + Paket adını değiştir + Yeni paket adı + Rastgele + Geçerli bir paket adı girin (ör. org.example.app) + Paket adını değiştirmek için Shizuku gerekir + Devam + Bu, LSPatch Manager\'ın farklı bir paket adıyla yeni bir kopyasını yükler, yerel moddaki uygulamaları günceller ve ardından mevcut kopyayı kaldırır. Tümleşik yamalar etkilenmez. Shizuku kullanılabilir kalmalıdır. Kurulumdan sonra yeni pakete Shizuku izni verin. + Özgün pakete geri dön + Geri al + JingMatrix/LSPatch sürümlerinden en yeni manager.apk dosyasını indirir, org.lsposed.lspatch olarak yükler (ayarlar ve yerel uygulama bağlantıları geri yüklenir) ve ardından bu uygulamayı kaldırır. Shizuku ve ağ gerektirir. diff --git a/manager/src/main/res/values-uk/strings.xml b/manager/src/main/res/values-uk/strings.xml index c493701f5..d71168328 100644 --- a/manager/src/main/res/values-uk/strings.xml +++ b/manager/src/main/res/values-uk/strings.xml @@ -247,4 +247,15 @@ Підписати власним сховищем ключів (BKS). Перевстановити цю збірку Не вдалося перевірити оновлення + Пакет + Змінити назву пакета + Нова назва пакета + Випадково + Введіть дійсну назву пакета (наприклад, org.example.app) + Щоб змінити назву пакета, потрібен Shizuku + Продовжити + Це встановлює нову копію LSPatch Manager під іншою назвою пакета, оновлює застосунки в локальному режимі, а потім видаляє поточну копію. Вбудовані патчі не зачіпаються. Shizuku має залишатися доступним. Після встановлення надайте Shizuku новому пакету. + Повернути початковий пакет + Повернути + Завантажує найновіший manager.apk із випусків JingMatrix/LSPatch, встановлює його як org.lsposed.lspatch (налаштування та зв\'язки локальних застосунків відновлюються), а потім видаляє цей застосунок. Потребує Shizuku та мережі. diff --git a/manager/src/main/res/values-vi/strings.xml b/manager/src/main/res/values-vi/strings.xml index 888851ae1..e9c30ac95 100644 --- a/manager/src/main/res/values-vi/strings.xml +++ b/manager/src/main/res/values-vi/strings.xml @@ -247,4 +247,15 @@ Ký bằng kho khoá của riêng bạn (BKS). Cài lại bản dựng này Không kiểm tra được cập nhật + Gói + Đổi tên gói + Tên gói mới + Ngẫu nhiên + Nhập tên gói hợp lệ (ví dụ: org.example.app) + Cần Shizuku để đổi tên gói + Tiếp tục + Thao tác này cài đặt một bản LSPatch Manager mới với tên gói khác, cập nhật các ứng dụng ở chế độ cục bộ, rồi gỡ cài đặt bản hiện tại. Các bản vá tích hợp không bị ảnh hưởng. Shizuku phải luôn khả dụng. Hãy cấp Shizuku cho gói mới sau khi cài đặt. + Khôi phục về gói gốc + Khôi phục + Tải manager.apk mới nhất từ các bản phát hành JingMatrix/LSPatch, cài đặt dưới tên org.lsposed.lspatch (khôi phục cài đặt và liên kết ứng dụng cục bộ), rồi gỡ cài đặt ứng dụng này. Yêu cầu Shizuku và mạng. diff --git a/manager/src/main/res/values-zh-rCN/strings.xml b/manager/src/main/res/values-zh-rCN/strings.xml index 5f5393b76..cc781c85a 100644 --- a/manager/src/main/res/values-zh-rCN/strings.xml +++ b/manager/src/main/res/values-zh-rCN/strings.xml @@ -247,4 +247,15 @@ 已添加 %1$d 个 暴露私有数据 添加一个提供程序,让文件管理器无需 root 即可通过系统文件选择器浏览应用的私有数据目录。这会扩大可访问应用数据的范围——若无需要请保持关闭。 + 包名 + 更改包名 + 新包名 + 随机生成 + 请输入有效的包名(例如 org.example.app) + 更改包名需要 Shizuku + 继续 + 这将以不同的包名安装一个新的 LSPatch Manager 副本,更新本地模式应用,然后卸载当前副本。集成模式的补丁不受影响。Shizuku 必须保持可用。安装后请为新包授予 Shizuku。 + 还原为原始包名 + 还原 + 从 JingMatrix/LSPatch 的发行版下载最新的 manager.apk,将其安装为 org.lsposed.lspatch(会还原设置和本地应用的关联),然后卸载此应用。需要 Shizuku 和网络。 diff --git a/manager/src/main/res/values-zh-rTW/strings.xml b/manager/src/main/res/values-zh-rTW/strings.xml index 5dbb44a52..20b732464 100644 --- a/manager/src/main/res/values-zh-rTW/strings.xml +++ b/manager/src/main/res/values-zh-rTW/strings.xml @@ -247,4 +247,15 @@ 使用你自己的金鑰庫(BKS)簽署。 重新安裝此版本 無法檢查更新 + 套件名稱 + 變更套件名稱 + 新套件名稱 + 隨機產生 + 請輸入有效的套件名稱(例如 org.example.app) + 變更套件名稱需要 Shizuku + 繼續 + 這會以不同的套件名稱安裝一份新的 LSPatch Manager,更新本機模式應用程式,然後解除安裝目前的版本。整合模式的修補不受影響。Shizuku 必須保持可用。安裝後請為新套件授予 Shizuku。 + 還原為原始套件 + 還原 + 從 JingMatrix/LSPatch 的發行版下載最新的 manager.apk,將其安裝為 org.lsposed.lspatch(會還原設定與本機應用程式的關聯),然後解除安裝此應用程式。需要 Shizuku 與網路。 diff --git a/manager/src/main/res/values/strings.xml b/manager/src/main/res/values/strings.xml index e63e51462..0d64065e8 100644 --- a/manager/src/main/res/values/strings.xml +++ b/manager/src/main/res/values/strings.xml @@ -221,26 +221,16 @@ Wrong alias name Wrong alias password Detail patch logs - Hide package name - Current: %1$s\nChange the installed package id so detectors looking for org.lsposed.lspatch fail. Requires Shizuku. Local patched apps will be updated. Change package name New package name Randomize This installs a new copy of LSPatch Manager under a different package name, updates Local-mode apps, then uninstalls the current one. Integrated patches are unaffected. Shizuku must stay available. Grant Shizuku to the new package after install. Enter a valid package name (e.g. org.example.app) - Package name is the same as the current one Shizuku is required to change the package name - Configure storage directory first (used when re-patching Local apps) - Working… - Installed as %1$s. Open the new manager and grant Shizuku if prompted. - Failed: %1$s Continue - Revert to original - Current: %1$s\nDownload the latest official manager from GitHub (org.lsposed.lspatch) and remove this cloaked install. Revert to original package Downloads the latest manager.apk from JingMatrix/LSPatch releases, installs it as org.lsposed.lspatch (settings and Local app links are restored), then uninstalls this app. Requires Shizuku and network. Revert - Restored org.lsposed.lspatch. Grant Shizuku to it if prompted. Inject loader dex Injects the loader dex directly into the app. Turn this on for apps with isolated processes, such as a browser\'s rendering engine, so their hooks still load.