import 'dart:io'; import 'package:app_installer/app_installer.dart'; import 'package:device_apps/device_apps.dart'; import 'package:flutter/services.dart'; 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/services/github_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'; @lazySingleton class PatcherAPI { static const platform = MethodChannel('app.revanced.manager/patcher'); final GithubAPI githubAPI = GithubAPI(); final List _filteredPackages = []; final Map> _filteredPatches = >{}; bool isRoot = false; Directory? _workDir; Directory? _cacheDir; File? _patchBundleFile; File? _integrations; File? _inputFile; File? _patchedFile; File? _outFile; Future handlePlatformChannelMethods() async { platform.setMethodCallHandler((call) async { switch (call.method) { case 'updateInstallerLog': var message = call.arguments('message'); locator().addLog(message); return 'OK'; } }); } Future loadPatches() async { if (_patchBundleFile == null) { String? dexFileUrl = await githubAPI.latestRelease('revanced', 'revanced-patches'); if (dexFileUrl != null && dexFileUrl.isNotEmpty) { _patchBundleFile = await DefaultCacheManager().getSingleFile(dexFileUrl); try { return await platform.invokeMethod( 'loadPatches', { 'pathBundlesPaths': [_patchBundleFile!.absolute.path], }, ); } on PlatformException { _patchBundleFile = null; return false; } } return false; } return true; } Future> getFilteredInstalledApps() async { if (_patchBundleFile != null && _filteredPackages.isEmpty) { try { List? patchesPackages = await platform.invokeListMethod('getCompatiblePackages'); if (patchesPackages != null) { for (String package in patchesPackages) { try { ApplicationWithIcon? app = await DeviceApps.getApp(package, true) as ApplicationWithIcon?; if (app != null) { _filteredPackages.add(app); } } catch (e) { continue; } } } } on PlatformException { _filteredPackages.clear(); return List.empty(); } } return _filteredPackages; } Future?> getFilteredPatches(ApplicationInfo? selectedApp) async { if (_patchBundleFile != null && selectedApp != null) { if (_filteredPatches[selectedApp.packageName] == null || _filteredPatches[selectedApp.packageName]!.isEmpty) { _filteredPatches[selectedApp.packageName] = []; try { var patches = await platform.invokeListMethod>( 'getFilteredPatches', { 'targetPackage': selectedApp.packageName, 'targetVersion': selectedApp.version, 'ignoreVersion': true, }, ); if (patches != null) { for (var patch in patches) { if (!_filteredPatches[selectedApp.packageName]! .any((element) => element.name == patch['name'])) { _filteredPatches[selectedApp.packageName]!.add( Patch( name: patch['name'], simpleName: (patch['name'] as String) .replaceAll('-', ' ') .split('-') .join(' ') .toTitleCase(), version: patch['version'] ?? '?.?.?', description: patch['description'] ?? 'N/A', ), ); } } } } on PlatformException { _filteredPatches[selectedApp.packageName]!.clear(); return List.empty(); } } } else { return List.empty(); } return _filteredPatches[selectedApp.packageName]; } Future downloadIntegrations() async { String? apkFileUrl = await githubAPI.latestRelease('revanced', 'revanced-integrations'); if (apkFileUrl != null && apkFileUrl.isNotEmpty) { return await DefaultCacheManager().getSingleFile(apkFileUrl); } return null; } Future initPatcher() async { try { _integrations = await downloadIntegrations(); if (_integrations != null) { Directory 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'); _cacheDir = Directory('${_workDir!.path}/cache'); _cacheDir!.createSync(); return true; } } on Exception { return false; } return false; } Future copyInputFile(String originalFilePath) async { if (_inputFile != null) { try { return await platform.invokeMethod( 'copyInputFile', { 'originalFilePath': originalFilePath, 'inputFilePath': _inputFile!.path, }, ); } on PlatformException { return false; } } return false; } Future createPatcher() async { if (_inputFile != null && _cacheDir != null) { try { return await platform.invokeMethod( 'createPatcher', { 'inputFilePath': _inputFile!.path, 'cacheDirPath': _cacheDir!.path, }, ); } on PlatformException { return false; } } return false; } Future mergeIntegrations() async { try { return await platform.invokeMethod( 'mergeIntegrations', { 'integrationsPath': _integrations!.path, }, ); } on PlatformException { return false; } } Future applyPatches(List selectedPatches) async { try { return await platform.invokeMethod( 'applyPatches', { 'selectedPatches': selectedPatches.map((e) => e.name).toList(), }, ); } on PlatformException { return false; } } Future repackPatchedFile() async { if (_inputFile != null && _patchedFile != null) { try { return await platform.invokeMethod( 'repackPatchedFile', { 'inputFilePath': _inputFile!.path, 'patchedFilePath': _patchedFile!.path, }, ); } on PlatformException { return false; } } return false; } Future signPatchedFile() async { if (_patchedFile != null && _outFile != null) { try { return await platform.invokeMethod( 'signPatchedFile', { 'patchedFilePath': _patchedFile!.path, 'outFilePath': _outFile!.path, }, ); } on PlatformException { return false; } } return false; } Future installPatchedFile() async { if (_outFile != null) { try { if (isRoot) { // TBD } else { await AppInstaller.installApk(_outFile!.path); } return true; } on Exception { return false; } } return false; } void cleanPatcher() { if (_workDir != null) { _workDir!.deleteSync(recursive: true); } } bool sharePatchedFile(String packageName) { if (_outFile != null) { String sharePath = '${_outFile!.parent.path}/$packageName.revanced.apk'; File share = _outFile!.copySync(sharePath); ShareExtend.share(share.path, "file"); return true; } else { return false; } } }