feat: add Patcher API.

This commit is contained in:
Alberto Ponces 2022-08-06 14:04:18 +01:00
parent 57b932fd23
commit fc06f8d571
13 changed files with 403 additions and 93 deletions

103
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,103 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build (Serializer)",
"type": "shell",
"command": "flutter packages pub run build_runner build --delete-conflicting-outputs",
"problemMatcher": []
},
{
"label": "Build (Android)",
"type": "shell",
"command": "flutter build apk",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Install (Android)",
"type": "shell",
"command": "adb install build\\app\\outputs\\flutter-apk\\app-release.apk",
"problemMatcher": []
},
{
"label": "Clean (Flutter)",
"type": "shell",
"command": "flutter clean && flutter pub get",
"problemMatcher": []
},
{
"label": "Clean (Serializer)",
"type": "shell",
"command": "flutter packages pub run build_runner clean",
"problemMatcher": []
},
{
"label": "Build all (Android)",
"dependsOrder": "sequence",
"dependsOn": [
"Build (Serializer)",
"Build (Android)"
],
"problemMatcher": []
},
{
"label": "Clean all",
"dependsOrder": "sequence",
"dependsOn": [
"Clean (Flutter)",
"Clean (Serializer)"
],
"problemMatcher": []
},
{
"label": "Clean all & Build all (Android)",
"dependsOrder": "sequence",
"dependsOn": [
"Clean all",
"Build all (Android)"
],
"problemMatcher": []
},
{
"label": "Clean all & Install (Android)",
"dependsOrder": "sequence",
"dependsOn": [
"Clean all",
"Build all (Android)",
"Install (Android)",
],
"problemMatcher": []
},
{
"label": "Build & Install (Android)",
"dependsOrder": "sequence",
"dependsOn": [
"Build (Android)",
"Install (Android)"
],
"problemMatcher": []
},
{
"label": "Validate dependencies",
"type": "shell",
"command": "flutter pub pub run dependency_validator",
"problemMatcher": []
},
{
"label": "Show upgradable dependencies",
"type": "shell",
"command": "flutter pub outdated",
"problemMatcher": []
},
{
"label": "Validate translations",
"type": "shell",
"command": "flutter pub run flutter_i18n diff en.json pt.json",
"problemMatcher": []
}
]
}

View File

@ -43,10 +43,7 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "app.revanced.revanced_manager_flutter"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
@ -55,8 +52,6 @@ android {
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
@ -68,4 +63,7 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// ReVanced
implementation "app.revanced:revanced-patcher:3.3.1"
}

View File

@ -1,8 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.revanced.revanced_manager_flutter">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -5,7 +5,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application
android:label="revanced_manager_flutter"
android:label="ReVanced Manager"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true">
@ -17,10 +17,6 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
@ -30,8 +26,6 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />

View File

