revanced-manager/lib/services/patcher_api.dart

192 lines
6.0 KiB
Dart
Raw Normal View History

import 'dart:convert';
2022-08-06 15:04:18 +02:00
import 'dart:io';
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';
2022-08-09 01:01:06 +02:00
import 'package:injectable/injectable.dart';
import 'package:path_provider/path_provider.dart';
2022-08-25 01:51:47 +02:00
import 'package:revanced_manager/app/app.locator.dart';
2022-08-06 23:35:35 +02:00
import 'package:revanced_manager/models/patch.dart';
2022-08-14 20:40:34 +02:00
import 'package:revanced_manager/models/patched_application.dart';
2022-08-18 16:33:33 +02:00
import 'package:revanced_manager/services/manager_api.dart';
2022-08-14 20:40:34 +02:00
import 'package:revanced_manager/services/root_api.dart';
import 'package:share_extend/share_extend.dart';
2022-08-06 15:04:18 +02:00
2022-08-09 01:01:06 +02:00
@lazySingleton
class PatcherAPI {
static const patcherChannel = MethodChannel(
'app.revanced.manager/patcher',
);
2022-08-25 01:51:47 +02:00
final ManagerAPI _managerAPI = locator<ManagerAPI>();
2022-08-18 16:33:33 +02:00
final RootAPI _rootAPI = RootAPI();
late Directory _tmpDir;
List<Patch> _patches = [];
File? _outFile;
2022-08-09 01:01:06 +02:00
Future<void> initialize() async {
await _loadPatches();
Directory appCache = await getTemporaryDirectory();
_tmpDir = Directory('${appCache.path}/patcher');
cleanPatcher();
}
void cleanPatcher() {
if (_tmpDir.existsSync()) {
_tmpDir.deleteSync(recursive: true);
}
2022-08-19 20:13:43 +02:00
}
Future<void> _loadPatches() async {
try {
if (_patches.isEmpty) {
File? patchJsonFile = await _managerAPI.downloadPatches('.json');
if (patchJsonFile != null) {
List<dynamic> list = json.decode(patchJsonFile.readAsStringSync());
_patches = list.map((patch) => Patch.fromJson(patch)).toList();
}
2022-08-06 15:04:18 +02:00
}
} on Exception {
_patches = List.empty();
2022-08-06 15:04:18 +02:00
}
}
Future<List<ApplicationWithIcon>> getFilteredInstalledApps() async {
List<ApplicationWithIcon> filteredApps = [];
await _loadPatches();
for (Patch patch in _patches) {
for (Package package in patch.compatiblePackages) {
try {
if (!filteredApps.any((app) => app.packageName == package.name)) {
ApplicationWithIcon? app =
await DeviceApps.getApp(package.name, true)
as ApplicationWithIcon?;
if (app != null) {
filteredApps.add(app);
2022-08-06 15:04:18 +02:00
}
}
} catch (e) {
continue;
2022-08-06 15:04:18 +02:00
}
2022-08-17 19:44:27 +02:00
}
}
return filteredApps;
2022-08-17 19:44:27 +02:00
}
Future<List<Patch>> getFilteredPatches(String packageName) async {
await _loadPatches();
return _patches
.where((patch) =>
!patch.name.contains('settings') &&
patch.compatiblePackages.any((pack) => pack.name == packageName))
.toList();
}
Future<List<Patch>> getAppliedPatches(List<String> appliedPatches) async {
await _loadPatches();
return _patches
.where((patch) => appliedPatches.contains(patch.name))
.toList();
}
Future<void> runPatcher(
String packageName,
String originalFilePath,
List<Patch> selectedPatches,
) async {
bool mergeIntegrations = selectedPatches.any(
(patch) => patch.dependencies.contains('integrations'),
);
bool resourcePatching = selectedPatches.any(
(patch) => patch.dependencies.any((dep) => dep.contains('resource-')),
);
bool includeSettings = selectedPatches.any(
(patch) => patch.dependencies.contains('settings'),
);
if (includeSettings) {
try {
Patch settingsPatch = _patches.firstWhere(
(patch) =>
patch.name.contains('settings') &&
patch.compatiblePackages.any((pack) => pack.name == packageName),
);
selectedPatches.add(settingsPatch);
} catch (e) {
// ignore
}
}
File? patchBundleFile = await _managerAPI.downloadPatches('.jar');
File? integrationsFile;
if (mergeIntegrations) {
integrationsFile = await _managerAPI.downloadIntegrations('.apk');
}
if (patchBundleFile != null) {
_tmpDir.createSync();
Directory workDir = _tmpDir.createTempSync('tmp-');
File inputFile = File('${workDir.path}/base.apk');
File patchedFile = File('${workDir.path}/patched.apk');
_outFile = File('${workDir.path}/out.apk');
Directory cacheDir = Directory('${workDir.path}/cache');
cacheDir.createSync();
await patcherChannel.invokeMethod(
'runPatcher',
{
'patchBundleFilePath': patchBundleFile.path,
'originalFilePath': originalFilePath,
'inputFilePath': inputFile.path,
'patchedFilePath': patchedFile.path,
'outFilePath': _outFile!.path,
'integrationsPath': mergeIntegrations ? integrationsFile!.path : '',
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
'cacheDirPath': cacheDir.path,
'mergeIntegrations': mergeIntegrations,
'resourcePatching': resourcePatching,
},
);
}
}
2022-08-14 20:40:34 +02:00
Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
if (_outFile != null) {
try {
2022-08-14 20:40:34 +02:00
if (patchedApp.isRooted && !patchedApp.isFromStorage) {
2022-08-18 16:33:33 +02:00
return _rootAPI.installApp(
2022-08-14 20:40:34 +02:00
patchedApp.packageName,
patchedApp.apkFilePath,
_outFile!.path,
);
} else {
await AppInstaller.installApk(_outFile!.path);
2022-08-17 18:07:00 +02:00
return await DeviceApps.isAppInstalled(patchedApp.packageName);
}
} on Exception {
return false;
}
}
return false;
}
bool sharePatchedFile(String appName, String version) {
if (_outFile != null) {
String prefix = appName.toLowerCase().replaceAll(' ', '-');
File share = _outFile!.renameSync('$prefix-revanced_v$version.apk');
2022-08-14 20:40:34 +02:00
ShareExtend.share(share.path, 'file');
return true;
} else {
return false;
}
2022-08-06 15:04:18 +02:00
}
Future<bool> checkOldPatch(PatchedApplication patchedApp) async {
if (patchedApp.isRooted) {
2022-08-25 01:51:47 +02:00
return await _rootAPI.isAppInstalled(patchedApp.packageName);
}
return false;
}
Future<void> deleteOldPatch(PatchedApplication patchedApp) async {
if (patchedApp.isRooted) {
2022-08-18 16:33:33 +02:00
await _rootAPI.deleteApp(patchedApp.packageName, patchedApp.apkFilePath);
}
}
2022-08-06 15:04:18 +02:00
}