mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
6829d3cdea
Improve code readability & additional refactoring Commits: chore: exclude generated from analyzer refactor: add SharedPreferences to locator refactor: access shared pref from locator, and code refactor refactor: remove unwanted `await` refactor: remove `const` from `CacheConfig`
234 lines
6.7 KiB
Dart
234 lines
6.7 KiB
Dart
import 'package:root/root.dart';
|
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
|
|
class RootAPI {
|
|
final String _managerDirPath = '/data/local/tmp/revanced-manager';
|
|
final String _postFsDataDirPath = '/data/adb/post-fs-data.d';
|
|
final String _serviceDDirPath = '/data/adb/service.d';
|
|
|
|
Future<bool> isRooted() async {
|
|
try {
|
|
final bool? isRooted = await Root.isRootAvailable();
|
|
return isRooted != null && isRooted;
|
|
} on Exception catch (e, s) {
|
|
Sentry.captureException(e, stackTrace: s).ignore();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<bool> hasRootPermissions() async {
|
|
try {
|
|
bool? isRooted = await Root.isRootAvailable();
|
|
if (isRooted != null && isRooted) {
|
|
isRooted = await Root.isRooted();
|
|
return isRooted != null && isRooted;
|
|
}
|
|
return false;
|
|
} on Exception catch (e, s) {
|
|
Sentry.captureException(e, stackTrace: s).ignore();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<void> setPermissions(
|
|
String permissions,
|
|
ownerGroup,
|
|
seLinux,
|
|
String filePath,
|
|
) async {
|
|
if (permissions.isNotEmpty) {
|
|
await Root.exec(
|
|
cmd: 'chmod $permissions "$filePath"',
|
|
);
|
|
}
|
|
if (ownerGroup.isNotEmpty) {
|
|
await Root.exec(
|
|
cmd: 'chown $ownerGroup "$filePath"',
|
|
);
|
|
}
|
|
if (seLinux.isNotEmpty) {
|
|
await Root.exec(
|
|
cmd: 'chcon $seLinux "$filePath"',
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<bool> isAppInstalled(String packageName) async {
|
|
if (packageName.isNotEmpty) {
|
|
String? res = await Root.exec(
|
|
cmd: 'ls "$_managerDirPath/$packageName"',
|
|
);
|
|
if (res != null && res.isNotEmpty) {
|
|
res = await Root.exec(
|
|
cmd: 'ls "$_serviceDDirPath/$packageName.sh"',
|
|
);
|
|
return res != null && res.isNotEmpty;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Future<List<String>> getInstalledApps() async {
|
|
try {
|
|
final String? res = await Root.exec(
|
|
cmd: 'ls "$_managerDirPath"',
|
|
);
|
|
if (res != null) {
|
|
final List<String> apps = res.split('\n');
|
|
apps.removeWhere((pack) => pack.isEmpty);
|
|
return apps.map((pack) => pack.trim()).toList();
|
|
}
|
|
} on Exception catch (e, s) {
|
|
Sentry.captureException(e, stackTrace: s).ignore();
|
|
return List.empty();
|
|
}
|
|
return List.empty();
|
|
}
|
|
|
|
Future<void> deleteApp(String packageName, String originalFilePath) async {
|
|
await Root.exec(
|
|
cmd: 'am force-stop "$packageName"',
|
|
);
|
|
await Root.exec(
|
|
cmd: 'su -mm -c "umount -l $originalFilePath"',
|
|
);
|
|
await Root.exec(
|
|
cmd: 'rm -rf "$_managerDirPath/$packageName"',
|
|
);
|
|
await Root.exec(
|
|
cmd: 'rm -rf "$_serviceDDirPath/$packageName.sh"',
|
|
);
|
|
await Root.exec(
|
|
cmd: 'rm -rf "$_postFsDataDirPath/$packageName.sh"',
|
|
);
|
|
}
|
|
|
|
Future<bool> installApp(
|
|
String packageName,
|
|
String originalFilePath,
|
|
String patchedFilePath,
|
|
) async {
|
|
try {
|
|
await deleteApp(packageName, originalFilePath);
|
|
await Root.exec(
|
|
cmd: 'mkdir -p "$_managerDirPath/$packageName"',
|
|
);
|
|
await setPermissions(
|
|
'0755',
|
|
'shell:shell',
|
|
'',
|
|
'$_managerDirPath/$packageName',
|
|
);
|
|
await saveOriginalFilePath(packageName, originalFilePath);
|
|
await installServiceDScript(packageName);
|
|
await installPostFsDataScript(packageName);
|
|
await installApk(packageName, patchedFilePath);
|
|
await mountApk(packageName, originalFilePath);
|
|
return true;
|
|
} on Exception catch (e, s) {
|
|
Sentry.captureException(e, stackTrace: s).ignore();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<void> installServiceDScript(String packageName) async {
|
|
final String content = '#!/system/bin/sh\n'
|
|
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
|
|
'base_path=$_managerDirPath/$packageName/base.apk\n'
|
|
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
|
r'[ ! -z $stock_path ] && mount -o bind $base_path $stock_path';
|
|
final String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
|
await Root.exec(
|
|
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
|
);
|
|
await setPermissions('0744', '', '', scriptFilePath);
|
|
}
|
|
|
|
Future<void> installPostFsDataScript(String packageName) async {
|
|
final String content = '#!/system/bin/sh\n'
|
|
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
|
r'[ ! -z $stock_path ] && umount -l $stock_path';
|
|
final String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
|
|
await Root.exec(
|
|
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
|
);
|
|
await setPermissions('0744', '', '', scriptFilePath);
|
|
}
|
|
|
|
Future<void> installApk(String packageName, String patchedFilePath) async {
|
|
final String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
|
await Root.exec(
|
|
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
|
|
);
|
|
await setPermissions(
|
|
'0644',
|
|
'system:system',
|
|
'u:object_r:apk_data_file:s0',
|
|
newPatchedFilePath,
|
|
);
|
|
}
|
|
|
|
Future<void> mountApk(String packageName, String originalFilePath) async {
|
|
final String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
|
await Root.exec(
|
|
cmd: 'am force-stop "$packageName"',
|
|
);
|
|
await Root.exec(
|
|
cmd: 'su -mm -c "umount -l $originalFilePath"',
|
|
);
|
|
await Root.exec(
|
|
cmd: 'su -mm -c "mount -o bind $newPatchedFilePath $originalFilePath"',
|
|
);
|
|
}
|
|
|
|
Future<bool> isMounted(String packageName) async {
|
|
final String? res = await Root.exec(
|
|
cmd: 'cat /proc/mounts | grep $packageName',
|
|
);
|
|
return res != null && res.isNotEmpty;
|
|
}
|
|
|
|
Future<String> getOriginalFilePath(
|
|
String packageName,
|
|
String originalFilePath,
|
|
) async {
|
|
final bool isInstalled = await isAppInstalled(packageName);
|
|
if (isInstalled && await isMounted(packageName)) {
|
|
originalFilePath = '$_managerDirPath/$packageName/original.apk';
|
|
await setPermissions(
|
|
'0644',
|
|
'shell:shell',
|
|
'u:object_r:apk_data_file:s0',
|
|
originalFilePath,
|
|
);
|
|
}
|
|
return originalFilePath;
|
|
}
|
|
|
|
Future<void> saveOriginalFilePath(
|
|
String packageName,
|
|
String originalFilePath,
|
|
) async {
|
|
final String originalRootPath =
|
|
'$_managerDirPath/$packageName/original.apk';
|
|
await Root.exec(
|
|
cmd: 'mkdir -p "$_managerDirPath/$packageName"',
|
|
);
|
|
await setPermissions(
|
|
'0755',
|
|
'shell:shell',
|
|
'',
|
|
'$_managerDirPath/$packageName',
|
|
);
|
|
await Root.exec(
|
|
cmd: 'cp "$originalFilePath" "$originalRootPath"',
|
|
);
|
|
await setPermissions(
|
|
'0644',
|
|
'shell:shell',
|
|
'u:object_r:apk_data_file:s0',
|
|
originalFilePath,
|
|
);
|
|
}
|
|
}
|