@ -1,6 +1,87 @@
package app.revanced.revanced_manager_flutter
import androidx.annotation.NonNull
import app.revanced.patcher.data.Data
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.description
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.extensions.PatchExtensions.version
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.util.patch.implementation.DexPatchBundle
import dalvik.system.DexClassLoader
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
class MainActivity : FlutterActivity() {
private val CHANNEL = "app.revanced/patcher"
private var patches = mutableListOf<Class<out Patch<Data>>>()
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"loadPatches" -> {
val pathBundlesPaths = call.argument<List<String>>("pathBundlesPaths")
if (pathBundlesPaths != null) {
loadPatches(pathBundlesPaths)
result.success("OK")
} else {
result.notImplemented()
}
}
"getCompatiblePackages" -> result.success(getCompatiblePackages())
"getFilteredPatches" -> {
val targetPackage = call.argument<String>("targetPackage")
val targetVersion = call.argument<String>("targetVersion")
val ignoreVersion = call.argument<Boolean>("ignoreVersion")
if (targetPackage != null && targetVersion != null && ignoreVersion != null) {
result.success(getFilteredPatches(targetPackage, targetVersion, ignoreVersion))
} else {
result.notImplemented()
}
}
else -> result.notImplemented()
}
}
}
fun loadPatches(pathBundlesPaths: List<String>) {
pathBundlesPaths.forEach { path ->
patches.addAll(DexPatchBundle(
path, DexClassLoader(
path,
context.cacheDir.path,
null,
javaClass.classLoader
)
).loadPatches())
}
}
fun getCompatiblePackages(): List<String> {
val filteredPackages = mutableListOf<String>()
patches.forEach patch@{ patch ->
patch.compatiblePackages?.forEach { pkg ->
filteredPackages.add(pkg.name)
}
}
return filteredPackages.distinct()
}
fun getFilteredPatches(targetPackage: String, targetVersion: String, ignoreVersion: Boolean): List<Map<String, String?>> {
val filteredPatches = mutableListOf<Map<String, String?>>()
patches.forEach patch@{ patch ->
patch.compatiblePackages?.forEach { pkg ->
if (pkg.name == targetPackage && (ignoreVersion || pkg.versions.isNotEmpty() || pkg.versions.contains(targetVersion))) {
var p = mutableMapOf<String, String?>();
p.put("name", patch.patchName);
p.put("version", patch.version);
p.put("description", patch.description);
filteredPatches.add(p)
}
}
}
return filteredPatches
}
}

View File

@ -1,8 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.revanced.revanced_manager_flutter">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -15,14 +15,19 @@ allprojects {
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
credentials {
username = (project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR")) as String
password = (project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")) as String
}
}
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}

13
lib/models/patch.dart Normal file
View File

@ -0,0 +1,13 @@
class Patch {
final String name;
final String simpleName;
final String version;
final String description;
Patch({
required this.name,
required this.simpleName,
required this.version,
required this.description,
});
}

View File

