fix: Improve root installations management to fix patching of already patched apps

This commit is contained in:
Alberto Ponces 2022-09-13 16:54:43 +01:00
parent 779b659108
commit d613cece15
7 changed files with 100 additions and 85 deletions

View File

@ -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<String>("originalFilePath")
val backupFilePath = call.argument<String>("backupFilePath")
if (originalFilePath != null && backupFilePath != null) {
File(originalFilePath).copyTo(File(backupFilePath), true)
result.success(null)
} else {
result.notImplemented()
}
}
"runPatcher" -> {
val patchBundleFilePath = call.argument<String>("patchBundleFilePath")
val originalFilePath = call.argument<String>("originalFilePath")
val inputFilePath = call.argument<String>("inputFilePath")
val patchedFilePath = call.argument<String>("patchedFilePath")
val outFilePath = call.argument<String>("outFilePath")
@ -45,7 +53,6 @@ class MainActivity : FlutterActivity() {
val resourcePatching = call.argument<Boolean>("resourcePatching")
val keyStoreFilePath = call.argument<String>("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"
)
)
}

View File

@ -198,7 +198,10 @@ class ManagerAPI {
Future<bool> 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;

View File

@ -82,9 +82,31 @@ class PatcherAPI {
.toList();
}
Future<void> runPatcher(
Future<String> 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<void> runPatcher(
String packageName,
String inputFilePath,
List<Patch> 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<bool> checkOldPatch(PatchedApplication patchedApp) async {
if (patchedApp.isRooted) {
return await _rootAPI.isAppInstalled(patchedApp.packageName);
}
return false;
}
Future<void> deleteOldPatch(PatchedApplication patchedApp) async {
if (patchedApp.isRooted) {
await _rootAPI.deleteApp(patchedApp.packageName, patchedApp.apkFilePath);
}
}
void shareLog(String logs) {
ShareExtend.share(logs, 'text');
}

View File

@ -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<void> mountApk(
String packageName,
String originalFilePath,
String patchedFilePath,
) async {
Future<void> 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<String> getOriginalFilePath(String packageName) async {
return '$_managerDirPath/$packageName/original.apk';
}
Future<void> saveOriginalFilePath(
String packageName,
String originalFilePath,
) async {
String originalRootPath = '$_managerDirPath/$packageName/original.apk';
await Root.exec(
cmd: 'cp "$originalFilePath" "$originalRootPath"',
);
}
}

View File

@ -30,7 +30,6 @@ class AppSelectorViewModel extends BaseViewModel {
apkFilePath: application.apkFilePath,
icon: application.icon,
patchDate: DateTime.now(),
isRooted: false,
);
locator<PatcherViewModel>().selectedPatches.clear();
locator<PatcherViewModel>().notifyListeners();
@ -55,7 +54,6 @@ class AppSelectorViewModel extends BaseViewModel {
apkFilePath: result.files.single.path!,
icon: application.icon,
patchDate: DateTime.now(),
isRooted: false,
);
locator<PatcherViewModel>().selectedPatches.clear();
locator<PatcherViewModel>().notifyListeners();

View File

@ -104,25 +104,25 @@ class InstallerViewModel extends BaseViewModel {
Future<void> 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();

View File

@ -19,10 +19,13 @@ class AppInfoViewModel extends BaseViewModel {
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
final RootAPI _rootAPI = RootAPI();
void uninstallApp(PatchedApplication app) {
Future<void> 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: <Widget>[
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: <Widget>[
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
)
],
),
);
}
} else {
return showDialog(
context: context,