mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2024-11-10 09:07:47 +01:00
feat: working resource patching
This commit is contained in:
parent
e373aef2d9
commit
4b2806c519
8 changed files with 107 additions and 64 deletions
|
@ -30,12 +30,12 @@ android {
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_11
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = '11'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -67,7 +67,7 @@ dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
// ReVanced
|
// ReVanced
|
||||||
implementation "app.revanced:revanced-patcher:3.3.1"
|
implementation "app.revanced:revanced-patcher:3.3.3"
|
||||||
|
|
||||||
// Signing & aligning
|
// Signing & aligning
|
||||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||||
|
|
|
@ -16,7 +16,7 @@ import app.revanced.patcher.extensions.PatchExtensions.description
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.version
|
import app.revanced.patcher.extensions.PatchExtensions.version
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import app.revanced.patcher.util.patch.implementation.DexPatchBundle
|
import app.revanced.patcher.util.patch.impl.DexPatchBundle
|
||||||
import dalvik.system.DexClassLoader
|
import dalvik.system.DexClassLoader
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
@ -41,9 +41,10 @@ class MainActivity : FlutterActivity() {
|
||||||
mainChannel.setMethodCallHandler { call, result ->
|
mainChannel.setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"loadPatches" -> {
|
"loadPatches" -> {
|
||||||
val pathBundlesPaths = call.argument<List<String>>("pathBundlesPaths")
|
val zipPatchBundlePath = call.argument<String>("zipPatchBundlePath")
|
||||||
if (pathBundlesPaths != null) {
|
val cacheDirPath = call.argument<String>("cacheDirPath")
|
||||||
loadPatches(result, pathBundlesPaths)
|
if (zipPatchBundlePath != null && cacheDirPath != null) {
|
||||||
|
loadPatches(result, zipPatchBundlePath, cacheDirPath)
|
||||||
} else {
|
} else {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
}
|
}
|
||||||
|
@ -100,23 +101,25 @@ class MainActivity : FlutterActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadPatches(result: MethodChannel.Result, pathBundlesPaths: List<String>) {
|
fun loadPatches(
|
||||||
|
result: MethodChannel.Result,
|
||||||
|
zipPatchBundlePath: String,
|
||||||
|
cacheDirPath: String
|
||||||
|
) {
|
||||||
Thread(
|
Thread(
|
||||||
Runnable {
|
Runnable {
|
||||||
pathBundlesPaths.forEach { path ->
|
|
||||||
patches.addAll(
|
patches.addAll(
|
||||||
DexPatchBundle(
|
DexPatchBundle(
|
||||||
path,
|
zipPatchBundlePath,
|
||||||
DexClassLoader(
|
DexClassLoader(
|
||||||
path,
|
zipPatchBundlePath,
|
||||||
applicationContext.cacheDir.path,
|
cacheDirPath,
|
||||||
null,
|
null,
|
||||||
javaClass.classLoader
|
javaClass.classLoader
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.loadPatches()
|
.loadPatches()
|
||||||
)
|
)
|
||||||
}
|
|
||||||
handler.post { result.success(null) }
|
handler.post { result.success(null) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -185,7 +188,8 @@ class MainActivity : FlutterActivity() {
|
||||||
val patchedFile = File(patchedFilePath)
|
val patchedFile = File(patchedFilePath)
|
||||||
val outFile = File(outFilePath)
|
val outFile = File(outFilePath)
|
||||||
val integrations = File(integrationsPath)
|
val integrations = File(integrationsPath)
|
||||||
val filteredPatches = patches.filter { patch -> selectedPatches.any { it == patch.patchName } }
|
val filteredPatches =
|
||||||
|
patches.filter { patch -> selectedPatches.any { it == patch.patchName } }
|
||||||
|
|
||||||
Thread(
|
Thread(
|
||||||
Runnable {
|
Runnable {
|
||||||
|
@ -289,7 +293,7 @@ class MainActivity : FlutterActivity() {
|
||||||
res.dexFiles.forEach {
|
res.dexFiles.forEach {
|
||||||
file.addEntryCompressData(
|
file.addEntryCompressData(
|
||||||
ZipEntry.createWithName(it.name),
|
ZipEntry.createWithName(it.name),
|
||||||
it.dexFileInputStream.readBytes()
|
it.stream.readBytes()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
res.resourceFile?.let {
|
res.resourceFile?.let {
|
||||||
|
|
|
@ -17,7 +17,11 @@ class GithubAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> latestReleaseFile(String org, repoName) async {
|
Future<File?> latestReleaseFile(
|
||||||
|
String extension,
|
||||||
|
String org,
|
||||||
|
repoName,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
var latestRelease = await _github.repositories.getLatestRelease(
|
var latestRelease = await _github.repositories.getLatestRelease(
|
||||||
RepositorySlug(org, repoName),
|
RepositorySlug(org, repoName),
|
||||||
|
@ -25,7 +29,7 @@ class GithubAPI {
|
||||||
String? url = latestRelease.assets
|
String? url = latestRelease.assets
|
||||||
?.firstWhere((asset) =>
|
?.firstWhere((asset) =>
|
||||||
asset.name != null &&
|
asset.name != null &&
|
||||||
(asset.name!.endsWith('.dex') || asset.name!.endsWith('.apk')) &&
|
asset.name!.endsWith(extension) &&
|
||||||
!asset.name!.contains('-sources') &&
|
!asset.name!.contains('-sources') &&
|
||||||
!asset.name!.contains('-javadoc'))
|
!asset.name!.contains('-javadoc'))
|
||||||
.browserDownloadUrl;
|
.browserDownloadUrl;
|
||||||
|
|
|
@ -6,30 +6,28 @@ import 'package:revanced_manager/services/github_api.dart';
|
||||||
class ManagerAPI {
|
class ManagerAPI {
|
||||||
final GithubAPI _githubAPI = GithubAPI();
|
final GithubAPI _githubAPI = GithubAPI();
|
||||||
|
|
||||||
Future<File?> downloadPatches() async {
|
Future<File?> downloadPatches(String extension) async {
|
||||||
return await _githubAPI.latestReleaseFile(ghOrg, patchesRepo);
|
return await _githubAPI.latestReleaseFile(extension, ghOrg, patchesRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadIntegrations() async {
|
Future<File?> downloadIntegrations(String extension) async {
|
||||||
return await _githubAPI.latestReleaseFile(ghOrg, integrationsRepo);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File?> downloadManager() async {
|
|
||||||
return await _githubAPI.latestReleaseFile(
|
return await _githubAPI.latestReleaseFile(
|
||||||
'Aunali321',
|
extension,
|
||||||
'revanced-manager-flutter',
|
ghOrg,
|
||||||
|
integrationsRepo,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<File?> downloadManager(String extension) async {
|
||||||
|
return await _githubAPI.latestReleaseFile(extension, ghOrg, managerRepo);
|
||||||
|
}
|
||||||
|
|
||||||
Future<String?> getLatestPatchesVersion() async {
|
Future<String?> getLatestPatchesVersion() async {
|
||||||
return await _githubAPI.latestReleaseVersion(ghOrg, patchesRepo);
|
return await _githubAPI.latestReleaseVersion(ghOrg, patchesRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getLatestManagerVersion() async {
|
Future<String?> getLatestManagerVersion() async {
|
||||||
return await _githubAPI.latestReleaseVersion(
|
return await _githubAPI.latestReleaseVersion(ghOrg, managerRepo);
|
||||||
'Aunali321',
|
|
||||||
'revanced-manager-flutter',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getCurrentManagerVersion() async {
|
Future<String> getCurrentManagerVersion() async {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
||||||
import 'package:app_installer/app_installer.dart';
|
import 'package:app_installer/app_installer.dart';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_archive/flutter_archive.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
|
@ -21,29 +22,47 @@ class PatcherAPI {
|
||||||
Directory? _tmpDir;
|
Directory? _tmpDir;
|
||||||
Directory? _workDir;
|
Directory? _workDir;
|
||||||
Directory? _cacheDir;
|
Directory? _cacheDir;
|
||||||
File? _patchBundleFile;
|
File? _zipPatchBundleFile;
|
||||||
File? _integrations;
|
File? _integrations;
|
||||||
File? _inputFile;
|
File? _inputFile;
|
||||||
File? _patchedFile;
|
File? _patchedFile;
|
||||||
File? _outFile;
|
File? _outFile;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loadPatches() async {
|
Future<void> loadPatches() async {
|
||||||
if (_patchBundleFile == null) {
|
if (_cacheDir == null) {
|
||||||
_patchBundleFile = await _managerAPI.downloadPatches();
|
await initPatcher();
|
||||||
if (_patchBundleFile != null) {
|
}
|
||||||
|
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>(
|
await patcherChannel.invokeMethod<bool>(
|
||||||
'loadPatches',
|
'loadPatches',
|
||||||
{
|
{
|
||||||
'pathBundlesPaths': <String>[_patchBundleFile!.absolute.path],
|
'zipPatchBundlePath': _zipPatchBundleFile!.path,
|
||||||
|
'cacheDirPath': _cacheDir!.path,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps() async {
|
Future<List<ApplicationWithIcon>> getFilteredInstalledApps() async {
|
||||||
List<ApplicationWithIcon> filteredPackages = [];
|
List<ApplicationWithIcon> filteredPackages = [];
|
||||||
if (_patchBundleFile != null) {
|
if (_zipPatchBundleFile != null) {
|
||||||
try {
|
try {
|
||||||
List<String>? patchesPackages = await patcherChannel
|
List<String>? patchesPackages = await patcherChannel
|
||||||
.invokeListMethod<String>('getCompatiblePackages');
|
.invokeListMethod<String>('getCompatiblePackages');
|
||||||
|
@ -71,7 +90,7 @@ class PatcherAPI {
|
||||||
PatchedApplication? selectedApp,
|
PatchedApplication? selectedApp,
|
||||||
) async {
|
) async {
|
||||||
List<Patch> filteredPatches = [];
|
List<Patch> filteredPatches = [];
|
||||||
if (_patchBundleFile != null && selectedApp != null) {
|
if (_zipPatchBundleFile != null && selectedApp != null) {
|
||||||
try {
|
try {
|
||||||
var patches =
|
var patches =
|
||||||
await patcherChannel.invokeListMethod<Map<dynamic, dynamic>>(
|
await patcherChannel.invokeListMethod<Map<dynamic, dynamic>>(
|
||||||
|
@ -112,7 +131,7 @@ class PatcherAPI {
|
||||||
PatchedApplication? selectedApp,
|
PatchedApplication? selectedApp,
|
||||||
) async {
|
) async {
|
||||||
List<Patch> appliedPatches = [];
|
List<Patch> appliedPatches = [];
|
||||||
if (_patchBundleFile != null && selectedApp != null) {
|
if (_zipPatchBundleFile != null && selectedApp != null) {
|
||||||
try {
|
try {
|
||||||
var patches =
|
var patches =
|
||||||
await patcherChannel.invokeListMethod<Map<dynamic, dynamic>>(
|
await patcherChannel.invokeListMethod<Map<dynamic, dynamic>>(
|
||||||
|
@ -148,17 +167,12 @@ class PatcherAPI {
|
||||||
return appliedPatches;
|
return appliedPatches;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initPatcher(bool mergeIntegrations) async {
|
Future<void> mergeIntegrations(bool mergeIntegrations) async {
|
||||||
if (mergeIntegrations) {
|
if (mergeIntegrations) {
|
||||||
_integrations = await _managerAPI.downloadIntegrations();
|
_integrations = await _managerAPI.downloadIntegrations('.apk');
|
||||||
|
} else {
|
||||||
|
_integrations = null;
|
||||||
}
|
}
|
||||||
_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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> runPatcher(
|
Future<void> runPatcher(
|
||||||
|
@ -234,4 +248,26 @@ class PatcherAPI {
|
||||||
await _rootAPI.deleteApp(patchedApp.packageName, patchedApp.apkFilePath);
|
await _rootAPI.deleteApp(patchedApp.packageName, patchedApp.apkFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ class HomeViewModel extends BaseViewModel {
|
||||||
toastLength: Toast.LENGTH_LONG,
|
toastLength: Toast.LENGTH_LONG,
|
||||||
gravity: ToastGravity.CENTER,
|
gravity: ToastGravity.CENTER,
|
||||||
);
|
);
|
||||||
File? managerApk = await _managerAPI.downloadManager();
|
File? managerApk = await _managerAPI.downloadManager('.apk');
|
||||||
if (managerApk != null) {
|
if (managerApk != null) {
|
||||||
flutterLocalNotificationsPlugin.show(
|
flutterLocalNotificationsPlugin.show(
|
||||||
0,
|
0,
|
||||||
|
|
|
@ -111,7 +111,7 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
'com.google.android.apps.youtube.music') {
|
'com.google.android.apps.youtube.music') {
|
||||||
resourcePatching = true;
|
resourcePatching = true;
|
||||||
}
|
}
|
||||||
await _patcherAPI.initPatcher(mergeIntegrations);
|
await _patcherAPI.mergeIntegrations(mergeIntegrations);
|
||||||
await _patcherAPI.runPatcher(
|
await _patcherAPI.runPatcher(
|
||||||
apkFilePath,
|
apkFilePath,
|
||||||
_patches,
|
_patches,
|
||||||
|
|
|
@ -21,6 +21,7 @@ dependencies:
|
||||||
file_picker: ^5.0.1
|
file_picker: ^5.0.1
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_archive: ^5.0.0
|
||||||
flutter_background: ^1.1.0
|
flutter_background: ^1.1.0
|
||||||
flutter_cache_manager: ^3.3.0
|
flutter_cache_manager: ^3.3.0
|
||||||
flutter_i18n: ^0.32.4
|
flutter_i18n: ^0.32.4
|
||||||
|
|
Loading…
Reference in a new issue