Do not use string resources for app label
This not only simplifies hiding stub APKs (no resource IDs involved), but also opens the opportunity to allow users to customize whatever app name they want after it is hidden.
This commit is contained in:
parent
40eda05a30
commit
78daa2eb62
2
app/proguard-rules.pro
vendored
2
app/proguard-rules.pro
vendored
@ -32,7 +32,7 @@
|
|||||||
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
|
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
|
||||||
|
|
||||||
# BootSigner
|
# BootSigner
|
||||||
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
|
-keep class a.a { *; }
|
||||||
|
|
||||||
# Strip logging
|
# Strip logging
|
||||||
-assumenosideeffects class timber.log.Timber.Tree { *; }
|
-assumenosideeffects class timber.log.Timber.Tree { *; }
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
package a;
|
package a;
|
||||||
|
|
||||||
import androidx.annotation.Keep;
|
|
||||||
import androidx.core.app.AppComponentFactory;
|
import androidx.core.app.AppComponentFactory;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||||
import com.topjohnwu.signing.BootSigner;
|
import com.topjohnwu.signing.BootSigner;
|
||||||
|
|
||||||
@Keep
|
|
||||||
public class a extends AppComponentFactory {
|
public class a extends AppComponentFactory {
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static boolean patchAPK(String in, String out, String pkg) {
|
public static boolean patchAPK(String in, String out, String pkg) {
|
||||||
return PatchAPK.patch(in, out, pkg);
|
return PatchAPK.patch(in, out, pkg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean patchAPK(String in, String out, String pkg, String label) {
|
||||||
|
return PatchAPK.patch(in, out, pkg, label);
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
BootSigner.main(args);
|
BootSigner.main(args);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import com.topjohnwu.magisk.BuildConfig
|
|||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.cmp
|
import com.topjohnwu.magisk.cmp
|
||||||
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
@ -12,7 +11,6 @@ import com.topjohnwu.magisk.ui.SplashActivity
|
|||||||
import com.topjohnwu.magisk.utils.PatchAPK
|
import com.topjohnwu.magisk.utils.PatchAPK
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
private fun RemoteFileService.patchPackage(apk: File, id: Int) {
|
private fun RemoteFileService.patchPackage(apk: File, id: Int) {
|
||||||
@ -24,17 +22,7 @@ private fun RemoteFileService.patchPackage(apk: File, id: Int) {
|
|||||||
.setContentText("")
|
.setContentText("")
|
||||||
}
|
}
|
||||||
val patched = File(apk.parent, "patched.apk")
|
val patched = File(apk.parent, "patched.apk")
|
||||||
try {
|
PatchAPK.patch(apk, patched, packageName, applicationInfo.nonLocalizedLabel.toString())
|
||||||
// Try using the new APK to patch itself
|
|
||||||
val loader = DynamicClassLoader(apk)
|
|
||||||
loader.loadClass("a.a")
|
|
||||||
.getMethod("patchAPK", String::class.java, String::class.java, String::class.java)
|
|
||||||
.invoke(null, apk.path, patched.path, packageName)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e)
|
|
||||||
// Fallback to use the current implementation
|
|
||||||
PatchAPK.patch(apk.path, patched.path, packageName)
|
|
||||||
}
|
|
||||||
apk.delete()
|
apk.delete()
|
||||||
patched.renameTo(apk)
|
patched.renameTo(apk)
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,8 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
val suCategory = findPreference<PreferenceCategory>("superuser")!!
|
val suCategory = findPreference<PreferenceCategory>("superuser")!!
|
||||||
val hideManager = findPreference<Preference>("hide")!!
|
val hideManager = findPreference<Preference>("hide")!!
|
||||||
hideManager.setOnPreferenceClickListener {
|
hideManager.setOnPreferenceClickListener {
|
||||||
PatchAPK.hideManager(requireContext())
|
// TODO: Add UI to allow user to customize app name
|
||||||
|
PatchAPK.hideManager(requireContext(), "Manager")
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
val restoreManager = findPreference<Preference>("restore")
|
val restoreManager = findPreference<Preference>("restore")
|
||||||
|
@ -3,6 +3,7 @@ package com.topjohnwu.magisk.utils
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.*
|
||||||
|
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
import com.topjohnwu.magisk.ui.SplashActivity
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
@ -46,52 +47,38 @@ object PatchAPK {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean {
|
private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean {
|
||||||
if (from.length != to.length)
|
if (to.length > from.length)
|
||||||
return false
|
return false
|
||||||
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer()
|
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer()
|
||||||
val offList = mutableListOf<Int>()
|
val offList = mutableListOf<Int>()
|
||||||
var i = 0
|
var i = 0
|
||||||
while (i < buf.length - from.length) {
|
loop@ while (i < buf.length - from.length) {
|
||||||
var match = true
|
for (j in from.indices) {
|
||||||
for (j in 0 until from.length) {
|
|
||||||
if (buf.get(i + j) != from[j]) {
|
if (buf.get(i + j) != from[j]) {
|
||||||
match = false
|
++i
|
||||||
break
|
continue@loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (match) {
|
|
||||||
offList.add(i)
|
offList.add(i)
|
||||||
i += from.length
|
i += from.length
|
||||||
}
|
}
|
||||||
++i
|
|
||||||
}
|
|
||||||
if (offList.isEmpty())
|
if (offList.isEmpty())
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
val toBuf = to.toCharArray().copyOf(from.length)
|
||||||
for (off in offList) {
|
for (off in offList) {
|
||||||
buf.position(off)
|
buf.position(off)
|
||||||
buf.put(to)
|
buf.put(toBuf)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findAndPatch(xml: ByteArray, a: Int, b: Int): Boolean {
|
private fun patchAndHide(context: Context, label: String): Boolean {
|
||||||
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer()
|
|
||||||
val len = xml.size / 4
|
|
||||||
for (i in 0 until len) {
|
|
||||||
if (buf.get(i) == a) {
|
|
||||||
buf.put(i, b)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun patchAndHide(context: Context): Boolean {
|
|
||||||
// Generate a new app with random package name
|
// Generate a new app with random package name
|
||||||
val repack = File(context.filesDir, "patched.apk")
|
val repack = File(context.filesDir, "patched.apk")
|
||||||
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
|
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
|
||||||
|
|
||||||
if (!patch(context.packageCodePath, repack.path, pkg))
|
if (!patch(context.packageCodePath, repack.path, pkg, label))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// Install the application
|
// Install the application
|
||||||
@ -107,14 +94,15 @@ object PatchAPK {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun patch(apk: String, out: String, pkg: String): Boolean {
|
@JvmOverloads
|
||||||
|
fun patch(apk: String, out: String, pkg: String, label: String = "Manager"): Boolean {
|
||||||
try {
|
try {
|
||||||
val jar = JarMap(apk)
|
val jar = JarMap(apk)
|
||||||
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
||||||
val xml = jar.getRawData(je)
|
val xml = jar.getRawData(je)
|
||||||
|
|
||||||
if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) ||
|
if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) ||
|
||||||
!findAndPatch(xml, R.string.app_name, R.string.re_app_name))
|
!findAndPatch(xml, "Magisk Manager", label))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// Write apk changes
|
// Write apk changes
|
||||||
@ -128,11 +116,32 @@ object PatchAPK {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hideManager(context: Context) {
|
fun patch(apk: File, out: File, pkg: String, label: String): Boolean {
|
||||||
|
try {
|
||||||
|
// Try using the new APK to patch itself
|
||||||
|
val loader = DynamicClassLoader(apk)
|
||||||
|
val cls = loader.loadClass("a.a")
|
||||||
|
|
||||||
|
for (m in cls.declaredMethods) {
|
||||||
|
val pars = m.parameterTypes
|
||||||
|
if (pars.size == 4 && pars[0] == String::class.java) {
|
||||||
|
return m.invoke(null, apk.path, out.path, pkg, label) as Boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Exception("No matching method found")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
// Fallback to use the current implementation
|
||||||
|
patch(apk.path, out.path, pkg, label)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideManager(context: Context, label: String) {
|
||||||
Completable.fromAction {
|
Completable.fromAction {
|
||||||
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
||||||
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
||||||
if (!patchAndHide(context))
|
if (!patchAndHide(context, label))
|
||||||
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
||||||
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
||||||
}.subscribeK()
|
}.subscribeK()
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<application
|
<application
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:installLocation="internalOnly"
|
android:installLocation="internalOnly"
|
||||||
android:label="@string/app_name"
|
android:label="Magisk Manager"
|
||||||
android:supportsRtl="true">
|
android:supportsRtl="true">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
Loading…
Reference in New Issue
Block a user