2022-08-06 15:04:18 +02:00
|
|
|
import 'dart:io';
|
2022-08-13 11:56:30 +02:00
|
|
|
import 'package:app_installer/app_installer.dart';
|
|
|
|
import 'package:device_apps/device_apps.dart';
|
2022-08-06 15:04:18 +02:00
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
2022-08-09 01:01:06 +02:00
|
|
|
import 'package:injectable/injectable.dart';
|
2022-08-13 11:56:30 +02:00
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import 'package:revanced_manager/app/app.locator.dart';
|
|
|
|
import 'package:revanced_manager/models/application_info.dart';
|
2022-08-06 23:35:35 +02:00
|
|
|
import 'package:revanced_manager/models/patch.dart';
|
|
|
|
import 'package:revanced_manager/services/github_api.dart';
|
2022-08-13 11:56:30 +02:00
|
|
|
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
|
2022-08-06 23:35:35 +02:00
|
|
|
import 'package:revanced_manager/utils/string.dart';
|
2022-08-13 11:56:30 +02:00
|
|
|
import 'package:share_extend/share_extend.dart';
|
2022-08-06 15:04:18 +02:00
|
|
|
|
2022-08-09 01:01:06 +02:00
|
|
|
@lazySingleton
|
2022-08-09 02:20:50 +02:00
|
|
|
class PatcherAPI {
|
2022-08-13 11:56:30 +02:00
|
|
|
static const platform = MethodChannel('app.revanced.manager/patcher');
|
2022-08-09 01:01:06 +02:00
|
|
|
final GithubAPI githubAPI = GithubAPI();
|
2022-08-13 11:56:30 +02:00
|
|
|
final List<ApplicationWithIcon> _filteredPackages = [];
|
2022-08-06 15:04:18 +02:00
|
|
|
final Map<String, List<Patch>> _filteredPatches = <String, List<Patch>>{};
|
2022-08-13 11:56:30 +02:00
|
|
|
bool isRoot = false;
|
|
|
|
Directory? _workDir;
|
|
|
|
Directory? _cacheDir;
|
2022-08-09 01:01:06 +02:00
|
|
|
File? _patchBundleFile;
|
2022-08-13 11:56:30 +02:00
|
|
|
File? _integrations;
|
|
|
|
File? _inputFile;
|
|
|
|
File? _patchedFile;
|
|
|
|
File? _outFile;
|
2022-08-09 01:01:06 +02:00
|
|
|
|
2022-08-13 11:56:30 +02:00
|
|
|
Future<dynamic> handlePlatformChannelMethods() async {
|
|
|
|
platform.setMethodCallHandler((call) async {
|
|
|
|
switch (call.method) {
|
|
|
|
case 'updateInstallerLog':
|
|
|
|
var message = call.arguments<String>('message');
|
|
|
|
locator<InstallerViewModel>().addLog(message);
|
|
|
|
return 'OK';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool?> loadPatches() async {
|
2022-08-06 15:04:18 +02:00
|
|
|
if (_patchBundleFile == null) {
|
|
|
|
String? dexFileUrl =
|
|
|
|
await githubAPI.latestRelease('revanced', 'revanced-patches');
|
2022-08-09 02:16:33 +02:00
|
|
|
if (dexFileUrl != null && dexFileUrl.isNotEmpty) {
|
2022-08-06 15:04:18 +02:00
|
|
|
_patchBundleFile =
|
|
|
|
await DefaultCacheManager().getSingleFile(dexFileUrl);
|
|
|
|
try {
|
2022-08-13 11:56:30 +02:00
|
|
|
return await platform.invokeMethod<bool>(
|
2022-08-06 15:04:18 +02:00
|
|
|
'loadPatches',
|
|
|
|
{
|
|
|
|
'pathBundlesPaths': <String>[_patchBundleFile!.absolute.path],
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} on PlatformException {
|
|
|
|
_patchBundleFile = null;
|
2022-08-13 11:56:30 +02:00
|
|
|
return false;
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
|
|
|
}
|
2022-08-13 11:56:30 +02:00
|
|
|
return false;
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
2022-08-13 11:56:30 +02:00
|
|
|
return true;
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
|
|
|
|
2022-08-13 11:56:30 +02:00
|
|
|
Future<List<ApplicationWithIcon>> getFilteredInstalledApps() async {
|
2022-08-06 15:04:18 +02:00
|
|
|
if (_patchBundleFile != null && _filteredPackages.isEmpty) {
|
|
|
|
try {
|
|
|
|
List<String>? patchesPackages =
|
|
|
|
await platform.invokeListMethod<String>('getCompatiblePackages');
|
|
|
|
if (patchesPackages != null) {
|
2022-08-10 01:50:02 +02:00
|
|
|
for (String package in patchesPackages) {
|
|
|
|
try {
|
2022-08-13 11:56:30 +02:00
|
|
|
ApplicationWithIcon? app = await DeviceApps.getApp(package, true)
|
|
|
|
as ApplicationWithIcon?;
|
|
|
|
if (app != null) {
|
|
|
|
_filteredPackages.add(app);
|
|
|
|
}
|
2022-08-10 01:50:02 +02:00
|
|
|
} catch (e) {
|
|
|
|
continue;
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-10 01:50:02 +02:00
|
|
|
} on PlatformException {
|
|
|
|
_filteredPackages.clear();
|
2022-08-06 15:04:18 +02:00
|
|
|
return List.empty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return _filteredPackages;
|
|
|
|
}
|
|
|
|
|
2022-08-13 11:56:30 +02:00
|
|
|
Future<List<Patch>?> getFilteredPatches(ApplicationInfo? selectedApp) async {
|
|
|
|
if (_patchBundleFile != null && selectedApp != null) {
|
|
|
|
if (_filteredPatches[selectedApp.packageName] == null ||
|
|
|
|
_filteredPatches[selectedApp.packageName]!.isEmpty) {
|
|
|
|
_filteredPatches[selectedApp.packageName] = [];
|
2022-08-06 15:04:18 +02:00
|
|
|
try {
|
|
|
|
var patches = await platform.invokeListMethod<Map<dynamic, dynamic>>(
|
|
|
|
'getFilteredPatches',
|
|
|
|
{
|
2022-08-13 11:56:30 +02:00
|
|
|
'targetPackage': selectedApp.packageName,
|
|
|
|
'targetVersion': selectedApp.version,
|
2022-08-06 15:04:18 +02:00
|
|
|
'ignoreVersion': true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if (patches != null) {
|
|
|
|
for (var patch in patches) {
|
2022-08-13 11:56:30 +02:00
|
|
|
if (!_filteredPatches[selectedApp.packageName]!
|
2022-08-10 01:50:02 +02:00
|
|
|
.any((element) => element.name == patch['name'])) {
|
2022-08-13 11:56:30 +02:00
|
|
|
_filteredPatches[selectedApp.packageName]!.add(
|
2022-08-10 01:50:02 +02:00
|
|
|
Patch(
|
|
|
|
name: patch['name'],
|
|
|
|
simpleName: (patch['name'] as String)
|
|
|
|
.replaceAll('-', ' ')
|
|
|
|
.split('-')
|
|
|
|
.join(' ')
|
|
|
|
.toTitleCase(),
|
|
|
|
version: patch['version'] ?? '?.?.?',
|
|
|
|
description: patch['description'] ?? 'N/A',
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
|
|
|
}
|
2022-08-10 01:50:02 +02:00
|
|
|
} on PlatformException {
|
2022-08-13 11:56:30 +02:00
|
|
|
_filteredPatches[selectedApp.packageName]!.clear();
|
2022-08-06 15:04:18 +02:00
|
|
|
return List.empty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return List.empty();
|
|
|
|
}
|
2022-08-13 11:56:30 +02:00
|
|
|
return _filteredPatches[selectedApp.packageName];
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<File?> downloadIntegrations() async {
|
|
|
|
String? apkFileUrl =
|
|
|
|
await githubAPI.latestRelease('revanced', 'revanced-integrations');
|
|
|
|
if (apkFileUrl != null && apkFileUrl.isNotEmpty) {
|
|
|
|
return await DefaultCacheManager().getSingleFile(apkFileUrl);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool?> 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<bool?> copyInputFile(String originalFilePath) async {
|
|
|
|
if (_inputFile != null) {
|
|
|
|
try {
|
|
|
|
return await platform.invokeMethod<bool>(
|
|
|
|
'copyInputFile',
|
|
|
|
{
|
|
|
|
'originalFilePath': originalFilePath,
|
|
|
|
'inputFilePath': _inputFile!.path,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} on PlatformException {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool?> createPatcher() async {
|
|
|
|
if (_inputFile != null && _cacheDir != null) {
|
|
|
|
try {
|
|
|
|
return await platform.invokeMethod<bool>(
|
|
|
|
'createPatcher',
|
|
|
|
{
|
|
|
|
'inputFilePath': _inputFile!.path,
|
|
|
|
'cacheDirPath': _cacheDir!.path,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} on PlatformException {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool?> mergeIntegrations() async {
|
|
|
|
try {
|
|
|
|
return await platform.invokeMethod<bool>(
|
|
|
|
'mergeIntegrations',
|
|
|
|
{
|
|
|
|
'integrationsPath': _integrations!.path,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} on PlatformException {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool?> applyPatches(List<Patch> selectedPatches) async {
|
|
|
|
try {
|
|
|
|
return await platform.invokeMethod<bool>(
|
|
|
|
'applyPatches',
|
|
|
|
{
|
|
|
|
'selectedPatches': selectedPatches.map((e) => e.name).toList(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} on PlatformException {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool?> repackPatchedFile() async {
|
|
|
|
if (_inputFile != null && _patchedFile != null) {
|
|
|
|
try {
|
|
|
|
return await platform.invokeMethod<bool>(
|
|
|
|
'repackPatchedFile',
|
|
|
|
{
|
|
|
|
'inputFilePath': _inputFile!.path,
|
|
|
|
'patchedFilePath': _patchedFile!.path,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} on PlatformException {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool?> signPatchedFile() async {
|
|
|
|
if (_patchedFile != null && _outFile != null) {
|
|
|
|
try {
|
|
|
|
return await platform.invokeMethod<bool>(
|
|
|
|
'signPatchedFile',
|
|
|
|
{
|
|
|
|
'patchedFilePath': _patchedFile!.path,
|
|
|
|
'outFilePath': _outFile!.path,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} on PlatformException {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool> 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;
|
|
|
|
}
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
|
|
|
}
|