mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-06-12 20:57:36 +02:00
feat: Improve installation robustness (#1528)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de> Co-authored-by: Ushie <ushiekane@gmail.com> Co-authored-by: Dhruvan Bhalara <53393418+dhruvanbhalara@users.noreply.github.com> Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
This commit is contained in:
@ -72,4 +72,3 @@ class DownloadManager {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,22 +3,24 @@ import 'dart:io';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:install_plugin/install_plugin.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/models/patched_application.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/root_api.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
@lazySingleton
|
||||
class PatcherAPI {
|
||||
static const patcherChannel =
|
||||
MethodChannel('app.revanced.manager.flutter/patcher');
|
||||
MethodChannel('app.revanced.manager.flutter/patcher');
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final RootAPI _rootAPI = RootAPI();
|
||||
late Directory _dataDir;
|
||||
@ -79,7 +81,8 @@ class PatcherAPI {
|
||||
}
|
||||
|
||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
||||
bool showUniversalPatches,) async {
|
||||
bool showUniversalPatches,
|
||||
) async {
|
||||
final List<ApplicationWithIcon> filteredApps = [];
|
||||
final bool allAppsIncluded =
|
||||
_universalPatches.isNotEmpty && showUniversalPatches;
|
||||
@ -121,11 +124,11 @@ class PatcherAPI {
|
||||
final List<Patch> patches = _patches
|
||||
.where(
|
||||
(patch) =>
|
||||
patch.compatiblePackages.isEmpty ||
|
||||
!patch.name.contains('settings') &&
|
||||
patch.compatiblePackages
|
||||
.any((pack) => pack.name == packageName),
|
||||
)
|
||||
patch.compatiblePackages.isEmpty ||
|
||||
!patch.name.contains('settings') &&
|
||||
patch.compatiblePackages
|
||||
.any((pack) => pack.name == packageName),
|
||||
)
|
||||
.toList();
|
||||
if (!_managerAPI.areUniversalPatchesEnabled()) {
|
||||
filteredPatches[packageName] = patches
|
||||
@ -137,22 +140,27 @@ class PatcherAPI {
|
||||
return filteredPatches[packageName];
|
||||
}
|
||||
|
||||
Future<List<Patch>> getAppliedPatches(List<String> appliedPatches,) async {
|
||||
Future<List<Patch>> getAppliedPatches(
|
||||
List<String> appliedPatches,
|
||||
) async {
|
||||
return _patches
|
||||
.where((patch) => appliedPatches.contains(patch.name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> runPatcher(String packageName,
|
||||
String apkFilePath,
|
||||
List<Patch> selectedPatches,) async {
|
||||
Future<void> runPatcher(
|
||||
String packageName,
|
||||
String apkFilePath,
|
||||
List<Patch> selectedPatches,
|
||||
) async {
|
||||
final File? integrationsFile = await _managerAPI.downloadIntegrations();
|
||||
final Map<String, Map<String, dynamic>> options = {};
|
||||
for (final patch in selectedPatches) {
|
||||
if (patch.options.isNotEmpty) {
|
||||
final Map<String, dynamic> patchOptions = {};
|
||||
for (final option in patch.options) {
|
||||
final patchOption = _managerAPI.getPatchOption(packageName, patch.name, option.key);
|
||||
final patchOption =
|
||||
_managerAPI.getPatchOption(packageName, patch.name, option.key);
|
||||
if (patchOption != null) {
|
||||
patchOptions[patchOption.key] = patchOption.value;
|
||||
}
|
||||
@ -194,133 +202,308 @@ class PatcherAPI {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stopPatcher() async {
|
||||
try {
|
||||
await patcherChannel.invokeMethod('stopPatcher');
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
Future<void> stopPatcher() async {
|
||||
try {
|
||||
await patcherChannel.invokeMethod('stopPatcher');
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
|
||||
if (outFile != null) {
|
||||
try {
|
||||
if (patchedApp.isRooted) {
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
if (hasRootPermissions) {
|
||||
return _rootAPI.installApp(
|
||||
patchedApp.packageName,
|
||||
patchedApp.apkFilePath,
|
||||
outFile!.path,
|
||||
);
|
||||
Future<int> installPatchedFile(
|
||||
BuildContext context,
|
||||
PatchedApplication patchedApp,
|
||||
) async {
|
||||
if (outFile != null) {
|
||||
_managerAPI.ctx = context;
|
||||
try {
|
||||
if (patchedApp.isRooted) {
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
final packageVersion = await DeviceApps.getApp(patchedApp.packageName)
|
||||
.then((app) => app?.versionName);
|
||||
if (!hasRootPermissions) {
|
||||
installErrorDialog(1);
|
||||
} else if (packageVersion == null) {
|
||||
installErrorDialog(1.2);
|
||||
} else if (packageVersion == patchedApp.version) {
|
||||
return await _rootAPI.installApp(
|
||||
patchedApp.packageName,
|
||||
patchedApp.apkFilePath,
|
||||
outFile!.path,
|
||||
)
|
||||
? 0
|
||||
: 1;
|
||||
} else {
|
||||
installErrorDialog(1.1);
|
||||
}
|
||||
} else {
|
||||
if (await _rootAPI.hasRootPermissions()) {
|
||||
await _rootAPI.unmount(patchedApp.packageName);
|
||||
}
|
||||
if (context.mounted) {
|
||||
return await installApk(
|
||||
context,
|
||||
outFile!.path,
|
||||
);
|
||||
}
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
Future<int> installApk(
|
||||
BuildContext context,
|
||||
String apkPath,
|
||||
) async {
|
||||
try {
|
||||
final status = await patcherChannel.invokeMethod('installApk', {
|
||||
'apkPath': apkPath,
|
||||
});
|
||||
final int statusCode = status['status'];
|
||||
final String message = status['message'];
|
||||
final bool hasExtra =
|
||||
message.contains('INSTALL_FAILED_VERIFICATION_FAILURE') ||
|
||||
message.contains('INSTALL_FAILED_VERSION_DOWNGRADE');
|
||||
if (statusCode == 0 || (statusCode == 3 && !hasExtra)) {
|
||||
return statusCode;
|
||||
} else {
|
||||
final install = await InstallPlugin.installApk(outFile!.path);
|
||||
return install['isSuccess'];
|
||||
_managerAPI.ctx = context;
|
||||
return await installErrorDialog(
|
||||
statusCode,
|
||||
status,
|
||||
hasExtra,
|
||||
);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return false;
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void exportPatchedFile(String appName, String version) {
|
||||
try {
|
||||
if (outFile != null) {
|
||||
final String newName = _getFileName(appName, version);
|
||||
FlutterFileDialog.saveFile(
|
||||
params: SaveFileDialogParams(
|
||||
sourceFilePath: outFile!.path,
|
||||
fileName: newName,
|
||||
mimeTypesFilter: ['application/vnd.android.package-archive'],
|
||||
),
|
||||
);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sharePatchedFile(String appName, String version) {
|
||||
try {
|
||||
if (outFile != null) {
|
||||
final String newName = _getFileName(appName, version);
|
||||
final int lastSeparator = outFile!.path.lastIndexOf('/');
|
||||
final String newPath =
|
||||
outFile!.path.substring(0, lastSeparator + 1) + newName;
|
||||
final File shareFile = outFile!.copySync(newPath);
|
||||
Share.shareXFiles([XFile(shareFile.path)]);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _getFileName(String appName, String version) {
|
||||
final String patchVersion = _managerAPI.patchesVersion!;
|
||||
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
||||
final String newName = '$prefix-revanced_v$version-patches_$patchVersion.apk';
|
||||
return newName;
|
||||
}
|
||||
|
||||
Future<void> exportPatcherLog(String logs) async {
|
||||
final Directory appCache = await getTemporaryDirectory();
|
||||
final Directory logDir = Directory('${appCache.path}/logs');
|
||||
logDir.createSync();
|
||||
final String dateTime = DateTime.now()
|
||||
.toIso8601String()
|
||||
.replaceAll('-', '')
|
||||
.replaceAll(':', '')
|
||||
.replaceAll('T', '')
|
||||
.replaceAll('.', '');
|
||||
final String fileName = 'revanced-manager_patcher_$dateTime.txt';
|
||||
final File log = File('${logDir.path}/$fileName');
|
||||
log.writeAsStringSync(logs);
|
||||
FlutterFileDialog.saveFile(
|
||||
params:SaveFileDialogParams(
|
||||
sourceFilePath: log.path,
|
||||
fileName: fileName,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String getSuggestedVersion(String packageName) {
|
||||
final Map<String, int> versions = {};
|
||||
for (final Patch patch in _patches) {
|
||||
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||
(pack) => pack.name == packageName,
|
||||
Future<int> installErrorDialog(
|
||||
num statusCode, [
|
||||
status,
|
||||
bool hasExtra = false,
|
||||
]) async {
|
||||
final String statusValue = InstallStatus.byCode(
|
||||
hasExtra ? double.parse('$statusCode.1') : statusCode,
|
||||
);
|
||||
if (package != null) {
|
||||
for (final String version in package.versions) {
|
||||
versions.update(
|
||||
version,
|
||||
(value) => versions[version]! + 1,
|
||||
ifAbsent: () => 1,
|
||||
bool cleanInstall = false;
|
||||
final bool isFixable = statusCode == 4 || statusCode == 5;
|
||||
await showDialog(
|
||||
context: _managerAPI.ctx!,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
title: I18nText('installErrorDialog.$statusValue'),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
I18nText(
|
||||
'installErrorDialog.${statusValue}_description',
|
||||
translationParams: statusCode == 2
|
||||
? {
|
||||
'packageName': status['otherPackageName'],
|
||||
}
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: (status == null)
|
||||
? <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
]
|
||||
: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: !isFixable,
|
||||
label: I18nText('cancelButton'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
if (isFixable)
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
onPressed: () async {
|
||||
final int response = await patcherChannel.invokeMethod(
|
||||
'uninstallApp',
|
||||
{'packageName': status['packageName']},
|
||||
);
|
||||
if (response == 0 && context.mounted) {
|
||||
cleanInstall = true;
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return cleanInstall ? 10 : 1;
|
||||
}
|
||||
|
||||
void exportPatchedFile(String appName, String version) {
|
||||
try {
|
||||
if (outFile != null) {
|
||||
final String newName = _getFileName(appName, version);
|
||||
FlutterFileDialog.saveFile(
|
||||
params: SaveFileDialogParams(
|
||||
sourceFilePath: outFile!.path,
|
||||
fileName: newName,
|
||||
mimeTypesFilter: ['application/vnd.android.package-archive'],
|
||||
),
|
||||
);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (versions.isNotEmpty) {
|
||||
final entries = versions.entries.toList()
|
||||
..sort((a, b) => a.value.compareTo(b.value));
|
||||
versions
|
||||
..clear()
|
||||
..addEntries(entries);
|
||||
versions.removeWhere((key, value) => value != versions.values.last);
|
||||
return (versions.keys.toList()
|
||||
..sort()).last;
|
||||
|
||||
void sharePatchedFile(String appName, String version) {
|
||||
try {
|
||||
if (outFile != null) {
|
||||
final String newName = _getFileName(appName, version);
|
||||
final int lastSeparator = outFile!.path.lastIndexOf('/');
|
||||
final String newPath =
|
||||
outFile!.path.substring(0, lastSeparator + 1) + newName;
|
||||
final File shareFile = outFile!.copySync(newPath);
|
||||
Share.shareXFiles([XFile(shareFile.path)]);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}}
|
||||
|
||||
String _getFileName(String appName, String version) {
|
||||
final String patchVersion = _managerAPI.patchesVersion!;
|
||||
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
||||
final String newName =
|
||||
'$prefix-revanced_v$version-patches_$patchVersion.apk';
|
||||
return newName;
|
||||
}
|
||||
|
||||
Future<void> exportPatcherLog(String logs) async {
|
||||
final Directory appCache = await getTemporaryDirectory();
|
||||
final Directory logDir = Directory('${appCache.path}/logs');
|
||||
logDir.createSync();
|
||||
final String dateTime = DateTime.now()
|
||||
.toIso8601String()
|
||||
.replaceAll('-', '')
|
||||
.replaceAll(':', '')
|
||||
.replaceAll('T', '')
|
||||
.replaceAll('.', '');
|
||||
final String fileName = 'revanced-manager_patcher_$dateTime.txt';
|
||||
final File log = File('${logDir.path}/$fileName');
|
||||
log.writeAsStringSync(logs);
|
||||
FlutterFileDialog.saveFile(
|
||||
params: SaveFileDialogParams(
|
||||
sourceFilePath: log.path,
|
||||
fileName: fileName,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String getSuggestedVersion(String packageName) {
|
||||
final Map<String, int> versions = {};
|
||||
for (final Patch patch in _patches) {
|
||||
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||
(pack) => pack.name == packageName,
|
||||
);
|
||||
if (package != null) {
|
||||
for (final String version in package.versions) {
|
||||
versions.update(
|
||||
version,
|
||||
(value) => versions[version]! + 1,
|
||||
ifAbsent: () => 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (versions.isNotEmpty) {
|
||||
final entries = versions.entries.toList()
|
||||
..sort((a, b) => a.value.compareTo(b.value));
|
||||
versions
|
||||
..clear()
|
||||
..addEntries(entries);
|
||||
versions.removeWhere((key, value) => value != versions.values.last);
|
||||
return (versions.keys.toList()..sort()).last;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
enum InstallStatus {
|
||||
mountNoRoot(1),
|
||||
mountVersionMismatch(1.1),
|
||||
mountMissingInstallation(1.2),
|
||||
|
||||
statusFailureBlocked(2),
|
||||
installFailedVerificationFailure(3.1),
|
||||
statusFailureInvalid(4),
|
||||
installFailedVersionDowngrade(4.1),
|
||||
statusFailureConflict(5),
|
||||
statusFailureStorage(6),
|
||||
statusFailureIncompatible(7),
|
||||
statusFailureTimeout(8);
|
||||
|
||||
const InstallStatus(this.statusCode);
|
||||
final double statusCode;
|
||||
|
||||
static String byCode(num code) {
|
||||
try {
|
||||
return InstallStatus.values
|
||||
.firstWhere((flag) => flag.statusCode == code)
|
||||
.status;
|
||||
} catch (e) {
|
||||
return 'status_unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension InstallStatusExtension on InstallStatus {
|
||||
String get status {
|
||||
switch (this) {
|
||||
case InstallStatus.mountNoRoot:
|
||||
return 'mount_no_root';
|
||||
case InstallStatus.mountVersionMismatch:
|
||||
return 'mount_version_mismatch';
|
||||
case InstallStatus.mountMissingInstallation:
|
||||
return 'mount_missing_installation';
|
||||
case InstallStatus.statusFailureBlocked:
|
||||
return 'status_failure_blocked';
|
||||
case InstallStatus.installFailedVerificationFailure:
|
||||
return 'install_failed_verification_failure';
|
||||
case InstallStatus.statusFailureInvalid:
|
||||
return 'status_failure_invalid';
|
||||
case InstallStatus.installFailedVersionDowngrade:
|
||||
return 'install_failed_version_downgrade';
|
||||
case InstallStatus.statusFailureConflict:
|
||||
return 'status_failure_conflict';
|
||||
case InstallStatus.statusFailureStorage:
|
||||
return 'status_failure_storage';
|
||||
case InstallStatus.statusFailureIncompatible:
|
||||
return 'status_failure_incompatible';
|
||||
case InstallStatus.statusFailureTimeout:
|
||||
return 'status_failure_timeout';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:root/root.dart';
|
||||
|
||||
class RootAPI {
|
||||
// TODO(ponces): remove in the future, keep it for now during migration.
|
||||
final String _revancedOldDirPath = '/data/local/tmp/revanced-manager';
|
||||
final String _revancedDirPath = '/data/adb/revanced';
|
||||
// TODO(aAbed): remove in the future, keep it for now during migration.
|
||||
final String _postFsDataDirPath = '/data/adb/post-fs-data.d';
|
||||
|
||||
final String _revancedDirPath = '/data/adb/revanced';
|
||||
final String _serviceDDirPath = '/data/adb/service.d';
|
||||
|
||||
Future<bool> isRooted() async {
|
||||
@ -75,7 +75,7 @@ class RootAPI {
|
||||
Future<List<String>> getInstalledApps() async {
|
||||
final List<String> apps = List.empty(growable: true);
|
||||
try {
|
||||
String? res = await Root.exec(
|
||||
final String? res = await Root.exec(
|
||||
cmd: 'ls "$_revancedDirPath"',
|
||||
);
|
||||
if (res != null) {
|
||||
@ -83,15 +83,6 @@ class RootAPI {
|
||||
list.removeWhere((pack) => pack.isEmpty);
|
||||
apps.addAll(list.map((pack) => pack.trim()).toList());
|
||||
}
|
||||
// TODO(ponces): remove in the future, keep it for now during migration.
|
||||
res = await Root.exec(
|
||||
cmd: 'ls "$_revancedOldDirPath"',
|
||||
);
|
||||
if (res != null) {
|
||||
final List<String> list = res.split('\n');
|
||||
list.removeWhere((pack) => pack.isEmpty);
|
||||
apps.addAll(list.map((pack) => pack.trim()).toList());
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
@ -100,16 +91,9 @@ class RootAPI {
|
||||
return apps;
|
||||
}
|
||||
|
||||
Future<void> deleteApp(String packageName, String originalFilePath) async {
|
||||
Future<void> unmount(String packageName) async {
|
||||
await Root.exec(
|
||||
cmd: 'am force-stop "$packageName"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'su -mm -c "umount -l $originalFilePath"',
|
||||
);
|
||||
// TODO(ponces): remove in the future, keep it for now during migration.
|
||||
await Root.exec(
|
||||
cmd: 'rm -rf "$_revancedOldDirPath/$packageName"',
|
||||
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',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'rm -rf "$_revancedDirPath/$packageName"',
|
||||
@ -117,8 +101,21 @@ class RootAPI {
|
||||
await Root.exec(
|
||||
cmd: 'rm -rf "$_serviceDDirPath/$packageName.sh"',
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(aAbed): remove in the future, keep it for now during migration.
|
||||
Future<void> removeOrphanedFiles() async {
|
||||
await Root.exec(
|
||||
cmd: 'rm -rf "$_postFsDataDirPath/$packageName.sh"',
|
||||
cmd: '''
|
||||
find "$_revancedDirPath" -type f -name original.apk -delete
|
||||
for file in "$_serviceDDirPath"/*; do
|
||||
filename=\$(basename "\$file")
|
||||
if [ -f "$_postFsDataDirPath/\$filename" ]; then
|
||||
rm "$_postFsDataDirPath/\$filename"
|
||||
fi
|
||||
done
|
||||
'''
|
||||
.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -128,7 +125,6 @@ class RootAPI {
|
||||
String patchedFilePath,
|
||||
) async {
|
||||
try {
|
||||
await deleteApp(packageName, originalFilePath);
|
||||
await Root.exec(
|
||||
cmd: 'mkdir -p "$_revancedDirPath/$packageName"',
|
||||
);
|
||||
@ -138,11 +134,9 @@ class RootAPI {
|
||||
'',
|
||||
'$_revancedDirPath/$packageName',
|
||||
);
|
||||
await saveOriginalFilePath(packageName, originalFilePath);
|
||||
await installServiceDScript(packageName);
|
||||
await installPostFsDataScript(packageName);
|
||||
await installApk(packageName, patchedFilePath);
|
||||
await mountApk(packageName, originalFilePath);
|
||||
await mountApk(packageName);
|
||||
return true;
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
@ -156,26 +150,25 @@ class RootAPI {
|
||||
await Root.exec(
|
||||
cmd: 'mkdir -p "$_serviceDDirPath"',
|
||||
);
|
||||
final String content = '#!/system/bin/sh\n'
|
||||
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
|
||||
'base_path=$_revancedDirPath/$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);
|
||||
}
|
||||
final String content = '''
|
||||
#!/system/bin/sh
|
||||
MAGISKTMP="\$(magisk --path)" || MAGISKTMP=/sbin
|
||||
MIRROR="\$MAGISKTMP/.magisk/mirror"
|
||||
|
||||
Future<void> installPostFsDataScript(String packageName) async {
|
||||
await Root.exec(
|
||||
cmd: 'mkdir -p "$_postFsDataDirPath"',
|
||||
);
|
||||
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';
|
||||
until [ "\$(getprop sys.boot_completed)" = 1 ]; do sleep 3; done
|
||||
until [ -d "/sdcard/Android" ]; do sleep 1; done
|
||||
|
||||
base_path=$_revancedDirPath/$packageName/base.apk
|
||||
stock_path=\$(pm path $packageName | grep base | sed 's/package://g' )
|
||||
|
||||
chcon u:object_r:apk_data_file:s0 \$base_path
|
||||
mount -o bind \$MIRROR\$base_path \$stock_path
|
||||
|
||||
# Kill the app to force it to restart the mounted APK in case it's already running
|
||||
am force-stop $packageName
|
||||
'''
|
||||
.trim();
|
||||
final String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
||||
await Root.exec(
|
||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
||||
);
|
||||
@ -195,49 +188,12 @@ class RootAPI {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> mountApk(String packageName, String originalFilePath) async {
|
||||
final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk';
|
||||
Future<void> mountApk(String packageName,) async {
|
||||
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<void> saveOriginalFilePath(
|
||||
String packageName,
|
||||
String originalFilePath,
|
||||
) async {
|
||||
final String originalRootPath =
|
||||
'$_revancedDirPath/$packageName/original.apk';
|
||||
await Root.exec(
|
||||
cmd: 'mkdir -p "$_revancedDirPath/$packageName"',
|
||||
);
|
||||
await setPermissions(
|
||||
'0755',
|
||||
'shell:shell',
|
||||
'',
|
||||
'$_revancedDirPath/$packageName',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'cp "$originalFilePath" "$originalRootPath"',
|
||||
);
|
||||
await setPermissions(
|
||||
'0644',
|
||||
'shell:shell',
|
||||
'u:object_r:apk_data_file:s0',
|
||||
originalFilePath,
|
||||
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
|
||||
.$_serviceDDirPath/$packageName.sh
|
||||
'''.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user