forked from ReVanced/revanced-manager
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: root installation (ReVanced#1243)
- Loading branch information
1 parent
b4dfcf1
commit bf10af2
Showing
22 changed files
with
684 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
app/src/main/aidl/app/revanced/manager/IRootSystemService.aidl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// IRootService.aidl | ||
package app.revanced.manager; | ||
|
||
// Declare any non-default types here with import statements | ||
|
||
interface IRootSystemService { | ||
IBinder getFileSystemService(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
id=__PKG_NAME__-ReVanced | ||
name=__LABEL__ ReVanced | ||
version=__VERSION__ | ||
versionCode=0 | ||
author=ReVanced | ||
description=Mounts the patched apk on top of the original apk |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
#!/system/bin/sh | ||
DIR=${0%/*} | ||
|
||
package_name="__PKG_NAME__" | ||
version="__VERSION__" | ||
|
||
rm "$DIR/log" | ||
|
||
{ | ||
|
||
until [ "$(getprop sys.boot_completed)" = 1 ]; do sleep 5; done | ||
sleep 5 | ||
|
||
base_path="$DIR/$package_name.apk" | ||
stock_path="$(pm path "$package_name" | grep base | sed 's/package://g')" | ||
stock_version="$(dumpsys package "$package_name" | grep versionName | cut -d "=" -f2)" | ||
|
||
echo "base_path: $base_path" | ||
echo "stock_path: $stock_path" | ||
echo "base_version: $version" | ||
echo "stock_version: $stock_version" | ||
|
||
if mount | grep -q "$stock_path" ; then | ||
echo "Not mounting as stock path is already mounted" | ||
exit 1 | ||
fi | ||
|
||
if [ "$version" != "$stock_version" ]; then | ||
echo "Not mounting as versions don't match" | ||
exit 1 | ||
fi | ||
|
||
if [ -z "$stock_path" ]; then | ||
echo "Not mounting as app info could not be loaded" | ||
exit 1 | ||
fi | ||
|
||
mount -o bind "$base_path" "$stock_path" | ||
|
||
} >> "$DIR/log" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package app.revanced.manager.di | ||
|
||
import app.revanced.manager.domain.installer.RootInstaller | ||
import app.revanced.manager.service.RootConnection | ||
import org.koin.core.module.dsl.singleOf | ||
import org.koin.dsl.module | ||
|
||
val rootModule = module { | ||
singleOf(::RootConnection) | ||
singleOf(::RootInstaller) | ||
} |
131 changes: 131 additions & 0 deletions
131
app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package app.revanced.manager.domain.installer | ||
|
||
import android.app.Application | ||
import app.revanced.manager.service.RootConnection | ||
import app.revanced.manager.util.PM | ||
import com.topjohnwu.superuser.Shell | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.withContext | ||
import java.io.File | ||
|
||
class RootInstaller( | ||
private val app: Application, | ||
private val rootConnection: RootConnection, | ||
private val pm: PM | ||
) { | ||
fun hasRootAccess() = Shell.isAppGrantedRoot() ?: false | ||
|
||
fun isAppInstalled(packageName: String) = | ||
rootConnection.remoteFS?.getFile("$modulesPath/$packageName-revanced") | ||
?.exists() ?: throw RootServiceException() | ||
|
||
fun isAppMounted(packageName: String): Boolean { | ||
return pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir?.let { | ||
Shell.cmd("mount | grep \"$it\"").exec().isSuccess | ||
} ?: false | ||
} | ||
|
||
fun mount(packageName: String) { | ||
if (isAppMounted(packageName)) return | ||
|
||
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir | ||
?: throw Exception("Failed to load application info") | ||
val patchedAPK = "$modulesPath/$packageName-revanced/$packageName.apk" | ||
|
||
Shell.cmd("mount -o bind \"$patchedAPK\" \"$stockAPK\"").exec() | ||
.also { if (!it.isSuccess) throw Exception("Failed to mount APK") } | ||
} | ||
|
||
fun unmount(packageName: String) { | ||
if (!isAppMounted(packageName)) return | ||
|
||
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir | ||
?: throw Exception("Failed to load application info") | ||
|
||
Shell.cmd("umount -l \"$stockAPK\"").exec() | ||
.also { if (!it.isSuccess) throw Exception("Failed to unmount APK") } | ||
} | ||
|
||
suspend fun install( | ||
patchedAPK: File, | ||
stockAPK: File?, | ||
packageName: String, | ||
version: String, | ||
label: String | ||
) { | ||
withContext(Dispatchers.IO) { | ||
rootConnection.remoteFS?.let { remoteFS -> | ||
val assets = app.assets | ||
val modulePath = "$modulesPath/$packageName-revanced" | ||
|
||
unmount(packageName) | ||
|
||
stockAPK?.let { stockApp -> | ||
pm.getPackageInfo(packageName)?.let { packageInfo -> | ||
if (packageInfo.versionName <= version) | ||
Shell.cmd("pm uninstall -k --user 0 $packageName").exec() | ||
.also { if (!it.isSuccess) throw Exception("Failed to uninstall stock app") } | ||
} | ||
|
||
Shell.cmd("pm install \"${stockApp.absolutePath}\"").exec() | ||
.also { if (!it.isSuccess) throw Exception("Failed to install stock app") } | ||
} | ||
|
||
remoteFS.getFile(modulePath).mkdir() | ||
|
||
listOf( | ||
"service.sh", | ||
"module.prop", | ||
).forEach { file -> | ||
assets.open("root/$file").use { inputStream -> | ||
remoteFS.getFile("$modulePath/$file").newOutputStream() | ||
.use { outputStream -> | ||
val content = String(inputStream.readBytes()) | ||
.replace("__PKG_NAME__", packageName) | ||
.replace("__VERSION__", version) | ||
.replace("__LABEL__", label) | ||
.toByteArray() | ||
|
||
outputStream.write(content) | ||
} | ||
} | ||
} | ||
|
||
"$modulePath/$packageName.apk".let { apkPath -> | ||
|
||
remoteFS.getFile(patchedAPK.absolutePath) | ||
.also { if (!it.exists()) throw Exception("File doesn't exist") } | ||
.newInputStream().use { inputStream -> | ||
remoteFS.getFile(apkPath).newOutputStream().use { outputStream -> | ||
inputStream.copyTo(outputStream) | ||
} | ||
} | ||
|
||
Shell.cmd( | ||
"chmod 644 $apkPath", | ||
"chown system:system $apkPath", | ||
"chcon u:object_r:apk_data_file:s0 $apkPath", | ||
"chmod +x $modulePath/service.sh" | ||
).exec() | ||
.let { if (!it.isSuccess) throw Exception("Failed to set file permissions") } | ||
} | ||
} ?: throw RootServiceException() | ||
} | ||
} | ||
|
||
fun uninstall(packageName: String) { | ||
rootConnection.remoteFS?.let { remoteFS -> | ||
if (isAppMounted(packageName)) | ||
unmount(packageName) | ||
|
||
remoteFS.getFile("$modulesPath/$packageName-revanced").deleteRecursively() | ||
.also { if (!it) throw Exception("Failed to delete files") } | ||
} ?: throw RootServiceException() | ||
} | ||
|
||
companion object { | ||
const val modulesPath = "/data/adb/modules" | ||
} | ||
} | ||
|
||
class RootServiceException: Exception("Root not available") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
app/src/main/java/app/revanced/manager/service/RootService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package app.revanced.manager.service | ||
|
||
import android.content.ComponentName | ||
import android.content.Intent | ||
import android.content.ServiceConnection | ||
import android.os.IBinder | ||
import app.revanced.manager.IRootSystemService | ||
import com.topjohnwu.superuser.ipc.RootService | ||
import com.topjohnwu.superuser.nio.FileSystemManager | ||
|
||
class ManagerRootService : RootService() { | ||
class RootSystemService : IRootSystemService.Stub() { | ||
override fun getFileSystemService() = | ||
FileSystemManager.getService() | ||
} | ||
|
||
override fun onBind(intent: Intent): IBinder { | ||
return RootSystemService() | ||
} | ||
} | ||
|
||
class RootConnection : ServiceConnection { | ||
var remoteFS: FileSystemManager? = null | ||
private set | ||
|
||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) { | ||
val ipc = IRootSystemService.Stub.asInterface(service) | ||
val binder = ipc.fileSystemService | ||
|
||
remoteFS = FileSystemManager.getRemote(binder) | ||
} | ||
|
||
override fun onServiceDisconnected(name: ComponentName?) { | ||
remoteFS = null | ||
} | ||
} |
Oops, something went wrong.