diff --git a/android/app/build.gradle b/android/app/build.gradle
index 9f8312e7..7f8b88f9 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -85,7 +85,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// ReVanced
- implementation "app.revanced:revanced-patcher:14.2.2"
+ implementation "app.revanced:revanced-patcher:16.0.0"
// Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 08e9e77c..3a2c4f8c 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -42,6 +42,10 @@
+
+
diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt
new file mode 100644
index 00000000..f5c7ad69
--- /dev/null
+++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/ExportSettingsActivity.kt
@@ -0,0 +1,86 @@
+package app.revanced.manager.flutter
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.util.Base64
+import org.json.JSONObject
+import java.io.ByteArrayInputStream
+import java.io.File
+import java.security.cert.CertificateFactory
+import java.security.cert.X509Certificate
+import java.security.MessageDigest
+
+class ExportSettingsActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val callingPackageName = getCallingPackage()!!
+
+ if (getFingerprint(callingPackageName) == getFingerprint(getPackageName())) {
+ // Create JSON Object
+ val json = JSONObject()
+
+ // Default Data
+ json.put("keystorePassword", "s3cur3p@ssw0rd")
+
+ // Load Shared Preferences
+ val sharedPreferences = getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
+ val allEntries: Map = sharedPreferences.getAll()
+ for ((key, value) in allEntries.entries) {
+ json.put(
+ key.replace("flutter.", ""),
+ if (value is Boolean) if (value) 1 else 0 else value
+ )
+ }
+
+ // Load keystore
+ val keystoreFile = File(getExternalFilesDir(null), "/revanced-manager.keystore")
+ if (keystoreFile.exists()) {
+ val keystoreBytes = keystoreFile.readBytes()
+ val keystoreBase64 = Base64.encodeToString(keystoreBytes, Base64.DEFAULT)
+ json.put("keystore", keystoreBase64)
+ }
+
+ // Load saved patches
+ val storedPatchesFile = File(filesDir.parentFile.absolutePath, "/app_flutter/selected-patches.json")
+ if (storedPatchesFile.exists()) {
+ val patchesBytes = storedPatchesFile.readBytes()
+ val patches = String(patchesBytes, Charsets.UTF_8)
+ json.put("patches", JSONObject(patches))
+ }
+
+ // Send data back
+ val resultIntent = Intent()
+ resultIntent.putExtra("data", json.toString())
+ setResult(Activity.RESULT_OK, resultIntent)
+ finish()
+ } else {
+ val resultIntent = Intent()
+ setResult(Activity.RESULT_CANCELED)
+ finish()
+ }
+ }
+
+ fun getFingerprint(packageName: String): String {
+ // Get the signature of the app that matches the package name
+ val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
+ val signature = packageInfo.signatures[0]
+
+ // Get the raw certificate data
+ val rawCert = signature.toByteArray()
+
+ // Generate an X509Certificate from the data
+ val certFactory = CertificateFactory.getInstance("X509")
+ val x509Cert = certFactory.generateCertificate(ByteArrayInputStream(rawCert)) as X509Certificate
+
+ // Get the SHA256 fingerprint
+ val fingerprint = MessageDigest.getInstance("SHA256").digest(x509Cert.encoded).joinToString("") {
+ "%02x".format(it)
+ }
+
+ return fingerprint
+ }
+}
diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt
index 22463f8c..b88abb82 100644
--- a/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt
+++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt
@@ -8,22 +8,21 @@ import app.revanced.manager.flutter.utils.signing.Signer
import app.revanced.manager.flutter.utils.zip.ZipFile
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
import app.revanced.patcher.PatchBundleLoader
+import app.revanced.patcher.PatchSet
import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions
-import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
-import app.revanced.patcher.extensions.PatchExtensions.dependencies
-import app.revanced.patcher.extensions.PatchExtensions.description
-import app.revanced.patcher.extensions.PatchExtensions.include
-import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.PatchResult
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
+import org.json.JSONArray
+import org.json.JSONObject
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
+import java.lang.Error
import java.util.logging.LogRecord
import java.util.logging.Logger
@@ -33,6 +32,8 @@ class MainActivity : FlutterActivity() {
private var cancel: Boolean = false
private var stopResult: MethodChannel.Result? = null
+ private lateinit var patches: PatchSet
+
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
@@ -48,7 +49,6 @@ class MainActivity : FlutterActivity() {
mainChannel.setMethodCallHandler { call, result ->
when (call.method) {
"runPatcher" -> {
- val patchBundleFilePath = call.argument("patchBundleFilePath")
val originalFilePath = call.argument("originalFilePath")
val inputFilePath = call.argument("inputFilePath")
val patchedFilePath = call.argument("patchedFilePath")
@@ -59,7 +59,7 @@ class MainActivity : FlutterActivity() {
val keyStoreFilePath = call.argument("keyStoreFilePath")
val keystorePassword = call.argument("keystorePassword")
- if (patchBundleFilePath != null &&
+ if (
originalFilePath != null &&
inputFilePath != null &&
patchedFilePath != null &&
@@ -73,7 +73,6 @@ class MainActivity : FlutterActivity() {
cancel = false
runPatcher(
result,
- patchBundleFilePath,
originalFilePath,
inputFilePath,
patchedFilePath,
@@ -93,29 +92,44 @@ class MainActivity : FlutterActivity() {
}
"getPatches" -> {
- val patchBundleFilePath = call.argument("patchBundleFilePath")
- val cacheDirPath = call.argument("cacheDirPath")
+ val patchBundleFilePath = call.argument("patchBundleFilePath")!!
+ val cacheDirPath = call.argument("cacheDirPath")!!
- if (patchBundleFilePath != null) {
- val patches = PatchBundleLoader.Dex(
+ try {
+ patches = PatchBundleLoader.Dex(
File(patchBundleFilePath),
optimizedDexDirectory = File(cacheDirPath)
- ).map { patch ->
- val map = HashMap()
- map["\"name\""] = "\"${patch.patchName.replace("\"","\\\"")}\""
- map["\"description\""] = "\"${patch.description?.replace("\"","\\\"")}\""
- map["\"excluded\""] = !patch.include
- map["\"dependencies\""] = patch.dependencies?.map { "\"${it.java.patchName}\"" } ?: emptyList()
- map["\"compatiblePackages\""] = patch.compatiblePackages?.map {
- val map2 = HashMap()
- map2["\"name\""] = "\"${it.name}\""
- map2["\"versions\""] = it.versions.map { version -> "\"${version}\"" }
- map2
- } ?: emptyList()
- map
+ )
+ } catch (ex: Exception) {
+ return@setMethodCallHandler result.notImplemented()
+ } catch (err: Error) {
+ return@setMethodCallHandler result.notImplemented()
+ }
+
+ JSONArray().apply {
+ patches.forEach {
+ JSONObject().apply {
+ put("name", it.name)
+ put("description", it.description)
+ put("excluded", !it.use)
+ put("compatiblePackages", JSONArray().apply {
+ it.compatiblePackages?.forEach { compatiblePackage ->
+ val compatiblePackageJson = JSONObject().apply {
+ put("name", compatiblePackage.name)
+ put(
+ "versions",
+ JSONArray().apply {
+ compatiblePackage.versions?.forEach { version ->
+ put(version)
+ }
+ })
+ }
+ put(compatiblePackageJson)
+ }
+ })
+ }.let(::put)
}
- result.success(patches)
- } else result.notImplemented()
+ }.toString().let(result::success)
}
else -> result.notImplemented()
@@ -125,7 +139,6 @@ class MainActivity : FlutterActivity() {
private fun runPatcher(
result: MethodChannel.Result,
- patchBundleFilePath: String,
originalFilePath: String,
inputFilePath: String,
patchedFilePath: String,
@@ -168,8 +181,11 @@ class MainActivity : FlutterActivity() {
}
object : java.util.logging.Handler() {
- override fun publish(record: LogRecord) =
+ override fun publish(record: LogRecord) {
+ if (record.loggerName?.startsWith("app.revanced") != true) return
+
updateProgress(-1.0, "", record.message)
+ }
override fun flush() = Unit
override fun close() = flush()
@@ -209,10 +225,7 @@ class MainActivity : FlutterActivity() {
updateProgress(0.1, "Loading patches...", "Loading patches")
- val patches = PatchBundleLoader.Dex(
- File(patchBundleFilePath),
- optimizedDexDirectory = cacheDir
- ).filter { patch ->
+ val patches = patches.filter { patch ->
val isCompatible = patch.compatiblePackages?.any {
it.name == patcher.context.packageMetadata.packageName
} ?: false
@@ -220,7 +233,7 @@ class MainActivity : FlutterActivity() {
val compatibleOrUniversal =
isCompatible || patch.compatiblePackages.isNullOrEmpty()
- compatibleOrUniversal && selectedPatches.any { it == patch.patchName }
+ compatibleOrUniversal && selectedPatches.any { it == patch.name }
}
if (cancel) {
@@ -251,9 +264,9 @@ class MainActivity : FlutterActivity() {
val msg = patchResult.exception?.let {
val writer = StringWriter()
it.printStackTrace(PrintWriter(writer))
- "${patchResult.patchName} failed: $writer"
+ "${patchResult.patch.name} failed: $writer"
} ?: run {
- "${patchResult.patchName} succeeded"
+ "${patchResult.patch.name} succeeded"
}
updateProgress(progress, "", msg)
@@ -317,7 +330,7 @@ class MainActivity : FlutterActivity() {
val stack = ex.stackTraceToString()
updateProgress(
-100.0,
- "Aborted",
+ "Failed",
"An error occurred:\n$stack"
)
}
diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json
index 05694ced..d3fe1128 100644
--- a/assets/i18n/en_US.json
+++ b/assets/i18n/en_US.json
@@ -23,13 +23,13 @@
"widgetTitle": "Dashboard",
"updatesSubtitle": "Updates",
- "patchedSubtitle": "Patched applications",
+ "patchedSubtitle": "Patched apps",
"noUpdates": "No updates available",
"WIP": "Work in progress...",
- "noInstallations": "No patched applications installed",
+ "noInstallations": "No patched apps installed",
"installUpdate": "Continue to install the update?",
"updateDialogTitle": "Update Manager",
@@ -56,9 +56,7 @@
"updatesDisabled": "Updating a patched app is currently disabled. Repatch the app again."
},
"applicationItem": {
- "patchButton": "Patch",
- "infoButton": "Info",
- "changelogLabel": "Changelog"
+ "infoButton": "Info"
},
"latestCommitCard": {
"loadingLabel": "Loading...",
@@ -71,9 +69,8 @@
"widgetTitle": "Patcher",
"patchButton": "Patch",
- "patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?",
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?",
- "splitApkWarningDialogText": "Patching a split APK is not yet supported and might fail. Proceed anyways?",
+
"removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?"
},
"appSelectorCard": {
@@ -148,9 +145,8 @@
"installTypeDescription": "Select the installation type to proceed with.",
"installButton": "Install",
- "installRootType": "Root",
- "installNonRootType": "Non-root",
- "installRecommendedType": "Recommended",
+ "installRootType": "Mount",
+ "installNonRootType": "Normal",
"pressBackAgain": "Press back again to cancel",
"openButton": "Open",
@@ -162,10 +158,6 @@
"exportApkButtonTooltip": "Export patched APK",
"exportLogButtonTooltip": "Export log",
- "installErrorDialogTitle": "Error",
- "installErrorDialogText1": "Root install is not possible with the current patches selection.\nRepatch your app or choose non-root install.",
- "installErrorDialogText2": "Non-root install is not possible with the current patches selection.\nRepatch your app or choose root install if you have your device rooted.",
- "installErrorDialogText3": "Root install is not possible as the original APK was selected from storage.\nSelect an installed app or choose non-root install.",
"noExit": "Installer is still running, cannot exit..."
},
"settingsView": {
@@ -285,7 +277,6 @@
"rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.",
"packageNameLabel": "Package name",
- "originalPackageNameLabel": "Original package name",
"installTypeLabel": "Installation type",
"rootTypeLabel": "Root",
"nonRootTypeLabel": "Non-root",
diff --git a/docs/4_building.md b/docs/4_building.md
index d4c0887c..adcf47dd 100644
--- a/docs/4_building.md
+++ b/docs/4_building.md
@@ -12,7 +12,7 @@ This page will guide you through building ReVanced Manager from source.
3. Create a GitHub personal access token with the `read:packages` scope [here](https://github.com/settings/tokens/new?scopes=read:packages&description=ReVanced)
-4. Add your GitHub username and the token to `~/.gradle/gradle.properties`
+4. Add your GitHub username and the token to `~/android/gradle.properties`
```properties
gpr.user = YourUsername
diff --git a/lib/models/patch.dart b/lib/models/patch.dart
index 7acf05ba..fe2b3c33 100644
--- a/lib/models/patch.dart
+++ b/lib/models/patch.dart
@@ -1,5 +1,4 @@
import 'package:json_annotation/json_annotation.dart';
-import 'package:revanced_manager/utils/string.dart';
part 'patch.g.dart';
@@ -9,26 +8,19 @@ class Patch {
required this.name,
required this.description,
required this.excluded,
- required this.dependencies,
required this.compatiblePackages,
});
factory Patch.fromJson(Map json) => _$PatchFromJson(json);
final String name;
- final String description;
+ final String? description;
final bool excluded;
- final List dependencies;
final List compatiblePackages;
Map toJson() => _$PatchToJson(this);
String getSimpleName() {
- return name
- .replaceAll('-', ' ')
- .split('-')
- .join(' ')
- .toTitleCase()
- .replaceFirst('Microg', 'MicroG');
+ return name;
}
}
diff --git a/lib/models/patched_application.dart b/lib/models/patched_application.dart
index 90bfb9a3..23517595 100644
--- a/lib/models/patched_application.dart
+++ b/lib/models/patched_application.dart
@@ -9,23 +9,19 @@ class PatchedApplication {
PatchedApplication({
required this.name,
required this.packageName,
- required this.originalPackageName,
required this.version,
required this.apkFilePath,
required this.icon,
required this.patchDate,
this.isRooted = false,
this.isFromStorage = false,
- this.hasUpdates = false,
this.appliedPatches = const [],
- this.changelog = const [],
});
factory PatchedApplication.fromJson(Map json) =>
_$PatchedApplicationFromJson(json);
String name;
String packageName;
- String originalPackageName;
String version;
final String apkFilePath;
@JsonKey(
@@ -36,9 +32,7 @@ class PatchedApplication {
DateTime patchDate;
bool isRooted;
bool isFromStorage;
- bool hasUpdates;
List appliedPatches;
- List changelog;
Map toJson() => _$PatchedApplicationToJson(this);
diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart
index 0949f1b9..8d3244b3 100644
--- a/lib/services/github_api.dart
+++ b/lib/services/github_api.dart
@@ -1,4 +1,3 @@
-import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
@@ -7,7 +6,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart';
import 'package:revanced_manager/app/app.locator.dart';
-import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/services/manager_api.dart';
@lazySingleton
@@ -21,17 +19,6 @@ class GithubAPI {
priority: CachePriority.high,
);
- final Map repoAppPath = {
- 'com.google.android.youtube': 'youtube',
- 'com.google.android.apps.youtube.music': 'music',
- 'com.twitter.android': 'twitter',
- 'com.reddit.frontpage': 'reddit',
- 'com.zhiliaoapp.musically': 'tiktok',
- 'de.dwd.warnapp': 'warnwetter',
- 'com.garzotto.pflotsh.ecmwf_a': 'ecmwf',
- 'com.spotify.music': 'spotify',
- };
-
Future initialize(String repoUrl) async {
try {
_dio = Dio(
@@ -142,38 +129,6 @@ class GithubAPI {
}
}
- Future> getCommits(
- String packageName,
- String repoName,
- DateTime since,
- ) async {
- final String path =
- 'src/main/kotlin/app/revanced/patches/${repoAppPath[packageName]}';
- try {
- final response = await _dio.get(
- '/repos/$repoName/commits',
- queryParameters: {
- 'path': path,
- 'since': since.toIso8601String(),
- },
- );
- final List commits = response.data;
- return commits
- .map(
- (commit) => commit['commit']['message'].split('\n')[0] +
- ' - ' +
- commit['commit']['author']['name'] +
- '\n' as String,
- )
- .toList();
- } on Exception catch (e) {
- if (kDebugMode) {
- print(e);
- }
- }
- return [];
- }
-
Future getLatestReleaseFile(
String extension,
String repoName,
@@ -237,30 +192,4 @@ class GithubAPI {
}
return null;
}
-
- Future> getPatches(
- String repoName,
- String version,
- String url,
- ) async {
- List patches = [];
- try {
- final File? f = await getPatchesReleaseFile(
- '.json',
- repoName,
- version,
- url,
- );
- if (f != null) {
- final List list = jsonDecode(f.readAsStringSync());
- patches = list.map((patch) => Patch.fromJson(patch)).toList();
- }
- } on Exception catch (e) {
- if (kDebugMode) {
- print(e);
- }
- }
-
- return patches;
- }
}
diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart
index 3401432b..d3eed845 100644
--- a/lib/services/manager_api.dart
+++ b/lib/services/manager_api.dart
@@ -42,12 +42,14 @@ class ManagerAPI {
String defaultManagerRepo = 'revanced/revanced-manager';
String? patchesVersion = '';
String? integrationsVersion = '';
+
bool isDefaultPatchesRepo() {
return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches';
}
bool isDefaultIntegrationsRepo() {
- return getIntegrationsRepo().toLowerCase() == 'revanced/revanced-integrations';
+ return getIntegrationsRepo().toLowerCase() ==
+ 'revanced/revanced-integrations';
}
Future initialize() async {
@@ -309,24 +311,24 @@ class ManagerAPI {
final Directory appCache = await getTemporaryDirectory();
Directory('${appCache.path}/cache').createSync();
final Directory workDir =
- Directory('${appCache.path}/cache').createTempSync('tmp-');
+ Directory('${appCache.path}/cache').createTempSync('tmp-');
final Directory cacheDir = Directory('${workDir.path}/cache');
cacheDir.createSync();
if (patchBundleFile != null) {
try {
- final patchesObject = await PatcherAPI.patcherChannel.invokeMethod(
+ final String patchesJson = await PatcherAPI.patcherChannel.invokeMethod(
'getPatches',
{
'patchBundleFilePath': patchBundleFile.path,
'cacheDirPath': cacheDir.path,
},
);
- final List