@ -8,7 +8,13 @@ class GithubAPI {
Future<String?> latestRelease(String org, repoName) async {
var latestRelease = await github.repositories
.getLatestRelease(RepositorySlug(org, repoName));
var dlurl = latestRelease.assets?.first.browserDownloadUrl;
var dlurl = latestRelease.assets
?.firstWhere((asset) =>
asset.name != null &&
asset.name!.endsWith('.dex') &&
!asset.name!.contains('-sources') &&
!asset.name!.contains('-javadoc'))
.browserDownloadUrl;
return dlurl;
}

View File

@ -0,0 +1,100 @@
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:installed_apps/app_info.dart';
import 'package:installed_apps/installed_apps.dart';
import 'package:revanced_manager_flutter/models/patch.dart';
import 'package:revanced_manager_flutter/services/github_api.dart';
import 'package:revanced_manager_flutter/utils/string.dart';
class PatcherService {
File? _patchBundleFile;
final List<AppInfo> _filteredPackages = [];
final Map<String, List<Patch>> _filteredPatches = <String, List<Patch>>{};
final GithubAPI githubAPI = GithubAPI();
static const platform = MethodChannel('app.revanced/patcher');
static final PatcherService _instance = PatcherService.internal();
factory PatcherService() => _instance;
PatcherService.internal();
Future<void> loadPatches() async {
if (_patchBundleFile == null) {
String? dexFileUrl =
await githubAPI.latestRelease('revanced', 'revanced-patches');
if (dexFileUrl != null) {
_patchBundleFile =
await DefaultCacheManager().getSingleFile(dexFileUrl);
try {
await platform.invokeMethod(
'loadPatches',
{
'pathBundlesPaths': <String>[_patchBundleFile!.absolute.path],
},
);
} on PlatformException {
_patchBundleFile = null;
}
}
}
}
Future<List<AppInfo>> getFilteredInstalledApps() async {
if (_patchBundleFile != null && _filteredPackages.isEmpty) {
List<AppInfo> all = await InstalledApps.getInstalledApps(false, true);
try {
List<String>? patchesPackages =
await platform.invokeListMethod<String>('getCompatiblePackages');
if (patchesPackages != null) {
for (AppInfo app in all) {
if (patchesPackages.contains(app.packageName)) {
_filteredPackages.add(app);
}
}
}
} on Exception {
return List.empty();
}
}
return _filteredPackages;
}
Future<List<Patch>?> getFilteredPatches(AppInfo? targetApp) async {
if (_patchBundleFile != null && targetApp != null) {
if (_filteredPatches[targetApp.packageName] == null ||
_filteredPatches[targetApp.packageName]!.isEmpty) {
_filteredPatches[targetApp.packageName!] = [];
try {
var patches = await platform.invokeListMethod<Map<dynamic, dynamic>>(
'getFilteredPatches',
{
'targetPackage': targetApp.packageName,
'targetVersion': targetApp.versionName,
'ignoreVersion': true,
},
);
if (patches != null) {
for (var patch in patches) {
_filteredPatches[targetApp.packageName]!.add(
Patch(
name: patch['name'],
simpleName: (patch['name'] as String)
.replaceAll('-', ' ')
.split('-')
.join(' ')
.toTitleCase(),
version: patch['version'] ?? 'unknown',
description: patch['description'] ?? 'unknown',
),
);
}
}
} on Exception {
return List.empty();
}
}
} else {
return List.empty();
}
return _filteredPatches[targetApp.packageName];
}
}

8
lib/utils/string.dart Normal file
View File

@ -0,0 +1,8 @@
extension StringCasingExtension on String {
String toCapitalized() =>
length > 0 ? '${this[0].toUpperCase()}${substring(1).toLowerCase()}' : '';
String toTitleCase() => replaceAll(RegExp(' +'), ' ')
.split(' ')
.map((str) => str.toCapitalized())
.join(' ');
}

View File

@ -209,6 +209,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_cache_manager:
dependency: "direct main"
description:
name: flutter_cache_manager
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.0"
flutter_lints:
dependency: "direct dev"
description:
@ -312,6 +319,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.4"
installed_apps:
dependency: "direct main"
description:
name: installed_apps
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
io:
dependency: transitive
description:
@ -327,12 +341,19 @@ packages:
source: hosted
version: "0.6.4"
json_annotation:
dependency: transitive
dependency: "direct main"
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.6.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
url: "https://pub.dartlang.org"
source: hosted
version: "6.3.1"
lints:
dependency: transitive
description:
@ -466,6 +487,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
petitparser:
dependency: transitive
description:
@ -529,6 +557,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
rxdart:
dependency: transitive
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
version: "0.27.5"
shelf:
dependency: transitive
description:
@ -555,6 +590,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.2"
source_helper:
dependency: transitive
description:
name: source_helper
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.2"
source_span:
dependency: transitive
description:
@ -562,6 +604,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
sqflite:
dependency: transitive
description:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3+1"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1+1"
stack_trace:
dependency: transitive
description:
@ -618,6 +674,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
synchronized:
dependency: transitive
description:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0+2"
term_glyph:
dependency: transitive
description:
@ -653,6 +716,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
uuid:
dependency: transitive
description:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.6"
vector_math:
dependency: transitive
description:

View File

@ -1,41 +1,23 @@
name: revanced_manager_flutter
description: A new Flutter project.
description: The unofficial ReVanced Manager based on Flutter.
homepage: https://github.com/Aunali321/revanced-manager
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
publish_to: 'none'
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.17.5 <3.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
flutter_cache_manager: ^3.3.0
flutter_svg: ^1.1.1+1
google_fonts: ^3.0.1
installed_apps: ^1.3.1
json_annotation: ^4.6.0
#networking
http: ^0.13.4
@ -59,54 +41,12 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
flutter_lints: ^2.0.1
build_runner: ^2.2.0
injectable_generator: ^1.5.4
json_serializable: ^6.3.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- lib/assets/images/
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Manrope
# fonts:
# - asset: lib/assets/fonts/Manrope.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages