From d613cece150cf37f42fab2bfe3acf32e6a05a8dd Mon Sep 17 00:00:00 2001 From: Alberto Ponces Date: Tue, 13 Sep 2022 16:54:43 +0100 Subject: [PATCH] fix: Improve root installations management to fix patching of already patched apps --- .../app/revanced/manager/MainActivity.kt | 32 ++++++-------- lib/services/manager_api.dart | 5 ++- lib/services/patcher_api.dart | 43 +++++++++++-------- lib/services/root_api.dart | 32 +++++++++----- .../app_selector/app_selector_viewmodel.dart | 2 - .../views/installer/installer_viewmodel.dart | 28 ++++++------ .../appInfoView/app_info_viewmodel.dart | 43 +++++++++++-------- 7 files changed, 100 insertions(+), 85 deletions(-) diff --git a/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt b/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt index 2dcb9789..6d6d4df0 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/MainActivity.kt @@ -28,13 +28,21 @@ class MainActivity : FlutterActivity() { override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) val mainChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, PATCHER_CHANNEL) - installerChannel = - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL) + installerChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL) mainChannel.setMethodCallHandler { call, result -> when (call.method) { + "copyOriginalApk" -> { + val originalFilePath = call.argument("originalFilePath") + val backupFilePath = call.argument("backupFilePath") + if (originalFilePath != null && backupFilePath != null) { + File(originalFilePath).copyTo(File(backupFilePath), true) + result.success(null) + } else { + result.notImplemented() + } + } "runPatcher" -> { val patchBundleFilePath = call.argument("patchBundleFilePath") - val originalFilePath = call.argument("originalFilePath") val inputFilePath = call.argument("inputFilePath") val patchedFilePath = call.argument("patchedFilePath") val outFilePath = call.argument("outFilePath") @@ -45,7 +53,6 @@ class MainActivity : FlutterActivity() { val resourcePatching = call.argument("resourcePatching") val keyStoreFilePath = call.argument("keyStoreFilePath") if (patchBundleFilePath != null && - originalFilePath != null && inputFilePath != null && patchedFilePath != null && outFilePath != null && @@ -59,7 +66,6 @@ class MainActivity : FlutterActivity() { runPatcher( result, patchBundleFilePath, - originalFilePath, inputFilePath, patchedFilePath, outFilePath, @@ -82,7 +88,6 @@ class MainActivity : FlutterActivity() { fun runPatcher( result: MethodChannel.Result, patchBundleFilePath: String, - originalFilePath: String, inputFilePath: String, patchedFilePath: String, outFilePath: String, @@ -93,7 +98,6 @@ class MainActivity : FlutterActivity() { resourcePatching: Boolean, keyStoreFilePath: String ) { - val originalFile = File(originalFilePath) val inputFile = File(inputFilePath) val patchedFile = File(patchedFilePath) val outFile = File(outFilePath) @@ -115,25 +119,13 @@ class MainActivity : FlutterActivity() { Thread( Runnable { - handler.post { - installerChannel.invokeMethod( - "update", - mapOf( - "progress" to 0.1, - "header" to "", - "log" to "Copying original apk" - ) - ) - } - originalFile.copyTo(inputFile, true) - handler.post { installerChannel.invokeMethod( "update", mapOf( "progress" to 0.2, "header" to "Unpacking apk...", - "log" to "Unpacking copied apk" + "log" to "Unpacking input apk" ) ) } diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index d621cb0c..8c9e3e73 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -198,7 +198,10 @@ class ManagerAPI { Future isAppUninstalled(PatchedApplication app) async { bool existsRoot = false; if (app.isRooted) { - existsRoot = await _rootAPI.isAppInstalled(app.packageName); + bool hasRootPermissions = await _rootAPI.hasRootPermissions(); + if (hasRootPermissions) { + existsRoot = await _rootAPI.isAppInstalled(app.packageName); + } } bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName); return !existsRoot && !existsNonRoot; diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index 54829360..cd70656e 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -82,9 +82,31 @@ class PatcherAPI { .toList(); } - Future runPatcher( + Future copyOriginalApk( String packageName, String originalFilePath, + ) async { + bool hasRootPermissions = await _rootAPI.hasRootPermissions(); + if (hasRootPermissions) { + String originalRootPath = await _rootAPI.getOriginalFilePath(packageName); + if (File(originalRootPath).existsSync()) { + originalFilePath = originalRootPath; + } + } + String backupFilePath = '${_tmpDir.path}/$packageName.apk'; + await patcherChannel.invokeMethod( + 'copyOriginalApk', + { + 'originalFilePath': originalFilePath, + 'backupFilePath': backupFilePath, + }, + ); + return backupFilePath; + } + + Future runPatcher( + String packageName, + String inputFilePath, List selectedPatches, ) async { bool mergeIntegrations = selectedPatches.any( @@ -118,7 +140,6 @@ class PatcherAPI { if (patchBundleFile != null) { _tmpDir.createSync(); Directory workDir = _tmpDir.createTempSync('tmp-'); - File inputFile = File('${workDir.path}/base.apk'); File patchedFile = File('${workDir.path}/patched.apk'); _outFile = File('${workDir.path}/out.apk'); Directory cacheDir = Directory('${workDir.path}/cache'); @@ -127,8 +148,7 @@ class PatcherAPI { 'runPatcher', { 'patchBundleFilePath': patchBundleFile.path, - 'originalFilePath': originalFilePath, - 'inputFilePath': inputFile.path, + 'inputFilePath': inputFilePath, 'patchedFilePath': patchedFile.path, 'outFilePath': _outFile!.path, 'integrationsPath': mergeIntegrations ? integrationsFile!.path : '', @@ -153,8 +173,6 @@ class PatcherAPI { patchedApp.apkFilePath, _outFile!.path, ); - } else { - return false; } } else { await AppInstaller.installApk(_outFile!.path); @@ -179,19 +197,6 @@ class PatcherAPI { } } - Future checkOldPatch(PatchedApplication patchedApp) async { - if (patchedApp.isRooted) { - return await _rootAPI.isAppInstalled(patchedApp.packageName); - } - return false; - } - - Future deleteOldPatch(PatchedApplication patchedApp) async { - if (patchedApp.isRooted) { - await _rootAPI.deleteApp(patchedApp.packageName, patchedApp.apkFilePath); - } - } - void shareLog(String logs) { ShareExtend.share(logs, 'text'); } diff --git a/lib/services/root_api.dart b/lib/services/root_api.dart index 8e454ab7..4f06ee3c 100644 --- a/lib/services/root_api.dart +++ b/lib/services/root_api.dart @@ -1,7 +1,7 @@ import 'package:root/root.dart'; class RootAPI { - final String _managerDirPath = '/data/adb/revanced_manager'; + final String _managerDirPath = '/data/adb/revanced-manager'; final String _postFsDataDirPath = '/data/adb/post-fs-data.d'; final String _serviceDDirPath = '/data/adb/service.d'; @@ -72,13 +72,15 @@ class RootAPI { String patchedFilePath, ) async { try { + await deleteApp(packageName, originalFilePath); await Root.exec( cmd: 'mkdir -p "$_managerDirPath/$packageName"', ); - installServiceDScript(packageName); - installPostFsDataScript(packageName); - installApk(packageName, patchedFilePath); - mountApk(packageName, originalFilePath, patchedFilePath); + await saveOriginalFilePath(packageName, originalFilePath); + await installServiceDScript(packageName); + await installPostFsDataScript(packageName); + await installApk(packageName, patchedFilePath); + await mountApk(packageName, originalFilePath); return true; } on Exception { return false; @@ -129,11 +131,7 @@ class RootAPI { ); } - Future mountApk( - String packageName, - String originalFilePath, - String patchedFilePath, - ) async { + Future mountApk(String packageName, String originalFilePath) async { String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk'; await Root.exec( cmd: 'am force-stop "$packageName"', @@ -145,4 +143,18 @@ class RootAPI { cmd: 'su -mm -c "mount -o bind $newPatchedFilePath $originalFilePath"', ); } + + Future getOriginalFilePath(String packageName) async { + return '$_managerDirPath/$packageName/original.apk'; + } + + Future saveOriginalFilePath( + String packageName, + String originalFilePath, + ) async { + String originalRootPath = '$_managerDirPath/$packageName/original.apk'; + await Root.exec( + cmd: 'cp "$originalFilePath" "$originalRootPath"', + ); + } } diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart index 90f75329..00e0f6a5 100644 --- a/lib/ui/views/app_selector/app_selector_viewmodel.dart +++ b/lib/ui/views/app_selector/app_selector_viewmodel.dart @@ -30,7 +30,6 @@ class AppSelectorViewModel extends BaseViewModel { apkFilePath: application.apkFilePath, icon: application.icon, patchDate: DateTime.now(), - isRooted: false, ); locator().selectedPatches.clear(); locator().notifyListeners(); @@ -55,7 +54,6 @@ class AppSelectorViewModel extends BaseViewModel { apkFilePath: result.files.single.path!, icon: application.icon, patchDate: DateTime.now(), - isRooted: false, ); locator().selectedPatches.clear(); locator().notifyListeners(); diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index b72f2fcf..393fea5e 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -104,25 +104,25 @@ class InstallerViewModel extends BaseViewModel { Future runPatcher() async { update(0.0, 'Initializing...', 'Initializing installer'); if (_patches.isNotEmpty) { - String apkFilePath = _app.apkFilePath; try { - if (_app.isRooted) { - update(0.0, '', 'Checking if an old patched version exists'); - bool oldExists = await _patcherAPI.checkOldPatch(_app); - if (oldExists) { - update(0.0, '', 'Deleting old patched version'); - await _patcherAPI.deleteOldPatch(_app); - } - } - update(0.0, '', 'Creating working directory'); - await _patcherAPI.runPatcher(_app.packageName, apkFilePath, _patches); - } on Exception { + update(0.1, '', 'Copying original apk'); + String inputFilePath = await _patcherAPI.copyOriginalApk( + _app.packageName, + _app.apkFilePath, + ); + update(0.1, '', 'Creating working directory'); + await _patcherAPI.runPatcher( + _app.packageName, + inputFilePath, + _patches, + ); + } catch (e) { hasErrors = true; - update(1.0, 'Aborting...', 'An error occurred! Aborting'); + update(-1.0, 'Aborting...', 'An error occurred! Aborting\nError: $e'); } } else { hasErrors = true; - update(1.0, 'Aborting...', 'No app or patches selected! Aborting'); + update(-1.0, 'Aborting...', 'No app or patches selected! Aborting'); } try { await FlutterBackground.disableBackgroundExecution(); diff --git a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart index 474f5b64..519d1a72 100644 --- a/lib/ui/widgets/appInfoView/app_info_viewmodel.dart +++ b/lib/ui/widgets/appInfoView/app_info_viewmodel.dart @@ -19,10 +19,13 @@ class AppInfoViewModel extends BaseViewModel { final PatcherAPI _patcherAPI = locator(); final RootAPI _rootAPI = RootAPI(); - void uninstallApp(PatchedApplication app) { + Future uninstallApp(PatchedApplication app) async { if (app.isRooted) { - _rootAPI.deleteApp(app.packageName, app.apkFilePath); - _managerAPI.deletePatchedApp(app); + bool hasRootPermissions = await _rootAPI.hasRootPermissions(); + if (hasRootPermissions) { + _rootAPI.deleteApp(app.packageName, app.apkFilePath); + _managerAPI.deletePatchedApp(app); + } } else { DeviceApps.uninstallApp(app.packageName); _managerAPI.deletePatchedApp(app); @@ -41,22 +44,24 @@ class AppInfoViewModel extends BaseViewModel { BuildContext context, PatchedApplication app, ) async { - bool hasRootPermissions = await _rootAPI.hasRootPermissions(); - if (app.isRooted && !hasRootPermissions) { - return showDialog( - context: context, - builder: (context) => AlertDialog( - title: I18nText('appInfoView.rootDialogTitle'), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, - content: I18nText('appInfoView.rootDialogText'), - actions: [ - CustomMaterialButton( - label: I18nText('okButton'), - onPressed: () => Navigator.of(context).pop(), - ) - ], - ), - ); + if (app.isRooted) { + bool hasRootPermissions = await _rootAPI.hasRootPermissions(); + if (!hasRootPermissions) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: I18nText('appInfoView.rootDialogTitle'), + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + content: I18nText('appInfoView.rootDialogText'), + actions: [ + CustomMaterialButton( + label: I18nText('okButton'), + onPressed: () => Navigator.of(context).pop(), + ) + ], + ), + ); + } } else { return showDialog( context: context,