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';
|
2022-08-19 20:13:43 +02:00
|
|
|
import 'package:flutter_archive/flutter_archive.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';
|
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';
|
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-17 13:48:03 +02:00
|
|
|
static const patcherChannel = MethodChannel(
|
|
|
|
'app.revanced.manager/patcher',
|
|
|
|
);
|
2022-08-18 16:33:33 +02:00
|
|
|
final ManagerAPI _managerAPI = ManagerAPI();
|
|
|
|
final RootAPI _rootAPI = RootAPI();
|
2022-08-14 20:40:34 +02:00
|
|
|
Directory? _tmpDir;
|
2022-08-13 11:56:30 +02:00
|
|
|
Directory? _workDir;
|
|
|
|
Directory? _cacheDir;
|
2022-08-19 20:13:43 +02:00
|
|
|
File? _zipPatchBundleFile;
|
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-19 20:13:43 +02:00
|
|
|
Future<void> initPatcher() async {
|
|
|
|
_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();
|
|
|
|
}
|
|
|
|
|
2022-08-18 16:33:33 +02:00
|
|
|
Future<void> loadPatches() async {
|
2022-08-19 20:13:43 +02:00
|
|
|
if (_cacheDir == null) {
|
|
|
|
await initPatcher();
|
|
|
|
}
|
|
|
|
if (_zipPatchBundleFile == null) {
|
|
|
|
File? patchBundleDexFile = await _managerAPI.downloadPatches('.dex');
|
|
|
|
File? patchBundleJarFile = await _managerAPI.downloadPatches('.jar');
|
|
|
|
if (patchBundleDexFile != null && patchBundleJarFile != null) {
|
|
|
|
await joinPatchBundleFiles(patchBundleDexFile, patchBundleJarFile);
|
|
|
|
if (_zipPatchBundleFile != null) {
|
|
|
|
await patcherChannel.invokeMethod<bool>(
|
|
|
|
'loadPatches',
|
|
|
|
{
|
|
|
|
'zipPatchBundlePath': _zipPatchBundleFile!.path,
|
|
|
|
'cacheDirPath': _cacheDir!.path,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-13 11:56:30 +02:00
|
|
|
Future<List<ApplicationWithIcon>> getFilteredInstalledApps() async {
|
2022-08-17 19:44:27 +02:00
|
|
|
List<ApplicationWithIcon> filteredPackages = [];
|
2022-08-19 20:13:43 +02:00
|
|
|
if (_zipPatchBundleFile != null) {
|
2022-08-06 15:04:18 +02:00
|
|
|
try {
|
2022-08-17 13:48:03 +02:00
|
|
|
List<String>? patchesPackages = await patcherChannel
|
|
|
|
.invokeListMethod<String>('getCompatiblePackages');
|
2022-08-06 15:04:18 +02:00
|
|
|
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) {
|
2022-08-17 19:44:27 +02:00
|
|
|
filteredPackages.add(app);
|
2022-08-13 11:56:30 +02:00
|
|
|
}
|
2022-08-10 01:50:02 +02:00
|
|
|
} catch (e) {
|
|
|
|
continue;
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-14 04:07:28 +02:00
|
|
|
} on Exception {
|
2022-08-06 15:04:18 +02:00
|
|
|
return List.empty();
|
|
|
|
}
|
|
|
|
}
|
2022-08-17 19:44:27 +02:00
|
|
|
return filteredPackages;
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
|
|
|
|
2022-08-17 19:44:27 +02:00
|
|
|
Future<List<Patch>> getFilteredPatches(
|
2022-08-14 20:40:34 +02:00
|
|
|
PatchedApplication? selectedApp,
|
|
|
|
) async {
|
2022-08-17 19:44:27 +02:00
|
|
|
List<Patch> filteredPatches = [];
|
2022-08-19 20:13:43 +02:00
|
|
|
if (_zipPatchBundleFile != null && selectedApp != null) {
|
2022-08-17 19:44:27 +02:00
|
|
|
try {
|
|
|
|
var patches =
|
|
|
|
await patcherChannel.invokeListMethod<Map<dynamic, dynamic>>(
|
|
|
|
'getFilteredPatches',
|
|
|
|
{
|
|
|
|
'targetPackage': selectedApp.packageName,
|
|
|
|
'targetVersion': selectedApp.version,
|
|
|
|
'ignoreVersion': true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if (patches != null) {
|
|
|
|
for (var patch in patches) {
|
|
|
|
if (!filteredPatches
|
|
|
|
.any((element) => element.name == patch['name'])) {
|
|
|
|
filteredPatches.add(
|
|
|
|
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-17 19:44:27 +02:00
|
|
|
} on Exception {
|
|
|
|
return List.empty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return filteredPatches;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<Patch>> getAppliedPatches(
|
|
|
|
PatchedApplication? selectedApp,
|
|
|
|
) async {
|
|
|
|
List<Patch> appliedPatches = [];
|
2022-08-19 20:13:43 +02:00
|
|
|
if (_zipPatchBundleFile != null && selectedApp != null) {
|
2022-08-17 19:44:27 +02:00
|
|
|
try {
|
|
|
|
var patches =
|
|
|
|
await patcherChannel.invokeListMethod<Map<dynamic, dynamic>>(
|
|
|
|
'getFilteredPatches',
|
|
|
|
{
|
|
|
|
'targetPackage': selectedApp.packageName,
|
|
|
|
'targetVersion': selectedApp.version,
|
|
|
|
'ignoreVersion': true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if (patches != null) {
|
|
|
|
for (var patch in patches) {
|
|
|
|
if (selectedApp.appliedPatches.contains(patch['name'])) {
|
|
|
|
appliedPatches.add(
|
|
|
|
Patch(
|
|
|
|
name: patch['name'],
|
|
|
|
simpleName: (patch['name'] as String)
|
|
|
|
.replaceAll('-', ' ')
|
|
|
|
.split('-')
|
|
|
|
.join(' ')
|
|
|
|
.toTitleCase(),
|
|
|
|
version: patch['version'] ?? '?.?.?',
|
|
|
|
description: patch['description'] ?? 'N/A',
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} on Exception {
|
|
|
|
return List.empty();
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
|
|
|
}
|
2022-08-17 19:44:27 +02:00
|
|
|
return appliedPatches;
|
2022-08-13 11:56:30 +02:00
|
|
|
}
|
|
|
|
|
2022-08-19 20:13:43 +02:00
|
|
|
Future<void> mergeIntegrations(bool mergeIntegrations) async {
|
2022-08-17 13:48:03 +02:00
|
|
|
if (mergeIntegrations) {
|
2022-08-19 20:13:43 +02:00
|
|
|
_integrations = await _managerAPI.downloadIntegrations('.apk');
|
|
|
|
} else {
|
|
|
|
_integrations = null;
|
2022-08-13 11:56:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-17 13:48:03 +02:00
|
|
|
Future<void> runPatcher(
|
|
|
|
String originalFilePath,
|
|
|
|
List<Patch> selectedPatches,
|
|
|
|
bool mergeIntegrations,
|
|
|
|
bool resourcePatching,
|
|
|
|
) async {
|
|
|
|
await patcherChannel.invokeMethod(
|
|
|
|
'runPatcher',
|
|
|
|
{
|
|
|
|
'originalFilePath': originalFilePath,
|
|
|
|
'inputFilePath': _inputFile!.path,
|
|
|
|
'patchedFilePath': _patchedFile!.path,
|
|
|
|
'outFilePath': _outFile!.path,
|
2022-08-18 16:33:33 +02:00
|
|
|
'integrationsPath': _integrations != null ? _integrations!.path : '',
|
2022-08-17 19:44:27 +02:00
|
|
|
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
2022-08-17 13:48:03 +02:00
|
|
|
'cacheDirPath': _cacheDir!.path,
|
|
|
|
'mergeIntegrations': mergeIntegrations,
|
|
|
|
'resourcePatching': resourcePatching,
|
|
|
|
},
|
|
|
|
);
|
2022-08-13 11:56:30 +02:00
|
|
|
}
|
|
|
|
|
2022-08-14 20:40:34 +02:00
|
|
|
Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
|
2022-08-13 11:56:30 +02:00
|
|
|
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,
|
|
|
|
);
|
2022-08-13 11:56:30 +02:00
|
|
|
} else {
|
|
|
|
await AppInstaller.installApk(_outFile!.path);
|
2022-08-17 18:07:00 +02:00
|
|
|
return await DeviceApps.isAppInstalled(patchedApp.packageName);
|
2022-08-13 11:56:30 +02:00
|
|
|
}
|
|
|
|
} on Exception {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void cleanPatcher() {
|
|
|
|
if (_workDir != null) {
|
|
|
|
_workDir!.deleteSync(recursive: true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-14 04:07:28 +02:00
|
|
|
bool sharePatchedFile(String appName, String version) {
|
2022-08-13 11:56:30 +02:00
|
|
|
if (_outFile != null) {
|
2022-08-14 20:40:34 +02:00
|
|
|
String path = _tmpDir!.path;
|
2022-08-14 04:07:28 +02:00
|
|
|
String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
|
|
|
String sharePath = '$path/$prefix-revanced_v$version.apk';
|
2022-08-13 11:56:30 +02:00
|
|
|
File share = _outFile!.copySync(sharePath);
|
2022-08-14 20:40:34 +02:00
|
|
|
ShareExtend.share(share.path, 'file');
|
2022-08-13 11:56:30 +02:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|
2022-08-15 04:31:36 +02:00
|
|
|
|
|
|
|
Future<bool> checkOldPatch(PatchedApplication patchedApp) async {
|
|
|
|
if (patchedApp.isRooted) {
|
2022-08-18 16:33:33 +02:00
|
|
|
return await _rootAPI.checkApp(patchedApp.packageName);
|
2022-08-15 04:31:36 +02:00
|
|
|
}
|
|
|
|
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-15 04:31:36 +02:00
|
|
|
}
|
|
|
|
}
|
2022-08-19 20:13:43 +02:00
|
|
|
|
|
|
|
Future<void> joinPatchBundleFiles(
|
|
|
|
File patchBundleDexFile,
|
|
|
|
File patchBundleJarFile,
|
|
|
|
) async {
|
|
|
|
_zipPatchBundleFile = File('${_workDir!.path}/join.zip');
|
|
|
|
Directory joinDir = Directory('${_cacheDir!.path}/join');
|
|
|
|
try {
|
|
|
|
await ZipFile.extractToDirectory(
|
|
|
|
zipFile: patchBundleJarFile,
|
|
|
|
destinationDir: joinDir,
|
|
|
|
);
|
|
|
|
patchBundleDexFile.copySync('${joinDir.path}/classes.dex');
|
|
|
|
await ZipFile.createFromDirectory(
|
|
|
|
sourceDir: joinDir,
|
|
|
|
zipFile: _zipPatchBundleFile!,
|
|
|
|
recurseSubDirs: true,
|
|
|
|
);
|
|
|
|
} on Exception {
|
|
|
|
_zipPatchBundleFile = null;
|
|
|
|
}
|
|
|
|
}
|
2022-08-06 15:04:18 +02:00
|
|
|
}
|