feat: root installation (wip)

This commit is contained in:
Alberto Ponces 2022-08-14 19:40:34 +01:00
parent 6061d900ed
commit 9ce0f81a89
16 changed files with 231 additions and 69 deletions

View file

@ -70,6 +70,7 @@
"rootCheckerView": {
"widgetTitle": "Is your device rooted?",
"widgetDescription": "Don't know what's this or prefer to use non-root version? Just click button below!",
"grantPermission": "Grant Root Permission"
"grantPermission": "Grant Root Permission",
"grantedPermission": "Magisk permission granted: {isRooted}"
}
}

View file

@ -1,8 +1,10 @@
import 'package:revanced_manager/main.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_view.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
import 'package:revanced_manager/ui/views/contributors/contributors_view.dart';
import 'package:revanced_manager/ui/views/home/home_view.dart';
import 'package:revanced_manager/ui/views/installer/installer_view.dart';
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
@ -16,7 +18,7 @@ import 'package:stacked_themes/stacked_themes.dart';
@StackedApp(
routes: [
MaterialRoute(page: HomeView),
MaterialRoute(page: Navigation),
MaterialRoute(page: AppSelectorView),
MaterialRoute(page: PatchesSelectorView),
MaterialRoute(page: InstallerView),
@ -27,12 +29,16 @@ import 'package:stacked_themes/stacked_themes.dart';
dependencies: [
LazySingleton(classType: NavigationService),
LazySingleton(classType: PatcherAPI),
LazySingleton(classType: ManagerAPI),
LazySingleton(classType: RootAPI),
LazySingleton(classType: PatcherViewModel),
LazySingleton(classType: AppSelectorViewModel),
LazySingleton(classType: PatchesSelectorViewModel),
LazySingleton(classType: InstallerViewModel),
LazySingleton(
classType: ThemeService, resolveUsing: ThemeService.getInstance),
classType: ThemeService,
resolveUsing: ThemeService.getInstance,
),
],
)
class AppSetup {}

View file

