2023-03-05 10:12:46 +01:00
|
|
|
import 'package:flutter/foundation.dart';
|
2022-08-14 20:40:34 +02:00
|
|
|
import 'package:root/root.dart';
|
|
|
|
|
|
|
|
class RootAPI {
|
2023-12-23 04:16:28 +01:00
|
|
|
final String _revancedDirPath = '/data/adb/revanced';
|
2022-08-31 10:36:36 +02:00
|
|
|
final String _serviceDDirPath = '/data/adb/service.d';
|
2022-08-14 20:40:34 +02:00
|
|
|
|
2022-09-23 16:47:30 +02:00
|
|
|
Future<bool> isRooted() async {
|
|
|
|
try {
|
2023-01-30 13:35:06 +01:00
|
|
|
final bool? isRooted = await Root.isRootAvailable();
|
2022-09-23 16:47:30 +02:00
|
|
|
return isRooted != null && isRooted;
|
2023-03-05 10:12:46 +01:00
|
|
|
} on Exception catch (e) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
print(e);
|
|
|
|
}
|
2022-09-23 16:47:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-06 15:40:49 +02:00
|
|
|
Future<bool> hasRootPermissions() async {
|
2022-09-16 17:39:37 +02:00
|
|
|
try {
|
2022-09-22 15:01:05 +02:00
|
|
|
bool? isRooted = await Root.isRootAvailable();
|
|
|
|
if (isRooted != null && isRooted) {
|
|
|
|
isRooted = await Root.isRooted();
|
|
|
|
return isRooted != null && isRooted;
|
|
|
|
}
|
|
|
|
return false;
|
2023-03-05 10:12:46 +01:00
|
|
|
} on Exception catch (e) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
print(e);
|
|
|
|
}
|
2022-09-16 17:39:37 +02:00
|
|
|
return false;
|
|
|
|
}
|
2022-09-06 15:40:49 +02:00
|
|
|
}
|
|
|
|
|
2022-09-14 10:59:56 +02:00
|
|
|
Future<void> setPermissions(
|
|
|
|
String permissions,
|
|
|
|
ownerGroup,
|
|
|
|
seLinux,
|
|
|
|
String filePath,
|
|
|
|
) async {
|
2023-06-23 00:03:03 +02:00
|
|
|
try {
|
2023-12-23 19:02:09 +01:00
|
|
|
final StringBuffer commands = StringBuffer();
|
2023-06-23 00:03:03 +02:00
|
|
|
if (permissions.isNotEmpty) {
|
2023-12-23 20:53:31 +01:00
|
|
|
commands.writeln('chmod $permissions $filePath');
|
2023-06-23 00:03:03 +02:00
|
|
|
}
|
2023-12-23 19:02:09 +01:00
|
|
|
|
2023-06-23 00:03:03 +02:00
|
|
|
if (ownerGroup.isNotEmpty) {
|
2023-12-23 20:53:31 +01:00
|
|
|
commands.writeln('chown $ownerGroup $filePath');
|
2023-06-23 00:03:03 +02:00
|
|
|
}
|
2023-12-23 19:02:09 +01:00
|
|
|
|
2023-06-23 00:03:03 +02:00
|
|
|
if (seLinux.isNotEmpty) {
|
2023-12-23 20:53:31 +01:00
|
|
|
commands.writeln('chcon $seLinux $filePath');
|
2023-06-23 00:03:03 +02:00
|
|
|
}
|
2023-12-23 19:02:09 +01:00
|
|
|
|
|
|
|
await Root.exec(
|
|
|
|
cmd: commands.toString(),
|
|
|
|
);
|
2023-06-23 00:03:03 +02:00
|
|
|
} on Exception catch (e) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
print(e);
|
|
|
|
}
|
2022-09-14 10:59:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-25 01:51:47 +02:00
|
|
|
Future<bool> isAppInstalled(String packageName) async {
|
|
|
|
if (packageName.isNotEmpty) {
|
2023-06-23 00:03:03 +02:00
|
|
|
return fileExists('$_serviceDDirPath/$packageName.sh');
|
2022-08-25 01:51:47 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<String>> getInstalledApps() async {
|
2023-07-14 12:55:04 +02:00
|
|
|
final List<String> apps = List.empty(growable: true);
|
2022-08-14 20:40:34 +02:00
|
|
|
try {
|
2023-12-23 20:53:31 +01:00
|
|
|
final String? res = await Root.exec(cmd: 'ls $_revancedDirPath');
|
2023-06-23 00:03:03 +02:00
|
|
|
if (res != null) {
|
|
|
|
final List<String> list = res.split('\n');
|
|
|
|
list.removeWhere((pack) => pack.isEmpty);
|
|
|
|
apps.addAll(list.map((pack) => pack.trim()).toList());
|
|
|
|
}
|
2023-03-05 10:12:46 +01:00
|
|
|
} on Exception catch (e) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
print(e);
|
|
|
|
}
|
2022-08-14 20:40:34 +02:00
|
|
|
}
|
2023-06-23 00:03:03 +02:00
|
|
|
return apps;
|
2022-08-14 20:40:34 +02:00
|
|
|
}
|
|
|
|
|
2023-12-23 19:02:09 +01:00
|
|
|
Future<void> uninstall(String packageName) async {
|
2022-08-15 04:31:36 +02:00
|
|
|
await Root.exec(
|
2023-12-23 19:02:09 +01:00
|
|
|
cmd: '''
|
|
|
|
grep $packageName /proc/mounts | while read -r line; do echo \$line | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -l; done
|
|
|
|
rm -rf $_revancedDirPath/$packageName $_serviceDDirPath/$packageName.sh
|
|
|
|
''',
|
2022-08-15 04:31:36 +02:00
|
|
|
);
|
2023-12-23 04:16:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> removeOrphanedFiles() async {
|
|
|
|
await Root.exec(
|
2024-01-25 18:24:43 +01:00
|
|
|
cmd: 'find $_revancedDirPath -type f -name original.apk -delete',
|
2022-08-15 04:31:36 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-12-23 19:02:09 +01:00
|
|
|
Future<bool> install(
|
2022-08-14 20:40:34 +02:00
|
|
|
String packageName,
|
|
|
|
String originalFilePath,
|
|
|
|
String patchedFilePath,
|
|
|
|
) async {
|
|
|
|
try {
|
2022-09-14 11:34:16 +02:00
|
|
|
await setPermissions(
|
|
|
|
'0755',
|
|
|
|
'shell:shell',
|
|
|
|
'',
|
2023-06-23 00:03:03 +02:00
|
|
|
'$_revancedDirPath/$packageName',
|
2022-09-14 11:34:16 +02:00
|
|
|
);
|
2023-12-23 19:02:09 +01:00
|
|
|
await installPatchedApk(packageName, patchedFilePath);
|
2022-09-13 17:54:43 +02:00
|
|
|
await installServiceDScript(packageName);
|
2023-12-23 19:02:09 +01:00
|
|
|
await runMountScript(packageName);
|
2022-08-14 20:40:34 +02:00
|
|
|
return true;
|
2023-03-05 10:12:46 +01:00
|
|
|
} on Exception catch (e) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
print(e);
|
|
|
|
}
|
2022-08-14 20:40:34 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-15 04:31:36 +02:00
|
|
|
Future<void> installServiceDScript(String packageName) async {
|
2023-08-09 22:57:09 +02:00
|
|
|
await Root.exec(
|
2023-12-23 20:53:31 +01:00
|
|
|
cmd: 'mkdir -p $_serviceDDirPath',
|
2023-08-09 22:57:09 +02:00
|
|
|
);
|
2023-12-23 19:01:26 +01:00
|
|
|
final String mountScript = '''
|
2023-12-23 04:16:28 +01:00
|
|
|
#!/system/bin/sh
|
2023-12-31 01:32:04 +01:00
|
|
|
# Mount using Magisk mirror, if available.
|
2023-12-31 22:32:57 +01:00
|
|
|
MAGISKTMP="\$( magisk --path )" || MAGISKTMP=/sbin
|
|
|
|
MIRROR="\$MAGISKTMP/.magisk/mirror"
|
|
|
|
if [ ! -f \$MIRROR ]; then
|
2023-12-31 01:32:04 +01:00
|
|
|
MIRROR=""
|
|
|
|
fi
|
2022-08-15 04:31:36 +02:00
|
|
|
|
2023-12-23 04:16:28 +01:00
|
|
|
until [ "\$(getprop sys.boot_completed)" = 1 ]; do sleep 3; done
|
|
|
|
until [ -d "/sdcard/Android" ]; do sleep 1; done
|
|
|
|
|
2023-12-23 19:02:09 +01:00
|
|
|
# Unmount any existing installation to prevent multiple unnecessary mounts.
|
|
|
|
grep $packageName /proc/mounts | while read -r line; do echo \$line | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -l; done
|
|
|
|
|
2023-12-23 04:16:28 +01:00
|
|
|
base_path=$_revancedDirPath/$packageName/base.apk
|
2023-12-23 19:01:26 +01:00
|
|
|
stock_path=\$(pm path $packageName | grep base | sed "s/package://g" )
|
2023-12-23 04:16:28 +01:00
|
|
|
|
|
|
|
chcon u:object_r:apk_data_file:s0 \$base_path
|
|
|
|
mount -o bind \$MIRROR\$base_path \$stock_path
|
|
|
|
|
2023-12-23 19:01:26 +01:00
|
|
|
# Kill the app to force it to restart the mounted APK in case it is already running
|
2023-12-23 04:16:28 +01:00
|
|
|
am force-stop $packageName
|
|
|
|
'''
|
2023-12-23 19:01:26 +01:00
|
|
|
.trimMultilineString();
|
2023-12-23 04:16:28 +01:00
|
|
|
final String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
2022-08-15 04:31:36 +02:00
|
|
|
await Root.exec(
|
2023-12-23 19:01:26 +01:00
|
|
|
cmd: 'echo \'$mountScript\' > "$scriptFilePath"',
|
2022-08-15 04:31:36 +02:00
|
|
|
);
|
2022-09-14 10:59:56 +02:00
|
|
|
await setPermissions('0744', '', '', scriptFilePath);
|
2022-08-14 20:40:34 +02:00
|
|
|
}
|
|
|
|
|
2023-12-23 19:02:09 +01:00
|
|
|
Future<void> installPatchedApk(
|
2023-12-28 22:17:25 +01:00
|
|
|
String packageName, String patchedFilePath,) async {
|
2023-06-23 00:03:03 +02:00
|
|
|
final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk';
|
2022-08-15 04:31:36 +02:00
|
|
|
await Root.exec(
|
2023-12-23 19:02:09 +01:00
|
|
|
cmd: '''
|
2023-12-23 20:53:31 +01:00
|
|
|
mkdir -p $_revancedDirPath/$packageName
|
|
|
|
cp "$patchedFilePath" $newPatchedFilePath
|
2023-12-23 19:02:09 +01:00
|
|
|
''',
|
2022-08-15 04:31:36 +02:00
|
|
|
);
|
2022-09-14 10:59:56 +02:00
|
|
|
await setPermissions(
|
|
|
|
'0644',
|
|
|
|
'system:system',
|
|
|
|
'u:object_r:apk_data_file:s0',
|
|
|
|
newPatchedFilePath,
|
2022-08-15 04:31:36 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-12-23 19:02:09 +01:00
|
|
|
Future<void> runMountScript(
|
2023-12-23 04:47:12 +01:00
|
|
|
String packageName,
|
|
|
|
) async {
|
2023-12-23 19:02:09 +01:00
|
|
|
await Root.exec(cmd: '.$_serviceDDirPath/$packageName.sh');
|
2022-09-13 17:54:43 +02:00
|
|
|
}
|
2023-06-23 00:03:03 +02:00
|
|
|
|
|
|
|
Future<bool> fileExists(String path) async {
|
|
|
|
try {
|
|
|
|
final String? res = await Root.exec(
|
|
|
|
cmd: 'ls $path',
|
|
|
|
);
|
|
|
|
return res != null && res.isNotEmpty;
|
|
|
|
} on Exception catch (e) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
print(e);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2022-08-14 20:40:34 +02:00
|
|
|
}
|
2023-12-23 19:01:26 +01:00
|
|
|
|
|
|
|
// Remove leading spaces manually until
|
|
|
|
// https://github.com/dart-lang/language/issues/559 is closed
|
|
|
|
extension StringExtension on String {
|
|
|
|
String trimMultilineString() =>
|
|
|
|
split('\n').map((line) => line.trim()).join('\n').trim();
|
|
|
|
}
|