@ -4,13 +4,15 @@
// StackedLocatorGenerator
// **************************************************************************
// ignore_for_file: public_member_api_docs
// ignore_for_file: public_member_api_docs, depend_on_referenced_packages
import 'package:stacked_core/stacked_core.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:stacked_themes/stacked_themes.dart';
import '../services/manager_api.dart';
import '../services/patcher_api.dart';
import '../services/root_api.dart';
import '../ui/views/app_selector/app_selector_viewmodel.dart';
import '../ui/views/installer/installer_viewmodel.dart';
import '../ui/views/patcher/patcher_viewmodel.dart';
@ -27,6 +29,8 @@ Future<void> setupLocator(
// Register dependencies
locator.registerLazySingleton(() => NavigationService());
locator.registerLazySingleton(() => PatcherAPI());
locator.registerLazySingleton(() => ManagerAPI());
locator.registerLazySingleton(() => RootAPI());
locator.registerLazySingleton(() => PatcherViewModel());
locator.registerLazySingleton(() => AppSelectorViewModel());
locator.registerLazySingleton(() => PatchesSelectorViewModel());

View file

@ -10,16 +10,16 @@ import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import '../main.dart';
import '../ui/views/app_selector/app_selector_view.dart';
import '../ui/views/contributors/contributors_view.dart';
import '../ui/views/home/home_view.dart';
import '../ui/views/installer/installer_view.dart';
import '../ui/views/patches_selector/patches_selector_view.dart';
import '../ui/views/root_checker/root_checker_view.dart';
import '../ui/views/settings/settings_view.dart';
class Routes {
static const String homeView = '/home-view';
static const String navigation = '/Navigation';
static const String appSelectorView = '/app-selector-view';
static const String patchesSelectorView = '/patches-selector-view';
static const String installerView = '/installer-view';
@ -27,7 +27,7 @@ class Routes {
static const String contributorsView = '/contributors-view';
static const String rootCheckerView = '/root-checker-view';
static const all = <String>{
homeView,
navigation,
appSelectorView,
patchesSelectorView,
installerView,
@ -41,7 +41,7 @@ class StackedRouter extends RouterBase {
@override
List<RouteDef> get routes => _routes;
final _routes = <RouteDef>[
RouteDef(Routes.homeView, page: HomeView),
RouteDef(Routes.navigation, page: Navigation),
RouteDef(Routes.appSelectorView, page: AppSelectorView),
RouteDef(Routes.patchesSelectorView, page: PatchesSelectorView),
RouteDef(Routes.installerView, page: InstallerView),
@ -52,9 +52,9 @@ class StackedRouter extends RouterBase {
@override
Map<Type, StackedRouteFactory> get pagesMap => _pagesMap;
final _pagesMap = <Type, StackedRouteFactory>{
HomeView: (data) {
Navigation: (data) {
return MaterialPageRoute<dynamic>(
builder: (context) => const HomeView(),
builder: (context) => const Navigation(),
settings: data,
);
},
@ -115,7 +115,7 @@ class InstallerViewArguments {
/// *************************************************************************
extension NavigatorStateExtension on NavigationService {
Future<dynamic> navigateToHomeView({
Future<dynamic> navigateToNavigation({
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
@ -123,7 +123,7 @@ extension NavigatorStateExtension on NavigationService {
transition,
}) async {
return navigateTo(
Routes.homeView,
Routes.navigation,
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,

View file

@ -8,7 +8,9 @@ import 'package:revanced_manager/main_viewmodel.dart';
import 'package:revanced_manager/theme.dart';
import 'package:revanced_manager/ui/views/home/home_view.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_view.dart';
import 'package:revanced_manager/ui/views/root_checker/root_checker_view.dart';
import 'package:revanced_manager/ui/views/settings/settings_view.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:stacked_themes/stacked_themes.dart';
@ -37,7 +39,20 @@ class MyApp extends StatelessWidget {
themeMode: themeMode,
navigatorKey: StackedService.navigatorKey,
onGenerateRoute: StackedRouter().onGenerateRoute,
home: const Navigation(),
home: FutureBuilder<Widget>(
future: _init(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return snapshot.data!;
} else {
return Center(
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.secondary,
),
);
}
},
),
localizationsDelegates: [
FlutterI18nDelegate(
translationLoader: FileTranslationLoader(
@ -51,6 +66,15 @@ class MyApp extends StatelessWidget {
),
);
}
Future<Widget> _init() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? isRooted = prefs.getBool('isRooted');
if (isRooted != null) {
return const Navigation();
}
return const RootCheckerView();
}
}
class Navigation extends StatelessWidget {

View file

@ -1,13 +0,0 @@
class ApplicationInfo {
final String name;
final String packageName;
final String version;
final String apkFilePath;
ApplicationInfo({
required this.name,
required this.packageName,
required this.version,
required this.apkFilePath,
});
}

View file

@ -0,0 +1,21 @@
import 'package:revanced_manager/models/patch.dart';
class PatchedApplication {
final String name;
final String packageName;
final String version;
final String apkFilePath;
final bool isRooted;
final bool isFromStorage;
final List<Patch> appliedPatches;
PatchedApplication({
required this.name,
required this.packageName,
required this.version,
required this.apkFilePath,
required this.isRooted,
required this.isFromStorage,
this.appliedPatches = const <Patch>[],
});
}

View file

@ -7,8 +7,8 @@ import 'package:revanced_manager/services/github_api.dart';
@lazySingleton
class ManagerAPI {
Dio dio = Dio();
GithubAPI githubAPI = GithubAPI();
final Dio dio = Dio();
final GithubAPI githubAPI = GithubAPI();
Future<String?> getPath() async {
final path = await p.getApplicationSupportDirectory();

View file

@ -6,9 +6,10 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart';
import 'package:path_provider/path_provider.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/application_info.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_api.dart';
import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
import 'package:revanced_manager/utils/string.dart';
import 'package:share_extend/share_extend.dart';
@ -17,9 +18,10 @@ import 'package:share_extend/share_extend.dart';
class PatcherAPI {
static const platform = MethodChannel('app.revanced.manager/patcher');
final GithubAPI githubAPI = GithubAPI();
final RootAPI rootAPI = RootAPI();
final List<ApplicationWithIcon> _filteredPackages = [];
final Map<String, List<Patch>> _filteredPatches = <String, List<Patch>>{};
bool isRoot = false;
Directory? _tmpDir;
Directory? _workDir;
Directory? _cacheDir;
File? _patchBundleFile;
@ -89,7 +91,9 @@ class PatcherAPI {
return _filteredPackages;
}
Future<List<Patch>?> getFilteredPatches(ApplicationInfo? selectedApp) async {
Future<List<Patch>?> getFilteredPatches(
PatchedApplication? selectedApp,
) async {
if (_patchBundleFile != null && selectedApp != null) {
if (_filteredPatches[selectedApp.packageName] == null ||
_filteredPatches[selectedApp.packageName]!.isEmpty) {
@ -146,8 +150,8 @@ class PatcherAPI {
try {
_integrations = await downloadIntegrations();
if (_integrations != null) {
Directory tmpDir = await getTemporaryDirectory();
_workDir = tmpDir.createTempSync('tmp-');
_tmpDir = await getTemporaryDirectory();
_workDir = _tmpDir!.createTempSync('tmp-');
_inputFile = File('${_workDir!.path}/base.apk');
_patchedFile = File('${_workDir!.path}/patched.apk');
_outFile = File('${_workDir!.path}/out.apk');
@ -256,15 +260,19 @@ class PatcherAPI {
return false;
}
Future<bool> installPatchedFile() async {
Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
if (_outFile != null) {
try {
if (isRoot) {
// TBD
if (patchedApp.isRooted && !patchedApp.isFromStorage) {
return rootAPI.installApp(
patchedApp.packageName,
patchedApp.apkFilePath,
_outFile!.path,
);
} else {
await AppInstaller.installApk(_outFile!.path);
return true;
}
return true;
} on Exception {
return false;
}
@ -280,11 +288,11 @@ class PatcherAPI {
bool sharePatchedFile(String appName, String version) {
if (_outFile != null) {
String path = _outFile!.parent.path;
String path = _tmpDir!.path;
String prefix = appName.toLowerCase().replaceAll(' ', '-');
String sharePath = '$path/$prefix-revanced_v$version.apk';
File share = _outFile!.copySync(sharePath);
ShareExtend.share(share.path, "file");
ShareExtend.share(share.path, 'file');
return true;
} else {
return false;

View file

@ -0,0 +1,85 @@
import 'dart:io';
import 'package:injectable/injectable.dart';
import 'package:root/root.dart';
@lazySingleton
class RootAPI {
final String managerDirPath = "/data/adb/revanced_manager";
final String postFsDataDirPath = "/data/adb/post-fs-data.d";
final String serviceDDirPath = "/data/adb/service.d";
bool deleteApp(String packageName) {
try {
File('$managerDirPath/$packageName.apk').deleteSync();
File('$serviceDDirPath/$packageName.sh').deleteSync();
File('$postFsDataDirPath/$packageName.sh').deleteSync();
return true;
} on Exception {
return false;
}
}
Future<bool> installApp(
String packageName,
String originalFilePath,
String patchedFilePath,
) async {
try {
Directory managerDir = Directory(managerDirPath);
managerDir.createSync();
String newPatchedFilePath = '$managerDirPath/$packageName.apk';
installServiceDScript(
packageName,
originalFilePath,
newPatchedFilePath,
);
installPostFsDataScript(
packageName,
originalFilePath,
newPatchedFilePath,
);
File(patchedFilePath).renameSync(newPatchedFilePath);
await Root.exec(
cmd: 'chmod 644 $newPatchedFilePath',
);
await Root.exec(
cmd: 'chown system:system $newPatchedFilePath',
);
await Root.exec(
cmd: 'chcon u:object_r:apk_data_file:s0 $newPatchedFilePath',
);
return true;
} on Exception {
return false;
}
}
Future<void> installServiceDScript(
String packageName,
String originalFilePath,
String patchedFilePath,
) async {
String content = '#!/system/bin/sh\n'
'while [ "\$(getprop sys.boot_completed | tr -d \'\r\')" != "1" ]; do sleep 1; done\n'
'sleep 1\n'
'chcon u:object_r:apk_data_file:s0 $patchedFilePath\n'
'mount -o bind $patchedFilePath $originalFilePath';
File scriptFile = File('$serviceDDirPath/$packageName.sh');
await scriptFile.writeAsString(content);
await Root.exec(cmd: 'chmod 744 ${scriptFile.path}');
}
Future<void> installPostFsDataScript(
String packageName,
String originalFilePath,
String patchedFilePath,
) async {
String content = '#!/system/bin/sh\n'
'while read line; do echo \$line | grep $originalFilePath | '
'awk \'{print \$2}\' | xargs umount -l; done< /proc/mounts';
File scriptFile = File('$postFsDataDirPath/$packageName.sh');
await scriptFile.writeAsString(content);
await Root.exec(cmd: 'chmod 744 ${scriptFile.path}');
}
}

View file

@ -6,19 +6,24 @@ import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:package_archive_info/package_archive_info.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/application_info.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked/stacked.dart';
class AppSelectorViewModel extends BaseViewModel {
final PatcherAPI patcherAPI = locator<PatcherAPI>();
bool isRooted = false;
bool isFromStorage = false;
List<ApplicationWithIcon> apps = [];
ApplicationInfo? selectedApp;
PatchedApplication? selectedApp;
Future<void> initialize() async {
await getApps();
SharedPreferences prefs = await SharedPreferences.getInstance();
isRooted = prefs.getBool('isRooted') ?? false;
notifyListeners();
}
@ -27,12 +32,15 @@ class AppSelectorViewModel extends BaseViewModel {
apps = await patcherAPI.getFilteredInstalledApps();
}
void selectApp(ApplicationWithIcon application) {
ApplicationInfo app = ApplicationInfo(
void selectApp(ApplicationWithIcon application) async {
isFromStorage = false;
PatchedApplication app = PatchedApplication(
name: application.appName,
packageName: application.packageName,
version: application.versionName!,
apkFilePath: application.apkFilePath,
isRooted: isRooted,
isFromStorage: isFromStorage,
);
locator<AppSelectorViewModel>().selectedApp = app;
locator<PatchesSelectorViewModel>().selectedPatches.clear();
@ -41,6 +49,7 @@ class AppSelectorViewModel extends BaseViewModel {
}
Future<void> selectAppFromStorage(BuildContext context) async {
isFromStorage = true;
try {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
@ -50,11 +59,13 @@ class AppSelectorViewModel extends BaseViewModel {
File apkFile = File(result.files.single.path!);
PackageArchiveInfo? packageArchiveInfo =
await PackageArchiveInfo.fromPath(apkFile.path);
ApplicationInfo app = ApplicationInfo(
PatchedApplication app = PatchedApplication(
name: packageArchiveInfo.appName,
packageName: packageArchiveInfo.packageName,
version: packageArchiveInfo.version,
apkFilePath: result.files.single.path!,
isRooted: isRooted,
isFromStorage: isFromStorage,
);
locator<AppSelectorViewModel>().selectedApp = app;
locator<PatchesSelectorViewModel>().selectedPatches.clear();

View file

@ -1,6 +1,6 @@
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/application_info.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
@ -38,7 +38,8 @@ class InstallerViewModel extends BaseViewModel {
Future<void> runPatcher() async {
updateProgress(0.0);
ApplicationInfo? selectedApp = locator<AppSelectorViewModel>().selectedApp;
PatchedApplication? selectedApp =
locator<AppSelectorViewModel>().selectedApp;
if (selectedApp != null) {
String apkFilePath = selectedApp.apkFilePath;
List<Patch> selectedPatches =
@ -111,11 +112,16 @@ class InstallerViewModel extends BaseViewModel {
}
void installResult() async {
await locator<PatcherAPI>().installPatchedFile();
PatchedApplication? selectedApp =
locator<AppSelectorViewModel>().selectedApp;
if (selectedApp != null) {
await locator<PatcherAPI>().installPatchedFile(selectedApp);
}
}
void shareResult() {
ApplicationInfo? selectedApp = locator<AppSelectorViewModel>().selectedApp;
PatchedApplication? selectedApp =
locator<AppSelectorViewModel>().selectedApp;
if (selectedApp != null) {
locator<PatcherAPI>().sharePatchedFile(
selectedApp.name,

View file

@ -1,6 +1,6 @@
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/application_info.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
@ -18,7 +18,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
}
Future<void> getPatches() async {
ApplicationInfo? app = locator<AppSelectorViewModel>().selectedApp;
PatchedApplication? app = locator<AppSelectorViewModel>().selectedApp;
patches = await patcherAPI.getFilteredPatches(app);
}

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/ui/views/root_checker/root_checker_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/magisk_button.dart';
import 'package:stacked/stacked.dart';
@ -11,8 +12,9 @@ class RootCheckerView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ViewModelBuilder<RootCheckerViewModel>.reactive(
disposeViewModel: false,
onModelReady: (model) => model.initialize,
viewModelBuilder: () => RootCheckerViewModel(),
viewModelBuilder: () => locator<RootCheckerViewModel>(),
builder: (context, model, child) => Scaffold(
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
@ -64,15 +66,20 @@ class RootCheckerView extends StatelessWidget {
const SizedBox(height: 170),
MagiskButton(
onPressed: () {
model.getMagiskPermissions();
Future.delayed(const Duration(seconds: 5), () {
model.checkRoot();
});
model
.getMagiskPermissions()
.then((value) => model.checkRoot());
},
),
Text(
"Magisk permission granted: ${model.isRooted.toString()}",
style: GoogleFonts.poppins(),
I18nText(
'rootCheckerView.grantedPermission',
translationParams: {
'isRooted': model.isRooted.toString(),
},
child: Text(
'',
style: GoogleFonts.poppins(),
),
),
],
),

View file

@ -1,7 +1,5 @@
import 'package:fluttertoast/fluttertoast.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/app/app.router.dart';
import 'package:revanced_manager/ui/views/home/home_view.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked/stacked.dart';
import 'package:root/root.dart';
@ -18,21 +16,25 @@ class RootCheckerViewModel extends BaseViewModel {
Future<void> checkRoot() async {
isRooted = await Root.isRooted();
if (isRooted == true) {
navigateToHome();
}
notifyListeners();
}
Future<void> getMagiskPermissions() async {
if (isRooted == true) {
Fluttertoast.showToast(msg: 'Magisk permission already granted!');
Future<bool> getMagiskPermissions() async {
try {
await Root.exec(cmd: 'cat /proc/version');
} on Exception {
return false;
}
await Root.exec(cmd: "adb shell su -c exit");
notifyListeners();
return true;
}
Future<void> navigateToHome() async {
final prefs = await SharedPreferences.getInstance();
prefs.setBool('showHome', true);
_navigationService.navigateTo(Routes.homeView);
prefs.setBool('isRooted', isRooted!);
_navigationService.navigateTo(Routes.navigation);
notifyListeners();
}
}

View file

@ -17,7 +17,7 @@ class LatestCommitCard extends StatefulWidget {
}
class _LatestCommitCardState extends State<LatestCommitCard> {
GithubAPI githubAPI = GithubAPI();
final GithubAPI githubAPI = GithubAPI();
@override
Widget build(BuildContext context) {