Compare commits
16
Commits
4b6c3e3123
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
937729130f | ||
|
|
b73ca08003 | ||
|
|
42622a1bca | ||
|
|
37ef795322 | ||
|
|
97b4fc5b5a | ||
|
|
7427432759 | ||
|
|
015fe10a23 | ||
|
|
9cb66197cf | ||
|
|
b632d0f042 | ||
|
|
fca2470990 | ||
|
|
66b6c8c8ed | ||
|
|
4b699da220 | ||
|
|
e19275fb7d | ||
|
|
4bc8c7c0f6 | ||
|
|
e51c5292c1 | ||
|
|
43688d0622 |
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package app.revanced.patches.spotify.premium.interceptors
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.Protocol
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
|
|
||||||
|
class ProductStateInterceptor : Interceptor {
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
if (request.url.encodedPath.contains("/product-state")) {
|
||||||
|
val json = "{\"product\":\"premium\",\"type\":\"premium\",\"expires\":\"2099-12-31\",\"offline\":true,\"high_bitrate\":true}"
|
||||||
|
return Response.Builder()
|
||||||
|
.request(request)
|
||||||
|
.protocol(Protocol.HTTP_1_1)
|
||||||
|
.code(200)
|
||||||
|
.message("OK")
|
||||||
|
.body(json.toResponseBody("application/json".toMediaType()))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
return chain.proceed(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,40 @@
|
|||||||
|
# [6.1.0](https://github.com/ReVanced/revanced-patches/compare/v6.0.1...v6.1.0) (2026-03-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Export internal data documents provider:** Correct S_IFLNK constant and symlink detection mask ([#6819](https://github.com/ReVanced/revanced-patches/issues/6819)) ([252617b](https://github.com/ReVanced/revanced-patches/commit/252617b8dd3f24e1ff9a04ba1d91b43dc29bd757))
|
||||||
|
* **YouTube - Custom branding:** Fix double icons and change default branding to ReVanced ([#6806](https://github.com/ReVanced/revanced-patches/issues/6806)) ([e51c529](https://github.com/ReVanced/revanced-patches/commit/e51c5292c171325e7cfa0f5ee85474d9b3961a34))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `Spoof root of trust` and `Spoof keystore security level` patch ([#6751](https://github.com/ReVanced/revanced-patches/issues/6751)) ([4bc8c7c](https://github.com/ReVanced/revanced-patches/commit/4bc8c7c0f60a095533f07dc281f0320f8eb22f3c))
|
||||||
|
* **Announcements:** Support ReVanced API v5 announcements ([a05386e](https://github.com/ReVanced/revanced-patches/commit/a05386e8bc24c085b5c74f3674c402c5dd5ad468))
|
||||||
|
* Change contact email in patches about ([df1c3a4](https://github.com/ReVanced/revanced-patches/commit/df1c3a4a70fd2595d77b539299f1f7301bc60d24))
|
||||||
|
* **Instagram:** Add `Enable location sticker redesign` patch ([#6808](https://github.com/ReVanced/revanced-patches/issues/6808)) ([4b699da](https://github.com/ReVanced/revanced-patches/commit/4b699da220e5d1527c390792b6228e2d9cffedb7))
|
||||||
|
* **Spoof video streams:** Add Android Reel client to fix playback issues ([#6830](https://github.com/ReVanced/revanced-patches/issues/6830)) ([4b6c3e3](https://github.com/ReVanced/revanced-patches/commit/4b6c3e312328fbf6a1c7065e27d8ff04573e58be))
|
||||||
|
|
||||||
|
# [6.1.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.3...v6.1.0-dev.4) (2026-03-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Custom branding:** Fix double icons and change default branding to ReVanced ([#6806](https://github.com/ReVanced/revanced-patches/issues/6806)) ([e51c529](https://github.com/ReVanced/revanced-patches/commit/e51c5292c171325e7cfa0f5ee85474d9b3961a34))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `Spoof root of trust` and `Spoof keystore security level` patch ([#6751](https://github.com/ReVanced/revanced-patches/issues/6751)) ([4bc8c7c](https://github.com/ReVanced/revanced-patches/commit/4bc8c7c0f60a095533f07dc281f0320f8eb22f3c))
|
||||||
|
* **Instagram:** Add `Enable location sticker redesign` patch ([#6808](https://github.com/ReVanced/revanced-patches/issues/6808)) ([4b699da](https://github.com/ReVanced/revanced-patches/commit/4b699da220e5d1527c390792b6228e2d9cffedb7))
|
||||||
|
|
||||||
|
# [6.1.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.2...v6.1.0-dev.3) (2026-03-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spoof video streams:** Add Android Reel client to fix playback issues ([#6830](https://github.com/ReVanced/revanced-patches/issues/6830)) ([4b6c3e3](https://github.com/ReVanced/revanced-patches/commit/4b6c3e312328fbf6a1c7065e27d8ff04573e58be))
|
||||||
|
|
||||||
# [6.1.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.1...v6.1.0-dev.2) (2026-03-17)
|
# [6.1.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.1...v6.1.0-dev.2) (2026-03-17)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package app.revanced.extension.playintegrity;
|
package app.revanced.extension.play;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
+3
-3
@@ -114,7 +114,7 @@ public class CustomBrandingPatch {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*
|
* <p>
|
||||||
* The total number of app name aliases, including dummy aliases.
|
* The total number of app name aliases, including dummy aliases.
|
||||||
*/
|
*/
|
||||||
private static int numberOfPresetAppNames() {
|
private static int numberOfPresetAppNames() {
|
||||||
@@ -146,13 +146,13 @@ public class CustomBrandingPatch {
|
|||||||
public static int getDefaultAppNameIndex() {
|
public static int getDefaultAppNameIndex() {
|
||||||
return userProvidedCustomName()
|
return userProvidedCustomName()
|
||||||
? numberOfPresetAppNames()
|
? numberOfPresetAppNames()
|
||||||
: 1;
|
: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BrandingTheme getDefaultIconStyle() {
|
public static BrandingTheme getDefaultIconStyle() {
|
||||||
return userProvidedCustomIcon()
|
return userProvidedCustomIcon()
|
||||||
? BrandingTheme.CUSTOM
|
? BrandingTheme.CUSTOM
|
||||||
: BrandingTheme.ORIGINAL;
|
: BrandingTheme.ROUNDED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
package app.revanced.extension.strava;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
|
public final class AddRouteExportPatch {
|
||||||
|
|
||||||
|
public static void showExportDialog(Activity activity) {
|
||||||
|
try {
|
||||||
|
Intent intent = activity.getIntent();
|
||||||
|
if (intent == null) return;
|
||||||
|
|
||||||
|
Bundle extras = intent.getExtras();
|
||||||
|
if (extras == null) return;
|
||||||
|
|
||||||
|
String routeId = null;
|
||||||
|
|
||||||
|
// Scan all Intent extras to find the Route ID.
|
||||||
|
for (String key : extras.keySet()) {
|
||||||
|
Object val = extras.get(key);
|
||||||
|
if (val != null) {
|
||||||
|
String strVal = val.toString();
|
||||||
|
|
||||||
|
// Check if we have a "SavedRoute" or "SuggestedRoute" object.
|
||||||
|
if (val.getClass().getName().contains("SavedRoute")) {
|
||||||
|
try {
|
||||||
|
for (java.lang.reflect.Field field : val.getClass().getDeclaredFields()) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
if (field.getType() == long.class) {
|
||||||
|
long id = field.getLong(val);
|
||||||
|
if (id > 0) {
|
||||||
|
routeId = String.valueOf(id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeId != null) break;
|
||||||
|
|
||||||
|
// Fallback: regex search in the text content.
|
||||||
|
Matcher m = Pattern.compile("routes/([0-9]+)").matcher(strVal);
|
||||||
|
if (m.find()) {
|
||||||
|
routeId = m.group(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeId != null) {
|
||||||
|
final String finalRouteId = routeId;
|
||||||
|
final String finalUrl = "https://www.strava.com/routes/" + finalRouteId;
|
||||||
|
|
||||||
|
// Wait 500ms to allow Strava's BottomSheet to open,
|
||||||
|
// then display the popup on top of it.
|
||||||
|
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||||
|
if (activity.isDestroyed() || activity.isFinishing()) return;
|
||||||
|
|
||||||
|
new AlertDialog.Builder(activity)
|
||||||
|
.setTitle("Share options")
|
||||||
|
.setMessage("Do you want to export this route or use the share menu?")
|
||||||
|
.setPositiveButton("Export GPX", (dialog, which) -> {
|
||||||
|
Utils.openLink(finalUrl + "/export_gpx");
|
||||||
|
activity.finish();
|
||||||
|
})
|
||||||
|
.setNeutralButton("Export TCX", (dialog, which) -> {
|
||||||
|
Utils.openLink(finalUrl + "/export_tcx");
|
||||||
|
activity.finish();
|
||||||
|
})
|
||||||
|
.setNegativeButton("Share", (dialog, which) -> {
|
||||||
|
// Close the popup, Strava's share menu is already displayed underneath.
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Silently ignore errors to avoid crashes on other types of shares.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.strava.routes.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class Route implements Serializable {
|
||||||
|
public String getId() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -236,6 +236,17 @@ public final class AdsFilter extends Filter {
|
|||||||
return Settings.HIDE_GENERAL_ADS.get();
|
return Settings.HIDE_GENERAL_ADS.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* 21.34+ the player overlay timely shelf handler no longer gets a dedicated method,
|
||||||
|
* so instead the result of the overlay type `String.equals` comparison is passed in.
|
||||||
|
*
|
||||||
|
* @param original Whether the overlay is the timely shelf (already being shown when not hiding ads).
|
||||||
|
*/
|
||||||
|
public static boolean allowAds(boolean original) {
|
||||||
|
return Settings.HIDE_GENERAL_ADS.get() ? false : original;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1
-1
@@ -4,4 +4,4 @@ org.gradle.parallel = true
|
|||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
android.uniquePackageNames = false
|
android.uniquePackageNames = false
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 6.1.0-dev.2
|
version = 6.1.0
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
sdk.dir=/usr/lib/android-sdk
|
||||||
+17
-1
@@ -89,10 +89,14 @@ public final class app/revanced/patches/all/misc/packagename/ChangePackageNamePa
|
|||||||
public static final fun setOrGetFallbackPackageName (Ljava/lang/String;)Ljava/lang/String;
|
public static final fun setOrGetFallbackPackageName (Ljava/lang/String;)Ljava/lang/String;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrityKt {
|
public final class app/revanced/patches/all/misc/play/DisablePlayIntegrityKt {
|
||||||
public static final fun getDisablePlayIntegrityPatch ()Lapp/revanced/patcher/patch/Patch;
|
public static final fun getDisablePlayIntegrityPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/all/misc/play/SpoofPlayAgeSignalsKt {
|
||||||
|
public static final fun getSpoofPlayAgeSignalsPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/all/misc/resources/AddResourcesPatchKt {
|
public final class app/revanced/patches/all/misc/resources/AddResourcesPatchKt {
|
||||||
public static final fun addResource (Ljava/lang/String;Lapp/revanced/util/resource/BaseResource;)Z
|
public static final fun addResource (Ljava/lang/String;Lapp/revanced/util/resource/BaseResource;)Z
|
||||||
public static final fun addResources (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;)Z
|
public static final fun addResources (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;)Z
|
||||||
@@ -125,6 +129,14 @@ public final class app/revanced/patches/all/misc/spoof/EnableRomSignatureSpoofin
|
|||||||
public static final fun getEnableROMSignatureSpoofingPatch ()Lapp/revanced/patcher/patch/Patch;
|
public static final fun getEnableROMSignatureSpoofingPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/all/misc/spoof/SpoofKeystoreSecurityLevelPatchKt {
|
||||||
|
public static final fun getSpoofKeystoreSecurityLevelPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/all/misc/spoof/SpoofRootOfTrustPatchKt {
|
||||||
|
public static final fun getSpoofRootOfTrustPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/all/misc/targetSdk/SetTargetSdkVersion34Kt {
|
public final class app/revanced/patches/all/misc/targetSdk/SetTargetSdkVersion34Kt {
|
||||||
public static final fun getSetTargetSDKVersion34Patch ()Lapp/revanced/patcher/patch/Patch;
|
public static final fun getSetTargetSDKVersion34Patch ()Lapp/revanced/patcher/patch/Patch;
|
||||||
}
|
}
|
||||||
@@ -365,6 +377,10 @@ public final class app/revanced/patches/instagram/story/flipping/DisableStoryAut
|
|||||||
public static final fun getDisableStoryAutoFlippingPatch ()Lapp/revanced/patcher/patch/Patch;
|
public static final fun getDisableStoryAutoFlippingPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/instagram/story/locationsticker/EnableLocationStickerRedesignPatchKt {
|
||||||
|
public static final fun getEnableLocationStickerRedesignPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/irplus/ad/RemoveAdsPatchKt {
|
public final class app/revanced/patches/irplus/ad/RemoveAdsPatchKt {
|
||||||
public static final fun getRemoveAdsPatch ()Lapp/revanced/patcher/patch/Patch;
|
public static final fun getRemoveAdsPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||||
}
|
}
|
||||||
|
|||||||
+205
-34
@@ -6,7 +6,8 @@ import app.revanced.patcher.extensions.replaceInstruction
|
|||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.patch.intOption
|
import app.revanced.patcher.patch.intOption
|
||||||
import app.revanced.patcher.patch.stringOption
|
import app.revanced.patcher.patch.stringOption
|
||||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
import app.revanced.util.forEachInstructionAsSequence
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
@@ -33,7 +34,8 @@ val spoofSIMProviderPatch = bytecodePatch(
|
|||||||
validator = { it: String? -> it == null || it.uppercase() in countries.values },
|
validator = { it: String? -> it == null || it.uppercase() in countries.values },
|
||||||
)
|
)
|
||||||
|
|
||||||
fun isMccMncValid(it: Int?): Boolean = it == null || (it >= 10000 && it <= 999999)
|
fun isMccMncValid(it: Int?) = it == null || (it in 10000..999999)
|
||||||
|
fun isNumericValid(it: String?, length: Int) = it.isNullOrBlank() || it.equals("random", true) || it.length == length
|
||||||
|
|
||||||
val networkCountryIso by isoCountryPatchOption("Network ISO country code")
|
val networkCountryIso by isoCountryPatchOption("Network ISO country code")
|
||||||
|
|
||||||
@@ -61,46 +63,119 @@ val spoofSIMProviderPatch = bytecodePatch(
|
|||||||
description = "The full name of the SIM operator.",
|
description = "The full name of the SIM operator.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val imei by stringOption(
|
||||||
|
name = "IMEI value",
|
||||||
|
description = "15-digit IMEI to spoof, blank to skip, or 'random'.",
|
||||||
|
validator = { isNumericValid(it, 15) },
|
||||||
|
)
|
||||||
|
|
||||||
|
val meid by stringOption(
|
||||||
|
name = "MEID value",
|
||||||
|
description = "14-char hex MEID to spoof, blank to skip, or 'random'.",
|
||||||
|
validator = { isNumericValid(it, 14) },
|
||||||
|
)
|
||||||
|
|
||||||
|
val imsi by stringOption(
|
||||||
|
name = "IMSI (Subscriber ID)",
|
||||||
|
description = "15-digit IMSI to spoof, blank to skip, or 'random'.",
|
||||||
|
validator = { isNumericValid(it, 15) },
|
||||||
|
)
|
||||||
|
|
||||||
|
val iccid by stringOption(
|
||||||
|
name = "ICCID (SIM Serial)",
|
||||||
|
description = "19-digit ICCID to spoof, blank to skip, or 'random'.",
|
||||||
|
validator = { isNumericValid(it, 19) },
|
||||||
|
)
|
||||||
|
|
||||||
|
val phone by stringOption(
|
||||||
|
name = "Phone number",
|
||||||
|
description = "Phone number to spoof, blank to skip, or 'random'.",
|
||||||
|
validator = { it.isNullOrBlank() || it.equals("random", ignoreCase = true) || it.startsWith("+") },
|
||||||
|
)
|
||||||
|
|
||||||
dependsOn(
|
dependsOn(
|
||||||
transformInstructionsPatch(
|
bytecodePatch {
|
||||||
filterMap = { _, _, instruction, instructionIndex ->
|
apply {
|
||||||
if (instruction !is ReferenceInstruction) return@transformInstructionsPatch null
|
fun generateRandomNumeric(length: Int) = (1..length).map { ('0'..'9').random() }.joinToString("")
|
||||||
|
|
||||||
val reference = instruction.reference as? MethodReference ?: return@transformInstructionsPatch null
|
fun String?.computeSpoof(randomizer: () -> String): String? {
|
||||||
|
if (this.isNullOrBlank()) return null
|
||||||
val match = MethodCall.entries.firstOrNull { search ->
|
if (this.equals("random", ignoreCase = true)) return randomizer()
|
||||||
MethodUtil.methodSignaturesMatch(reference, search.reference)
|
return this
|
||||||
} ?: return@transformInstructionsPatch null
|
|
||||||
|
|
||||||
val replacement = when (match) {
|
|
||||||
MethodCall.NetworkCountryIso -> networkCountryIso?.lowercase()
|
|
||||||
MethodCall.NetworkOperator -> networkOperator?.toString()
|
|
||||||
MethodCall.NetworkOperatorName -> networkOperatorName
|
|
||||||
MethodCall.SimCountryIso -> simCountryIso?.lowercase()
|
|
||||||
MethodCall.SimOperator -> simOperator?.toString()
|
|
||||||
MethodCall.SimOperatorName -> simOperatorName
|
|
||||||
}
|
}
|
||||||
replacement?.let { instructionIndex to it }
|
|
||||||
},
|
// Calculate the Luhn checksum (mod 10) to generate a valid 15th digit, standard for IMEI numbers.
|
||||||
transform = ::transformMethodCall,
|
// Structure of an IMEI is as follows:
|
||||||
),
|
// TAC (Type Allocation Code): First 8 digits (e.g., "86" + 6 digits)
|
||||||
|
// SNR (Serial Number): Next 6 digits
|
||||||
|
// CD (Check Digit): The 15th digit
|
||||||
|
val computedImei = imei.computeSpoof {
|
||||||
|
val prefix = "86" + generateRandomNumeric(12)
|
||||||
|
|
||||||
|
val sum = prefix.mapIndexed { i, c ->
|
||||||
|
var d = c.digitToInt()
|
||||||
|
// Double every second digit (index 1, 3, 5...).
|
||||||
|
if (i % 2 != 0) {
|
||||||
|
d *= 2
|
||||||
|
// If result is two digits (e.g. 14), sum them (1+4=5).
|
||||||
|
// This is mathematically equivalent to d - 9.
|
||||||
|
if (d > 9) d -= 9
|
||||||
|
}
|
||||||
|
d
|
||||||
|
}.sum()
|
||||||
|
// Append the calculated check digit to the 14-digit prefix.
|
||||||
|
prefix + ((10 - (sum % 10)) % 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
val computedMeid = meid.computeSpoof { (1..14).map { "0123456789ABCDEF".random() }.joinToString("") }?.uppercase()
|
||||||
|
val computedImsi = imsi.computeSpoof { generateRandomNumeric(15) }
|
||||||
|
val computedIccid = iccid.computeSpoof { "89" + generateRandomNumeric(17) }
|
||||||
|
val computedPhone = phone.computeSpoof { "+" + generateRandomNumeric(11) }
|
||||||
|
|
||||||
|
forEachInstructionAsSequence(
|
||||||
|
match = { _, _, instruction, instructionIndex ->
|
||||||
|
if (instruction !is ReferenceInstruction) return@forEachInstructionAsSequence null
|
||||||
|
|
||||||
|
val reference = instruction.reference as? MethodReference ?: return@forEachInstructionAsSequence null
|
||||||
|
|
||||||
|
val match = MethodCall.entries.firstOrNull { search ->
|
||||||
|
MethodUtil.methodSignaturesMatch(reference, search.reference)
|
||||||
|
} ?: return@forEachInstructionAsSequence null
|
||||||
|
|
||||||
|
val replacement = when (match) {
|
||||||
|
MethodCall.NetworkCountryIso -> networkCountryIso?.lowercase()
|
||||||
|
MethodCall.NetworkOperator -> networkOperator?.toString()
|
||||||
|
MethodCall.NetworkOperatorName -> networkOperatorName
|
||||||
|
MethodCall.SimCountryIso -> simCountryIso?.lowercase()
|
||||||
|
MethodCall.SimOperator -> simOperator?.toString()
|
||||||
|
MethodCall.SimOperatorName -> simOperatorName
|
||||||
|
MethodCall.Imei, MethodCall.ImeiWithSlot, MethodCall.DeviceId, MethodCall.DeviceIdWithSlot -> computedImei
|
||||||
|
MethodCall.Meid, MethodCall.MeidWithSlot -> computedMeid
|
||||||
|
MethodCall.SubscriberId, MethodCall.SubscriberIdWithSlot -> computedImsi
|
||||||
|
MethodCall.SimSerialNumber, MethodCall.SimSerialNumberWithSlot -> computedIccid
|
||||||
|
MethodCall.Line1Number, MethodCall.Line1NumberWithSlot -> computedPhone
|
||||||
|
}
|
||||||
|
replacement?.let { instructionIndex to it }
|
||||||
|
},
|
||||||
|
transform = ::transformMethodCall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transformMethodCall(
|
private fun transformMethodCall(mutableMethod: MutableMethod, entry: Pair<Int, String>) {
|
||||||
mutableMethod: MutableMethod,
|
val (index, value) = entry
|
||||||
entry: Pair<Int, String>,
|
val nextInstr = mutableMethod.getInstruction<Instruction>(index + 1)
|
||||||
) {
|
|
||||||
val (instructionIndex, methodCallValue) = entry
|
|
||||||
|
|
||||||
// Get the register which would have contained the return value
|
if (nextInstr.opcode.name != "move-result-object") {
|
||||||
val register = mutableMethod.getInstruction<OneRegisterInstruction>(instructionIndex + 1).registerA
|
mutableMethod.replaceInstruction(index, "nop")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Replace the move-result instruction with our fake value
|
val register = (nextInstr as OneRegisterInstruction).registerA
|
||||||
mutableMethod.replaceInstruction(
|
mutableMethod.replaceInstruction(index, "const-string v$register, \"$value\"")
|
||||||
instructionIndex + 1,
|
mutableMethod.replaceInstruction(index + 1, "nop")
|
||||||
"const-string v$register, \"$methodCallValue\"",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class MethodCall(
|
private enum class MethodCall(
|
||||||
@@ -154,4 +229,100 @@ private enum class MethodCall(
|
|||||||
"Ljava/lang/String;",
|
"Ljava/lang/String;",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Imei(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getImei",
|
||||||
|
emptyList(),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ImeiWithSlot(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getImei",
|
||||||
|
listOf("I"),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DeviceId(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getDeviceId",
|
||||||
|
emptyList(),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DeviceIdWithSlot(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getDeviceId",
|
||||||
|
listOf("I"),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Meid(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getMeid",
|
||||||
|
emptyList(),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MeidWithSlot(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getMeid",
|
||||||
|
listOf("I"),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SubscriberId(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getSubscriberId",
|
||||||
|
emptyList(),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
SubscriberIdWithSlot(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getSubscriberId",
|
||||||
|
listOf("I"),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
SimSerialNumber(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getSimSerialNumber",
|
||||||
|
emptyList(),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
SimSerialNumberWithSlot(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getSimSerialNumber",
|
||||||
|
listOf("I"),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Line1Number(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getLine1Number",
|
||||||
|
emptyList(),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Line1NumberWithSlot(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Landroid/telephony/TelephonyManager;",
|
||||||
|
"getLine1Number",
|
||||||
|
listOf("I"),
|
||||||
|
"Ljava/lang/String;"
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-33
@@ -3,7 +3,7 @@ package app.revanced.patches.all.misc.connectivity.wifi.spoof
|
|||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patches.all.misc.transformation.IMethodCall
|
import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||||
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
||||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
import app.revanced.util.forEachInstructionAsSequence
|
||||||
|
|
||||||
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
|
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
|
||||||
"Lapp/revanced/extension/all/misc/connectivity/wifi/spoof/SpoofWifiPatch"
|
"Lapp/revanced/extension/all/misc/connectivity/wifi/spoof/SpoofWifiPatch"
|
||||||
@@ -19,29 +19,32 @@ val spoofWiFiConnectionPatch = bytecodePatch(
|
|||||||
extendWith("extensions/all/misc/connectivity/wifi/spoof/spoof-wifi.rve")
|
extendWith("extensions/all/misc/connectivity/wifi/spoof/spoof-wifi.rve")
|
||||||
|
|
||||||
dependsOn(
|
dependsOn(
|
||||||
transformInstructionsPatch(
|
bytecodePatch {
|
||||||
filterMap = { classDef, _, instruction, instructionIndex ->
|
apply {
|
||||||
filterMapInstruction35c<MethodCall>(
|
forEachInstructionAsSequence(
|
||||||
EXTENSION_CLASS_DESCRIPTOR_PREFIX,
|
match = { classDef, _, instruction, instructionIndex ->
|
||||||
classDef,
|
filterMapInstruction35c<MethodCall>(
|
||||||
instruction,
|
EXTENSION_CLASS_DESCRIPTOR_PREFIX,
|
||||||
instructionIndex,
|
classDef,
|
||||||
)
|
instruction,
|
||||||
},
|
instructionIndex,
|
||||||
transform = { method, entry ->
|
)
|
||||||
val (methodType, instruction, instructionIndex) = entry
|
},
|
||||||
methodType.replaceInvokeVirtualWithExtension(
|
transform = { method, entry ->
|
||||||
EXTENSION_CLASS_DESCRIPTOR,
|
val (methodType, instruction, instructionIndex) = entry
|
||||||
method,
|
methodType.replaceInvokeVirtualWithExtension(
|
||||||
instruction,
|
EXTENSION_CLASS_DESCRIPTOR,
|
||||||
instructionIndex,
|
method,
|
||||||
)
|
instruction,
|
||||||
},
|
instructionIndex,
|
||||||
),
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Information about method calls we want to replace
|
// Information about method calls we want to replace.
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
private enum class MethodCall(
|
private enum class MethodCall(
|
||||||
override val definedClassName: String,
|
override val definedClassName: String,
|
||||||
@@ -89,13 +92,13 @@ private enum class MethodCall(
|
|||||||
"Landroid/net/NetworkInfo;",
|
"Landroid/net/NetworkInfo;",
|
||||||
"getState",
|
"getState",
|
||||||
arrayOf(),
|
arrayOf(),
|
||||||
"Landroid/net/NetworkInfo\$State;",
|
$$"Landroid/net/NetworkInfo$State;",
|
||||||
),
|
),
|
||||||
GetDetailedState(
|
GetDetailedState(
|
||||||
"Landroid/net/NetworkInfo;",
|
"Landroid/net/NetworkInfo;",
|
||||||
"getDetailedState",
|
"getDetailedState",
|
||||||
arrayOf(),
|
arrayOf(),
|
||||||
"Landroid/net/NetworkInfo\$DetailedState;",
|
$$"Landroid/net/NetworkInfo$DetailedState;",
|
||||||
),
|
),
|
||||||
IsActiveNetworkMetered(
|
IsActiveNetworkMetered(
|
||||||
"Landroid/net/ConnectivityManager;",
|
"Landroid/net/ConnectivityManager;",
|
||||||
@@ -132,7 +135,7 @@ private enum class MethodCall(
|
|||||||
"registerBestMatchingNetworkCallback",
|
"registerBestMatchingNetworkCallback",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"Landroid/net/NetworkRequest;",
|
"Landroid/net/NetworkRequest;",
|
||||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
$$"Landroid/net/ConnectivityManager$NetworkCallback;",
|
||||||
"Landroid/os/Handler;",
|
"Landroid/os/Handler;",
|
||||||
),
|
),
|
||||||
"V",
|
"V",
|
||||||
@@ -140,19 +143,19 @@ private enum class MethodCall(
|
|||||||
RegisterDefaultNetworkCallback1(
|
RegisterDefaultNetworkCallback1(
|
||||||
"Landroid/net/ConnectivityManager;",
|
"Landroid/net/ConnectivityManager;",
|
||||||
"registerDefaultNetworkCallback",
|
"registerDefaultNetworkCallback",
|
||||||
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
arrayOf($$"Landroid/net/ConnectivityManager$NetworkCallback;"),
|
||||||
"V",
|
"V",
|
||||||
),
|
),
|
||||||
RegisterDefaultNetworkCallback2(
|
RegisterDefaultNetworkCallback2(
|
||||||
"Landroid/net/ConnectivityManager;",
|
"Landroid/net/ConnectivityManager;",
|
||||||
"registerDefaultNetworkCallback",
|
"registerDefaultNetworkCallback",
|
||||||
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;"),
|
arrayOf($$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;"),
|
||||||
"V",
|
"V",
|
||||||
),
|
),
|
||||||
RegisterNetworkCallback1(
|
RegisterNetworkCallback1(
|
||||||
"Landroid/net/ConnectivityManager;",
|
"Landroid/net/ConnectivityManager;",
|
||||||
"registerNetworkCallback",
|
"registerNetworkCallback",
|
||||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
arrayOf("Landroid/net/NetworkRequest;", $$"Landroid/net/ConnectivityManager$NetworkCallback;"),
|
||||||
"V",
|
"V",
|
||||||
),
|
),
|
||||||
RegisterNetworkCallback2(
|
RegisterNetworkCallback2(
|
||||||
@@ -166,7 +169,7 @@ private enum class MethodCall(
|
|||||||
"registerNetworkCallback",
|
"registerNetworkCallback",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"Landroid/net/NetworkRequest;",
|
"Landroid/net/NetworkRequest;",
|
||||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
$$"Landroid/net/ConnectivityManager$NetworkCallback;",
|
||||||
"Landroid/os/Handler;",
|
"Landroid/os/Handler;",
|
||||||
),
|
),
|
||||||
"V",
|
"V",
|
||||||
@@ -174,13 +177,13 @@ private enum class MethodCall(
|
|||||||
RequestNetwork1(
|
RequestNetwork1(
|
||||||
"Landroid/net/ConnectivityManager;",
|
"Landroid/net/ConnectivityManager;",
|
||||||
"requestNetwork",
|
"requestNetwork",
|
||||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
arrayOf("Landroid/net/NetworkRequest;", $$"Landroid/net/ConnectivityManager$NetworkCallback;"),
|
||||||
"V",
|
"V",
|
||||||
),
|
),
|
||||||
RequestNetwork2(
|
RequestNetwork2(
|
||||||
"Landroid/net/ConnectivityManager;",
|
"Landroid/net/ConnectivityManager;",
|
||||||
"requestNetwork",
|
"requestNetwork",
|
||||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "I"),
|
arrayOf("Landroid/net/NetworkRequest;", $$"Landroid/net/ConnectivityManager$NetworkCallback;", "I"),
|
||||||
"V",
|
"V",
|
||||||
),
|
),
|
||||||
RequestNetwork3(
|
RequestNetwork3(
|
||||||
@@ -188,7 +191,7 @@ private enum class MethodCall(
|
|||||||
"requestNetwork",
|
"requestNetwork",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"Landroid/net/NetworkRequest;",
|
"Landroid/net/NetworkRequest;",
|
||||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
$$"Landroid/net/ConnectivityManager$NetworkCallback;",
|
||||||
"Landroid/os/Handler;",
|
"Landroid/os/Handler;",
|
||||||
),
|
),
|
||||||
"V",
|
"V",
|
||||||
@@ -204,7 +207,7 @@ private enum class MethodCall(
|
|||||||
"requestNetwork",
|
"requestNetwork",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"Landroid/net/NetworkRequest;",
|
"Landroid/net/NetworkRequest;",
|
||||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
$$"Landroid/net/ConnectivityManager$NetworkCallback;",
|
||||||
"Landroid/os/Handler;",
|
"Landroid/os/Handler;",
|
||||||
"I",
|
"I",
|
||||||
),
|
),
|
||||||
@@ -213,7 +216,7 @@ private enum class MethodCall(
|
|||||||
UnregisterNetworkCallback1(
|
UnregisterNetworkCallback1(
|
||||||
"Landroid/net/ConnectivityManager;",
|
"Landroid/net/ConnectivityManager;",
|
||||||
"unregisterNetworkCallback",
|
"unregisterNetworkCallback",
|
||||||
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
arrayOf($$"Landroid/net/ConnectivityManager$NetworkCallback;"),
|
||||||
"V",
|
"V",
|
||||||
),
|
),
|
||||||
UnregisterNetworkCallback2(
|
UnregisterNetworkCallback2(
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
package app.revanced.patches.all.misc.playintegrity
|
package app.revanced.patches.all.misc.play
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.replaceInstruction
|
import app.revanced.patcher.extensions.replaceInstruction
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
@@ -9,7 +9,7 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|||||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
|
|
||||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/playintegrity/DisablePlayIntegrityPatch;"
|
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/play/DisablePlayIntegrityPatch;"
|
||||||
|
|
||||||
private val CONTEXT_BIND_SERVICE_METHOD_REFERENCE = ImmutableMethodReference(
|
private val CONTEXT_BIND_SERVICE_METHOD_REFERENCE = ImmutableMethodReference(
|
||||||
"Landroid/content/Context;",
|
"Landroid/content/Context;",
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package app.revanced.patches.all.misc.play
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.addInstructions
|
||||||
|
import app.revanced.patcher.extensions.getInstruction
|
||||||
|
import app.revanced.patcher.extensions.removeInstructions
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patcher.patch.intOption
|
||||||
|
import app.revanced.patcher.patch.option
|
||||||
|
import app.revanced.util.forEachInstructionAsSequence
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val spoofPlayAgeSignalsPatch = bytecodePatch(
|
||||||
|
name = "Spoof Play Age Signals",
|
||||||
|
description = "Spoofs Google Play data about the user's age and verification status.",
|
||||||
|
use = false,
|
||||||
|
) {
|
||||||
|
val lowerAgeBound by intOption(
|
||||||
|
name = "Lower age bound",
|
||||||
|
description = "A positive integer.",
|
||||||
|
default = 18,
|
||||||
|
validator = { it == null || it > 0 },
|
||||||
|
)
|
||||||
|
|
||||||
|
val upperAgeBound by intOption(
|
||||||
|
name = "Upper age bound",
|
||||||
|
description = "A positive integer. Must be greater than the lower age bound.",
|
||||||
|
default = Int.MAX_VALUE,
|
||||||
|
validator = { it == null || it > lowerAgeBound!! },
|
||||||
|
)
|
||||||
|
|
||||||
|
val userStatus by intOption(
|
||||||
|
name = "User status",
|
||||||
|
description = "An integer representing the user status.",
|
||||||
|
default = UserStatus.VERIFIED.value,
|
||||||
|
values = UserStatus.entries.associate { it.name to it.value },
|
||||||
|
)
|
||||||
|
|
||||||
|
apply {
|
||||||
|
forEachInstructionAsSequence(match = { classDef, _, instruction, instructionIndex ->
|
||||||
|
// Avoid patching the library itself.
|
||||||
|
if (classDef.type.startsWith("Lcom/google/android/play/agesignals/")) return@forEachInstructionAsSequence null
|
||||||
|
|
||||||
|
// Keep method calls only.
|
||||||
|
val reference = instruction.getReference<MethodReference>()
|
||||||
|
?: return@forEachInstructionAsSequence null
|
||||||
|
|
||||||
|
val match = MethodCall.entries.firstOrNull {
|
||||||
|
reference == it.reference
|
||||||
|
} ?: return@forEachInstructionAsSequence null
|
||||||
|
|
||||||
|
val replacement = when (match) {
|
||||||
|
MethodCall.AgeLower -> lowerAgeBound!!
|
||||||
|
MethodCall.AgeUpper -> upperAgeBound!!
|
||||||
|
MethodCall.UserStatus -> userStatus!!
|
||||||
|
}
|
||||||
|
|
||||||
|
replacement.let { instructionIndex to it }
|
||||||
|
}, transform = { method, entry ->
|
||||||
|
val (instructionIndex, replacement) = entry
|
||||||
|
|
||||||
|
// Get the register which would have contained the return value.
|
||||||
|
val register = method.getInstruction<OneRegisterInstruction>(instructionIndex + 1).registerA
|
||||||
|
|
||||||
|
// Replace the call instructions with the spoofed value.
|
||||||
|
method.removeInstructions(instructionIndex, 2)
|
||||||
|
method.addInstructions(
|
||||||
|
instructionIndex,
|
||||||
|
"""
|
||||||
|
const v$register, $replacement
|
||||||
|
invoke-static { v$register }, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
|
||||||
|
move-result-object v$register
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See [AgeSignalsResult](https://developer.android.com/google/play/age-signals/reference/com/google/android/play/agesignals/AgeSignalsResult).
|
||||||
|
*/
|
||||||
|
private enum class MethodCall(
|
||||||
|
val reference: MethodReference,
|
||||||
|
) {
|
||||||
|
AgeLower(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Lcom/google/android/play/agesignals/AgeSignalsResult;",
|
||||||
|
"ageLower",
|
||||||
|
emptyList(),
|
||||||
|
"Ljava/lang/Integer;",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AgeUpper(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Lcom/google/android/play/agesignals/AgeSignalsResult;",
|
||||||
|
"ageUpper",
|
||||||
|
emptyList(),
|
||||||
|
"Ljava/lang/Integer;",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
UserStatus(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"Lcom/google/android/play/agesignals/AgeSignalsResult;",
|
||||||
|
"userStatus",
|
||||||
|
emptyList(),
|
||||||
|
"Ljava/lang/Integer;",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All possible user verification statuses.
|
||||||
|
*
|
||||||
|
* See [AgeSignalsVerificationStatus](https://developer.android.com/google/play/age-signals/reference/com/google/android/play/agesignals/model/AgeSignalsVerificationStatus).
|
||||||
|
*/
|
||||||
|
private enum class UserStatus(val value: Int) {
|
||||||
|
/** The user provided their age, but it hasn't been verified yet. */
|
||||||
|
DECLARED(5),
|
||||||
|
|
||||||
|
/** The user is 18+. */
|
||||||
|
VERIFIED(0),
|
||||||
|
|
||||||
|
/** The user's guardian has set the age for him. */
|
||||||
|
SUPERVISED(1),
|
||||||
|
|
||||||
|
/** The user's guardian hasn't approved the significant changes yet. */
|
||||||
|
SUPERVISED_APPROVAL_PENDING(2),
|
||||||
|
|
||||||
|
/** The user's guardian has denied approval for one or more pending significant changes. */
|
||||||
|
SUPERVISED_APPROVAL_DENIED(3),
|
||||||
|
|
||||||
|
/** The user is not verified or supervised. */
|
||||||
|
UNKNOWN(4),
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package app.revanced.patches.all.misc.spoof
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.replaceInstructions
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.util.forEachInstructionAsSequence
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val spoofKeystoreSecurityLevelPatch = bytecodePatch(
|
||||||
|
name = "Spoof keystore security level",
|
||||||
|
description = "Forces apps to see Keymaster and Attestation security levels as 'StrongBox' (Level 2).",
|
||||||
|
use = false
|
||||||
|
) {
|
||||||
|
apply {
|
||||||
|
forEachInstructionAsSequence(
|
||||||
|
match = { _, method, _, _ ->
|
||||||
|
// Match methods by comparing the current method to a reference criteria.
|
||||||
|
val name = method.name.lowercase()
|
||||||
|
if (name.contains("securitylevel") && method.returnType == "I") method else null
|
||||||
|
},
|
||||||
|
transform = { mutableMethod, _ ->
|
||||||
|
// Ensure the method has an implementation before replacing.
|
||||||
|
if (mutableMethod.implementation?.instructions?.iterator()?.hasNext() == true) {
|
||||||
|
mutableMethod.replaceInstructions(0, "const/4 v0, 0x2\nreturn v0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package app.revanced.patches.all.misc.spoof
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.replaceInstructions
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.util.forEachInstructionAsSequence
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val spoofRootOfTrustPatch = bytecodePatch(
|
||||||
|
name = "Spoof root of trust",
|
||||||
|
description = "Spoofs device integrity states (Locked Bootloader, Verified OS) for apps that perform local certificate attestation.",
|
||||||
|
use = false
|
||||||
|
) {
|
||||||
|
apply {
|
||||||
|
forEachInstructionAsSequence(
|
||||||
|
match = { _, method, _, _ ->
|
||||||
|
MethodCall.entries.firstOrNull { MethodUtil.methodSignaturesMatch(method, it.reference) }
|
||||||
|
},
|
||||||
|
transform = { mutableMethod, methodCall ->
|
||||||
|
if (mutableMethod.implementation?.instructions?.iterator()?.hasNext() == true) {
|
||||||
|
mutableMethod.replaceInstructions(0, methodCall.replacementInstructions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class MethodCall(
|
||||||
|
val reference: MethodReference,
|
||||||
|
val replacementInstructions: String,
|
||||||
|
) {
|
||||||
|
IsDeviceLockedRootOfTrust(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"LRootOfTrust;",
|
||||||
|
"isDeviceLocked",
|
||||||
|
emptyList(),
|
||||||
|
"Z"
|
||||||
|
),
|
||||||
|
"const/4 v0, 0x1\nreturn v0",
|
||||||
|
),
|
||||||
|
GetVerifiedBootStateRootOfTrust(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"LRootOfTrust;",
|
||||||
|
"getVerifiedBootState",
|
||||||
|
emptyList(),
|
||||||
|
"I"
|
||||||
|
),
|
||||||
|
"const/4 v0, 0x0\nreturn v0",
|
||||||
|
),
|
||||||
|
IsDeviceLockedAttestation(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"LAttestation;",
|
||||||
|
"isDeviceLocked",
|
||||||
|
emptyList(),
|
||||||
|
"Z"
|
||||||
|
),
|
||||||
|
"const/4 v0, 0x1\nreturn v0",
|
||||||
|
),
|
||||||
|
GetVerifiedBootStateAttestation(
|
||||||
|
ImmutableMethodReference(
|
||||||
|
"LAttestation;",
|
||||||
|
"getVerifiedBootState",
|
||||||
|
emptyList(),
|
||||||
|
"I"
|
||||||
|
),
|
||||||
|
"const/4 v0, 0x0\nreturn v0",
|
||||||
|
),
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package app.revanced.patches.instagram.story.locationsticker
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.util.returnEarly
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val enableLocationStickerRedesignPatch = bytecodePatch(
|
||||||
|
name = "Enable location sticker redesign",
|
||||||
|
description = "Unlocks the redesigned location sticker with additional style options.",
|
||||||
|
use = false,
|
||||||
|
) {
|
||||||
|
compatibleWith("com.instagram.android")
|
||||||
|
|
||||||
|
apply {
|
||||||
|
// The gate method reads a MobileConfig boolean flag and returns it directly.
|
||||||
|
// Returning early with true bypasses the flag check entirely,
|
||||||
|
// enabling the redesigned sticker styles regardless of server configuration.
|
||||||
|
locationStickerRedesignGateMethodMatch.method.returnEarly(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.patches.instagram.story.locationsticker
|
||||||
|
|
||||||
|
import app.revanced.patcher.composingFirstMethod
|
||||||
|
import app.revanced.patcher.instructions
|
||||||
|
import app.revanced.patcher.invoke
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchContext
|
||||||
|
|
||||||
|
// MobileConfig boolean key that gates the redesigned location sticker styles.
|
||||||
|
// The method containing this constant reads the flag and returns it directly,
|
||||||
|
// making it the sole control point for the feature. The key is stable across
|
||||||
|
// app updates as MobileConfig keys are server-assigned constants.
|
||||||
|
private const val LOCATION_STICKER_REDESIGN_CONFIG_KEY = 0x8105a100041e0dL
|
||||||
|
|
||||||
|
internal val BytecodePatchContext.locationStickerRedesignGateMethodMatch by composingFirstMethod {
|
||||||
|
instructions(LOCATION_STICKER_REDESIGN_CONFIG_KEY())
|
||||||
|
}
|
||||||
+44
-21
@@ -3,7 +3,6 @@ package app.revanced.patches.shared.layout.branding
|
|||||||
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
|
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
|
||||||
import app.revanced.patcher.extensions.addInstruction
|
import app.revanced.patcher.extensions.addInstruction
|
||||||
import app.revanced.patcher.extensions.getInstruction
|
import app.revanced.patcher.extensions.getInstruction
|
||||||
import app.revanced.patcher.firstImmutableClassDef
|
|
||||||
import app.revanced.patcher.patch.*
|
import app.revanced.patcher.patch.*
|
||||||
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
|
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
|
||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
@@ -126,14 +125,14 @@ internal fun baseCustomBrandingPatch(
|
|||||||
val getBuilderIndex = if (isYouTubeMusic) {
|
val getBuilderIndex = if (isYouTubeMusic) {
|
||||||
// YT Music the field is not a plain object type.
|
// YT Music the field is not a plain object type.
|
||||||
indexOfFirstInstructionOrThrow {
|
indexOfFirstInstructionOrThrow {
|
||||||
getReference<FieldReference>()?.type == "Landroid/app/Notification\$Builder;"
|
getReference<FieldReference>()?.type == $$"Landroid/app/Notification$Builder;"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Find the field name of the notification builder. Field is an Object type.
|
// Find the field name of the notification builder. Field is an Object type.
|
||||||
val builderCastIndex = indexOfFirstInstructionOrThrow {
|
val builderCastIndex = indexOfFirstInstructionOrThrow {
|
||||||
val reference = getReference<TypeReference>()
|
val reference = getReference<TypeReference>()
|
||||||
opcode == Opcode.CHECK_CAST &&
|
opcode == Opcode.CHECK_CAST &&
|
||||||
reference?.type == "Landroid/app/Notification\$Builder;"
|
reference?.type == $$"Landroid/app/Notification$Builder;"
|
||||||
}
|
}
|
||||||
indexOfFirstInstructionReversedOrThrow(builderCastIndex) {
|
indexOfFirstInstructionReversedOrThrow(builderCastIndex) {
|
||||||
getReference<FieldReference>()?.type == "Ljava/lang/Object;"
|
getReference<FieldReference>()?.type == "Ljava/lang/Object;"
|
||||||
@@ -148,11 +147,11 @@ internal fun baseCustomBrandingPatch(
|
|||||||
).forEach { index ->
|
).forEach { index ->
|
||||||
addInstructionsAtControlFlowLabel(
|
addInstructionsAtControlFlowLabel(
|
||||||
index,
|
index,
|
||||||
"""
|
$$"""
|
||||||
move-object/from16 v0, p0
|
move-object/from16 v0, p0
|
||||||
iget-object v0, v0, $builderFieldName
|
iget-object v0, v0, $$builderFieldName
|
||||||
check-cast v0, Landroid/app/Notification${'$'}Builder;
|
check-cast v0, Landroid/app/Notification$Builder;
|
||||||
invoke-static { v0 }, $EXTENSION_CLASS_DESCRIPTOR->setNotificationIcon(Landroid/app/Notification${'$'}Builder;)V
|
invoke-static { v0 }, $$EXTENSION_CLASS_DESCRIPTOR->setNotificationIcon(Landroid/app/Notification$Builder;)V
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -162,16 +161,37 @@ internal fun baseCustomBrandingPatch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
afterDependents {
|
afterDependents {
|
||||||
|
val useCustomName = customName != null
|
||||||
|
val useCustomIcon = customIcon != null
|
||||||
|
val isRootInstall = setOrGetFallbackPackageName(originalAppPackageName) == originalAppPackageName
|
||||||
|
|
||||||
// Can only check if app is root installation by checking if change package name patch is in use.
|
// Can only check if app is root installation by checking if change package name patch is in use.
|
||||||
// and can only do that in the afterDependents block here.
|
// and can only do that in the afterDependents block here.
|
||||||
// The UI preferences cannot be selectively added here, because the settings afterDependents block
|
// The UI preferences cannot be selectively added here, because the settings afterDependents block
|
||||||
// may have already run and the settings are already wrote to file.
|
// may have already run and the settings are already wrote to file.
|
||||||
// Instead, show a warning if any patch option was used (A rooted device launcher ignores the manifest changes),
|
// Instead, show a warning if any patch option was used (A rooted device launcher ignores the manifest changes),
|
||||||
// and the non-functional in-app settings are removed on app startup by extension code.
|
// and the non-functional in-app settings are removed on app startup by extension code.
|
||||||
if (customName != null || customIcon != null) {
|
if (isRootInstall && (useCustomName || useCustomIcon)) {
|
||||||
if (setOrGetFallbackPackageName(originalAppPackageName) == originalAppPackageName) {
|
Logger.getLogger(this::class.java.name).warning(
|
||||||
Logger.getLogger(this::class.java.name).warning(
|
"Custom branding does not work with root installation. No changes applied."
|
||||||
"Custom branding does not work with root installation. No changes applied.",
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRootInstall || useCustomName) {
|
||||||
|
document("AndroidManifest.xml").use { document ->
|
||||||
|
val application = document.getElementsByTagName("application").item(0) as Element
|
||||||
|
application.setAttribute(
|
||||||
|
"android:label",
|
||||||
|
if (useCustomName) {
|
||||||
|
// Use custom name everywhere.
|
||||||
|
customName
|
||||||
|
} else {
|
||||||
|
// The YT application name can appear in some places alongside the system
|
||||||
|
// YouTube app, such as the settings app list and in the "open with" file picker.
|
||||||
|
// Because the YouTube app cannot be completely uninstalled and only disabled,
|
||||||
|
// use a custom name for this situation to disambiguate which app is which.
|
||||||
|
"@string/revanced_custom_branding_name_entry_2"
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,16 +332,19 @@ internal fun baseCustomBrandingPatch(
|
|||||||
activityAliasNameWithIntents,
|
activityAliasNameWithIntents,
|
||||||
).childNodes
|
).childNodes
|
||||||
|
|
||||||
// The YT application name can appear in some places alongside the system
|
// If user provides a custom icon, then change the application icon ('static' icon)
|
||||||
// YouTube app, such as the settings app list and in the "open with" file picker.
|
// which shows as the push notification for some devices, in the app settings,
|
||||||
// Because the YouTube app cannot be completely uninstalled and only disabled,
|
// and as the icon for the apk before installing.
|
||||||
// use a custom name for this situation to disambiguate which app is which.
|
// This icon cannot be dynamically selected and this change must only be done if the
|
||||||
application.setAttribute(
|
// user provides an icon otherwise there is no way to restore the original YouTube icon.
|
||||||
"android:label",
|
if (useCustomIcon) {
|
||||||
"@string/revanced_custom_branding_name_entry_2",
|
application.setAttribute(
|
||||||
)
|
"android:icon",
|
||||||
|
"@mipmap/revanced_launcher_custom"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val enabledNameIndex = if (useCustomName) numberOfPresetAppNames else 1 // 1 indexing.
|
val enabledNameIndex = if (useCustomName) numberOfPresetAppNames else 2 // 1 indexing.
|
||||||
val enabledIconIndex = if (useCustomIcon) iconStyleNames.size else 0 // 0 indexing.
|
val enabledIconIndex = if (useCustomIcon) iconStyleNames.size else 0 // 0 indexing.
|
||||||
|
|
||||||
for (appNameIndex in 1..numberOfPresetAppNames) {
|
for (appNameIndex in 1..numberOfPresetAppNames) {
|
||||||
@@ -336,7 +359,7 @@ internal fun baseCustomBrandingPatch(
|
|||||||
iconMipmapName = originalLauncherIconName,
|
iconMipmapName = originalLauncherIconName,
|
||||||
appNameIndex = appNameIndex,
|
appNameIndex = appNameIndex,
|
||||||
useCustomName = useCustomNameLabel,
|
useCustomName = useCustomNameLabel,
|
||||||
enabled = (appNameIndex == 1),
|
enabled = false,
|
||||||
intentFilters,
|
intentFilters,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,14 +31,11 @@ internal val BytecodePatchContext.buildInitPlaybackRequestMethodMatch by composi
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal val BytecodePatchContext.buildPlayerRequestURIMethodMatch by composingFirstMethod("key", "asig") {
|
internal val BytecodePatchContext.buildPlayerRequestURIMethodMatch by composingFirstMethod("key", "asig") {
|
||||||
returnType("Ljava/lang/String;")
|
returnType($$"Landroid/net/Uri$Builder;")
|
||||||
opcodes(
|
opcodes(
|
||||||
Opcode.INVOKE_VIRTUAL, // Register holds player request URI.
|
Opcode.INVOKE_VIRTUAL, // The last Uri$Builder mutating call. Register holds the player request Uri$Builder.
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
Opcode.MOVE_RESULT_OBJECT,
|
||||||
Opcode.IPUT_OBJECT,
|
Opcode.RETURN_OBJECT, // Returns the built player request Uri$Builder.
|
||||||
Opcode.IGET_OBJECT,
|
|
||||||
Opcode.MONITOR_EXIT,
|
|
||||||
Opcode.RETURN_OBJECT,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +151,7 @@ internal val BytecodePatchContext.nerdsStatsVideoFormatBuilderMethod by gettingF
|
|||||||
returnType("Ljava/lang/String;")
|
returnType("Ljava/lang/String;")
|
||||||
parameterTypes("L")
|
parameterTypes("L")
|
||||||
instructions(
|
instructions(
|
||||||
"codecs=\""(),
|
", codecs="(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-6
@@ -82,14 +82,19 @@ internal fun spoofVideoStreamsPatch(
|
|||||||
// region Block /get_watch requests to fall back to /player requests.
|
// region Block /get_watch requests to fall back to /player requests.
|
||||||
|
|
||||||
buildPlayerRequestURIMethodMatch.method.apply {
|
buildPlayerRequestURIMethodMatch.method.apply {
|
||||||
val invokeToStringIndex = buildPlayerRequestURIMethodMatch[0]
|
val returnIndex = buildPlayerRequestURIMethodMatch[-1]
|
||||||
val uriRegister = getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
val builderRegister = getInstruction<OneRegisterInstruction>(returnIndex).registerA
|
||||||
|
val freeRegister = findFreeRegister(returnIndex, builderRegister)
|
||||||
|
|
||||||
addInstructions(
|
addInstructions(
|
||||||
invokeToStringIndex,
|
returnIndex,
|
||||||
"""
|
$$"""
|
||||||
invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
invoke-virtual { v$builderRegister }, Landroid/net/Uri$Builder;->build()Landroid/net/Uri;
|
||||||
move-result-object v$uriRegister
|
move-result-object v$freeRegister
|
||||||
|
invoke-static { v$freeRegister }, $$EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||||
|
move-result-object v$freeRegister
|
||||||
|
invoke-virtual { v$freeRegister }, Landroid/net/Uri;->buildUpon()Landroid/net/Uri$Builder;
|
||||||
|
move-result-object v$builderRegister
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package app.revanced.patches.spotify.misc.skip
|
||||||
|
|
||||||
|
import app.revanced.patcher.*
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchContext
|
||||||
|
import app.revanced.util.literal
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
|
||||||
|
// Getter on the locally-held player Restrictions model (deserialized from local player
|
||||||
|
// state, not fetched fresh from the server per call) that lists the reasons skipping to
|
||||||
|
// the next track is currently disallowed. A free-tier account gets a non-empty set here
|
||||||
|
// once the "6 skips per hour" cap is hit, and it is also checked while an ad is playing
|
||||||
|
// to decide whether the skip button is enabled. Every skip button in the app (Now Playing
|
||||||
|
// bar, ad track, notification, etc.) reads this same getter, so overriding it to always
|
||||||
|
// report "no restrictions" removes the skip cap and unlocks skipping ads, without ever
|
||||||
|
// telling the server the account's plan changed.
|
||||||
|
internal val BytecodePatchContext.disallowSkippingNextReasonsMethod by gettingFirstMethodDeclaratively {
|
||||||
|
name("disallowSkippingNextReasons")
|
||||||
|
accessFlags(AccessFlags.PUBLIC)
|
||||||
|
parameterTypes()
|
||||||
|
// Restrictions itself only declares the getter abstractly; skip past it to the
|
||||||
|
// AutoValue-generated implementation, which is the one with an actual method body.
|
||||||
|
custom { !AccessFlags.ABSTRACT.isSet(accessFlags) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local (client-side) rapid-skip cooldown tracker: keeps the timestamps of the last 3
|
||||||
|
// skips in a LinkedList and returns 3 ("Slow down! You get 6 skips per hour.") if all 3
|
||||||
|
// happened within 10 seconds (0x2710 ms) of each other, 2 (first-ever-skip "You now have
|
||||||
|
// 6 skips per hour" toast) the very first time it's called, or 1 (no toast) otherwise.
|
||||||
|
internal val BytecodePatchContext.freeTierRapidSkipCounterMethodMatch by composingFirstMethod {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
returnType("I")
|
||||||
|
parameterTypes()
|
||||||
|
instructions(
|
||||||
|
method { name == "elapsedRealtime" && returnType == "J" },
|
||||||
|
afterAtMost(50, 10000L()),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package app.revanced.patches.spotify.misc.skip
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.addInstructions
|
||||||
|
import app.revanced.patcher.firstClassDef
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.util.returnEarly
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val removeSkipRestrictionsPatch = bytecodePatch(
|
||||||
|
name = "Remove skip restrictions",
|
||||||
|
description = "Removes the free-tier \"6 skips per hour\" limit and its toasts/upsells, " +
|
||||||
|
"and unlocks the skip button while an ad is playing. This only changes local " +
|
||||||
|
"client-side behavior; it does not report a Premium account to Spotify.",
|
||||||
|
use = false,
|
||||||
|
) {
|
||||||
|
compatibleWith("com.spotify.music")
|
||||||
|
|
||||||
|
apply {
|
||||||
|
// Force the Restrictions getter that every skip button reads to always report
|
||||||
|
// that skipping is allowed, by having it construct and return an empty instance
|
||||||
|
// of its own (obfuscated) collection return type instead of the real field.
|
||||||
|
disallowSkippingNextReasonsMethod.apply {
|
||||||
|
val emptyCollectionType = returnType
|
||||||
|
|
||||||
|
val emptyCollectionFactoryMethod = firstClassDef(emptyCollectionType).methods.first {
|
||||||
|
AccessFlags.STATIC.isSet(it.accessFlags) &&
|
||||||
|
it.returnType == emptyCollectionType &&
|
||||||
|
it.parameterTypes.map(Any::toString) == listOf("I", "[Ljava/lang/Object;")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuses "this" (p0) as a scratch register instead of requiring a second
|
||||||
|
// local, since everything below this early return becomes dead code.
|
||||||
|
addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
const/4 v0, 0x0
|
||||||
|
new-array p0, v0, [Ljava/lang/Object;
|
||||||
|
invoke-static { v0, p0 }, ${emptyCollectionFactoryMethod.definingClass}->${emptyCollectionFactoryMethod.name}(I[Ljava/lang/Object;)$emptyCollectionType
|
||||||
|
move-result-object v0
|
||||||
|
return-object v0
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force the rapid-skip cooldown tracker to always behave like it does after the
|
||||||
|
// first-ever skip (return 1, no toast), instead of counting towards the cap.
|
||||||
|
freeTierRapidSkipCounterMethodMatch.method.returnEarly(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package app.revanced.patches.strava.route.export
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.addInstructions
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.strava.misc.extension.sharedExtensionPatch
|
||||||
|
|
||||||
|
private const val ROUTE_EXPORT_CLASS_DESCRIPTOR = "Lapp/revanced/extension/strava/AddRouteExportPatch;"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val addRouteExportPatch = bytecodePatch(
|
||||||
|
name = "Add route export",
|
||||||
|
description = "Extends the route menu with items to export them in GPX or TCX formats.",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.strava")
|
||||||
|
|
||||||
|
dependsOn(sharedExtensionPatch)
|
||||||
|
|
||||||
|
apply {
|
||||||
|
// Intercept onCreate to display the export dialog over.
|
||||||
|
shareSheetActivityOnCreateMethod.addInstructions(
|
||||||
|
0,
|
||||||
|
"invoke-static {p0}, $ROUTE_EXPORT_CLASS_DESCRIPTOR->showExportDialog(Landroid/app/Activity;)V"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package app.revanced.patches.strava.route.export
|
||||||
|
|
||||||
|
import app.revanced.patcher.definingClass
|
||||||
|
import app.revanced.patcher.gettingFirstMethodDeclaratively
|
||||||
|
import app.revanced.patcher.name
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchContext
|
||||||
|
|
||||||
|
internal val BytecodePatchContext.shareSheetActivityOnCreateMethod by gettingFirstMethodDeclaratively {
|
||||||
|
name("onCreate")
|
||||||
|
definingClass("Lcom/strava/sharing/view/ShareSheetActivity;")
|
||||||
|
}
|
||||||
@@ -71,3 +71,27 @@ internal val BytecodePatchContext.playerOverlayTimelyShelfMethod by gettingFirst
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 21.34+ removed the [String]"innertube_cue_range", "Null id" and "Null onExitActions" strings
|
||||||
|
* from the player overlay timely shelf handler, and the handler moved into a synthetic lambda
|
||||||
|
* (`a(Ljava/lang/Object;)V`) that dispatches on the overlay type via a `String.equals` comparison.
|
||||||
|
* Only [String]"player_overlay_timely_shelf" remains, so match on it plus the following
|
||||||
|
* `String.equals`/`move-result` used to check if the overlay is the timely shelf.
|
||||||
|
*/
|
||||||
|
internal val BytecodePatchContext.playerOverlayTimelyShelfMethod21_34 by composingFirstMethod {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
returnType("V")
|
||||||
|
parameterTypes("Ljava/lang/Object;")
|
||||||
|
instructions(
|
||||||
|
"player_overlay_timely_shelf"(),
|
||||||
|
afterAtMost(
|
||||||
|
5,
|
||||||
|
allOf(
|
||||||
|
Opcode.INVOKE_VIRTUAL(),
|
||||||
|
method { name == "equals" && definingClass == "Ljava/lang/String;" },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
after(Opcode.MOVE_RESULT()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import app.revanced.patches.youtube.misc.engagement.engagementPanelHookPatch
|
|||||||
import app.revanced.patches.youtube.misc.fix.backtoexitgesture.fixBackToExitGesturePatch
|
import app.revanced.patches.youtube.misc.fix.backtoexitgesture.fixBackToExitGesturePatch
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_20_14_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_14_or_greater
|
||||||
|
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
@@ -34,6 +35,7 @@ import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
|||||||
import app.revanced.util.injectHideViewCall
|
import app.revanced.util.injectHideViewCall
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||||
|
|
||||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
@@ -94,7 +96,8 @@ val hideAdsPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -154,17 +157,41 @@ val hideAdsPatch = bytecodePatch(
|
|||||||
// Hide player overlay view. This can be hidden with a regular litho filter
|
// Hide player overlay view. This can be hidden with a regular litho filter
|
||||||
// but an empty space remains.
|
// but an empty space remains.
|
||||||
if (is_20_14_or_greater) {
|
if (is_20_14_or_greater) {
|
||||||
playerOverlayTimelyShelfMethod.addInstructionsWithLabels(
|
if (is_21_34_or_greater) {
|
||||||
0,
|
// 21.34+ the overlay handler moved into a synthetic lambda `a(Object)V`
|
||||||
"""
|
// that dispatches on overlay type. Hook right after the `String.equals` /
|
||||||
invoke-static {}, ${EXTENSION_CLASS_DESCRIPTOR}->hideAds()Z
|
// `move-result` that determines the overlay is the timely shelf so that
|
||||||
move-result v0
|
// only the timely shelf is hidden and not every player overlay.
|
||||||
if-eqz v0, :show
|
playerOverlayTimelyShelfMethod21_34.let { match ->
|
||||||
return-void
|
val insertIndex = match[-1]
|
||||||
:show
|
val equalsResultRegister =
|
||||||
nop
|
match.method.getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||||
"""
|
|
||||||
)
|
match.method.addInstructionsWithLabels(
|
||||||
|
insertIndex + 1,
|
||||||
|
"""
|
||||||
|
invoke-static { v$equalsResultRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->allowAds(Z)Z
|
||||||
|
move-result v$equalsResultRegister
|
||||||
|
if-nez v$equalsResultRegister, :show
|
||||||
|
return-void
|
||||||
|
:show
|
||||||
|
nop
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
playerOverlayTimelyShelfMethod.addInstructionsWithLabels(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static {}, ${EXTENSION_CLASS_DESCRIPTOR}->hideAds()Z
|
||||||
|
move-result v0
|
||||||
|
if-eqz v0, :show
|
||||||
|
return-void
|
||||||
|
:show
|
||||||
|
nop
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ val videoAdsPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -31,7 +31,8 @@ val disableAutoCaptionsPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -31,7 +31,8 @@ val customBrandingPatch = baseCustomBrandingPatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-1
@@ -39,7 +39,8 @@ val hideAutoplayPreviewPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -13,6 +13,7 @@ import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
|||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_19_43_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_19_43_or_greater
|
||||||
|
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
@@ -68,7 +69,8 @@ val hideEndScreenCardsPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,7 +92,11 @@ val hideEndScreenCardsPatch = bytecodePatch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_19_43_or_greater) {
|
// 21.34+ the end screen cards are no longer shown through a dedicated 5-method
|
||||||
|
// container class that the legacy showEndscreenCardsMethod fingerprint can locate.
|
||||||
|
// Instead each card view (circle/icon/video) is created and hidden individually by the
|
||||||
|
// three layout fingerprints hooked above, which cover hiding the whole card on 21.34.
|
||||||
|
if (is_19_43_or_greater && !is_21_34_or_greater) {
|
||||||
showEndscreenCardsMethod.addInstructionsWithLabels(
|
showEndscreenCardsMethod.addInstructionsWithLabels(
|
||||||
0,
|
0,
|
||||||
"""
|
"""
|
||||||
|
|||||||
+2
-1
@@ -36,7 +36,8 @@ val hideEndScreenSuggestedVideoPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,8 @@ val disableFullscreenAmbientModePatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -31,7 +31,8 @@ val disablePlayerPopupPanelsPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,8 @@ val hideRelatedVideoOverlayPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,8 @@ val disableRollingNumberAnimationsPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -32,7 +32,8 @@ val disableSignInToTVPopupPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -30,7 +30,8 @@ val hideTimestampPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+121
-83
@@ -92,8 +92,15 @@ val miniplayerPatch = bytecodePatch(
|
|||||||
val preferences = mutableSetOf<BasePreference>()
|
val preferences = mutableSetOf<BasePreference>()
|
||||||
|
|
||||||
preferences +=
|
preferences +=
|
||||||
if (is_20_37_or_greater) {
|
if (is_20_37_or_greater && !is_21_34_or_greater) {
|
||||||
ListPreference("revanced_miniplayer_type")
|
ListPreference("revanced_miniplayer_type")
|
||||||
|
} else if (is_21_34_or_greater) {
|
||||||
|
// YouTube 21.29+ removed all modern miniplayers except "Modern 4",
|
||||||
|
// so the type picker no longer has any effect.
|
||||||
|
NonInteractivePreference(
|
||||||
|
key = "revanced_miniplayer_type",
|
||||||
|
summaryKey = "revanced_miniplayer_type_not_available_summary",
|
||||||
|
)
|
||||||
} else if (is_20_03_or_greater) {
|
} else if (is_20_03_or_greater) {
|
||||||
ListPreference(
|
ListPreference(
|
||||||
key = "revanced_miniplayer_type",
|
key = "revanced_miniplayer_type",
|
||||||
@@ -153,7 +160,9 @@ val miniplayerPatch = bytecodePatch(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences += TextPreference("revanced_miniplayer_opacity", inputType = InputType.NUMBER)
|
if (!is_21_34_or_greater) {
|
||||||
|
preferences += TextPreference("revanced_miniplayer_opacity", inputType = InputType.NUMBER)
|
||||||
|
}
|
||||||
|
|
||||||
PreferenceScreen.PLAYER.addPreferences(
|
PreferenceScreen.PLAYER.addPreferences(
|
||||||
PreferenceScreenPreference(
|
PreferenceScreenPreference(
|
||||||
@@ -265,101 +274,113 @@ val miniplayerPatch = bytecodePatch(
|
|||||||
|
|
||||||
// region Enable modern miniplayer.
|
// region Enable modern miniplayer.
|
||||||
|
|
||||||
miniplayerModernConstructorMethod.classDef.methods.forEach {
|
// Select which modern miniplayer type is used.
|
||||||
it.apply {
|
// YouTube 21.29+ removed all modern miniplayers except "Modern 4", so there is
|
||||||
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
|
// nothing to choose between and this override is dead code.
|
||||||
val iPutIndex = indexOfFirstInstructionOrThrow {
|
if (!is_21_34_or_greater) {
|
||||||
this.opcode == Opcode.IPUT && this.getReference<FieldReference>()?.type == "I"
|
miniplayerModernConstructorMethod.classDef.methods.forEach {
|
||||||
}
|
it.apply {
|
||||||
|
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
|
||||||
|
val iPutIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
this.opcode == Opcode.IPUT && this.getReference<FieldReference>()?.type == "I"
|
||||||
|
}
|
||||||
|
|
||||||
insertModernMiniplayerTypeOverride(iPutIndex)
|
insertModernMiniplayerTypeOverride(iPutIndex)
|
||||||
} else {
|
} else {
|
||||||
findReturnIndicesReversed().forEach { index ->
|
findReturnIndicesReversed().forEach { index ->
|
||||||
insertModernMiniplayerOverride(
|
insertModernMiniplayerOverride(
|
||||||
index
|
index
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_19_23_or_greater) {
|
// Modern miniplayer feature-flag overrides.
|
||||||
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
// YouTube 21.29+ removed all modern miniplayers except "Modern 4" and moved/removed the
|
||||||
MINIPLAYER_DRAG_DROP_FEATURE_KEY,
|
// feature flags for drag & drop, double-tap, rounded corners, horizontal drag and animated
|
||||||
"getMiniplayerDragAndDrop",
|
// expand. The default/minimum size flags were removed in 21.32+. The fingerprints below
|
||||||
)
|
// therefore no longer resolve on 21.34+, so these overrides only apply to older versions.
|
||||||
}
|
if (!is_21_34_or_greater) {
|
||||||
|
if (is_19_23_or_greater) {
|
||||||
if (is_19_25_or_greater) {
|
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
||||||
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
MINIPLAYER_DRAG_DROP_FEATURE_KEY,
|
||||||
MINIPLAYER_MODERN_FEATURE_LEGACY_KEY,
|
"getMiniplayerDragAndDrop",
|
||||||
"getModernMiniplayerOverride",
|
|
||||||
)
|
|
||||||
|
|
||||||
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
|
||||||
MINIPLAYER_MODERN_FEATURE_KEY,
|
|
||||||
"getModernFeatureFlagsActiveOverride",
|
|
||||||
)
|
|
||||||
|
|
||||||
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
|
||||||
MINIPLAYER_DOUBLE_TAP_FEATURE_KEY,
|
|
||||||
"getMiniplayerDoubleTapAction",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_19_26_or_greater) {
|
|
||||||
miniplayerModernConstructorMethod.apply {
|
|
||||||
val literalIndex = indexOfFirstLiteralInstructionOrThrow(
|
|
||||||
MINIPLAYER_INITIAL_SIZE_FEATURE_KEY,
|
|
||||||
)
|
|
||||||
val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.LONG_TO_INT)
|
|
||||||
val register = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
targetIndex + 1,
|
|
||||||
"""
|
|
||||||
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getMiniplayerDefaultSize(I)I
|
|
||||||
move-result v$register
|
|
||||||
""",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override a minimum size constant.
|
if (is_19_25_or_greater) {
|
||||||
miniplayerMinimumSizeMethodMatch.let {
|
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
||||||
it.method.apply {
|
MINIPLAYER_MODERN_FEATURE_LEGACY_KEY,
|
||||||
val index = it[1]
|
"getModernMiniplayerOverride",
|
||||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
)
|
||||||
|
|
||||||
// Smaller sizes can be used, but the miniplayer will always start in size 170 if set any smaller.
|
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
||||||
// The 170 initial limit probably could be patched to allow even smaller initial sizes,
|
MINIPLAYER_MODERN_FEATURE_KEY,
|
||||||
// but 170 is already half the horizontal space and smaller does not seem useful.
|
"getModernFeatureFlagsActiveOverride",
|
||||||
replaceInstruction(index, "const/16 v$register, 170")
|
)
|
||||||
|
|
||||||
|
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
||||||
|
MINIPLAYER_DOUBLE_TAP_FEATURE_KEY,
|
||||||
|
"getMiniplayerDoubleTapAction",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_19_26_or_greater) {
|
||||||
|
miniplayerModernConstructorMethod.apply {
|
||||||
|
val literalIndex = indexOfFirstLiteralInstructionOrThrow(
|
||||||
|
MINIPLAYER_INITIAL_SIZE_FEATURE_KEY,
|
||||||
|
)
|
||||||
|
val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.LONG_TO_INT)
|
||||||
|
val register = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
targetIndex + 1,
|
||||||
|
"""
|
||||||
|
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getMiniplayerDefaultSize(I)I
|
||||||
|
move-result v$register
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override a minimum size constant.
|
||||||
|
miniplayerMinimumSizeMethodMatch.let {
|
||||||
|
it.method.apply {
|
||||||
|
val index = it[1]
|
||||||
|
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||||
|
|
||||||
|
// Smaller sizes can be used, but the miniplayer will always start in size 170 if set any smaller.
|
||||||
|
// The 170 initial limit probably could be patched to allow even smaller initial sizes,
|
||||||
|
// but 170 is already half the horizontal space and smaller does not seem useful.
|
||||||
|
replaceInstruction(index, "const/16 v$register, 170")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (is_19_36_or_greater) {
|
if (is_19_36_or_greater) {
|
||||||
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
||||||
MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY,
|
MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY,
|
||||||
"getRoundedCorners",
|
"getRoundedCorners",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_19_43_or_greater) {
|
if (is_19_43_or_greater) {
|
||||||
miniplayerOnCloseHandlerMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
miniplayerOnCloseHandlerMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
||||||
MINIPLAYER_DISABLED_FEATURE_KEY,
|
MINIPLAYER_DISABLED_FEATURE_KEY,
|
||||||
"getMiniplayerOnCloseHandler",
|
"getMiniplayerOnCloseHandler",
|
||||||
)
|
)
|
||||||
|
|
||||||
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
||||||
MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY,
|
MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY,
|
||||||
"getHorizontalDrag",
|
"getHorizontalDrag",
|
||||||
)
|
)
|
||||||
|
|
||||||
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
||||||
MINIPLAYER_ANIMATED_EXPAND_FEATURE_KEY,
|
MINIPLAYER_ANIMATED_EXPAND_FEATURE_KEY,
|
||||||
"getMaximizeAnimation",
|
"getMaximizeAnimation",
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
@@ -391,7 +412,8 @@ val miniplayerPatch = bytecodePatch(
|
|||||||
|
|
||||||
// region fix minimal miniplayer using the wrong pause/play bold icons.
|
// region fix minimal miniplayer using the wrong pause/play bold icons.
|
||||||
|
|
||||||
if (is_20_31_or_greater) {
|
// 21.34 does not have the yt_fill_pause_* drawables this fingerprint anchors on.
|
||||||
|
if (is_20_31_or_greater && !is_21_34_or_greater) {
|
||||||
miniplayerSetIconsMethod.apply {
|
miniplayerSetIconsMethod.apply {
|
||||||
findInstructionIndicesReversedOrThrow {
|
findInstructionIndicesReversedOrThrow {
|
||||||
val reference = getReference<MethodReference>()
|
val reference = getReference<MethodReference>()
|
||||||
@@ -421,7 +443,6 @@ val miniplayerPatch = bytecodePatch(
|
|||||||
ClassDef::miniplayerModernActionButtonMethodMatch to "hideMiniplayerActionButton",
|
ClassDef::miniplayerModernActionButtonMethodMatch to "hideMiniplayerActionButton",
|
||||||
ClassDef::miniplayerModernRewindButtonMethodMatch to "hideMiniplayerRewindForward",
|
ClassDef::miniplayerModernRewindButtonMethodMatch to "hideMiniplayerRewindForward",
|
||||||
ClassDef::miniplayerModernForwardButtonMethodMatch to "hideMiniplayerRewindForward",
|
ClassDef::miniplayerModernForwardButtonMethodMatch to "hideMiniplayerRewindForward",
|
||||||
ClassDef::miniplayerModernOverlayViewMethodMatch to "adjustMiniplayerOpacity",
|
|
||||||
).forEach { (matching, methodName) ->
|
).forEach { (matching, methodName) ->
|
||||||
val match = matching.get(miniplayerModernViewParentMethod.immutableClassDef)
|
val match = matching.get(miniplayerModernViewParentMethod.immutableClassDef)
|
||||||
|
|
||||||
@@ -436,6 +457,23 @@ val miniplayerPatch = bytecodePatch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust the overlay opacity. The modern overlay was restructured in 21.29+ alongside the
|
||||||
|
// removal of the default/minimum size flags, so this hook is only applied on older versions.
|
||||||
|
if (!is_21_34_or_greater) {
|
||||||
|
val match = ClassDef::miniplayerModernOverlayViewMethodMatch
|
||||||
|
.get(miniplayerModernViewParentMethod.immutableClassDef)
|
||||||
|
|
||||||
|
match.method.apply {
|
||||||
|
val index = match[-1]
|
||||||
|
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||||
|
|
||||||
|
addInstruction(
|
||||||
|
index + 1,
|
||||||
|
"invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->adjustMiniplayerOpacity(Landroid/view/View;)V",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
miniplayerModernViewParentMethod.immutableClassDef.getMiniplayerModernAddViewListenerMethod()
|
miniplayerModernViewParentMethod.immutableClassDef.getMiniplayerModernAddViewListenerMethod()
|
||||||
.addInstruction(
|
.addInstruction(
|
||||||
0,
|
0,
|
||||||
|
|||||||
+96
-89
@@ -15,6 +15,7 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
|||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_20_09_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_09_or_greater
|
||||||
|
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
@@ -93,103 +94,109 @@ val shortsAutoplayPatch = bytecodePatch(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val reelPlaybackRepeatMethod =
|
// YouTube 21.29+ replaced the diagnostics string and reworked the reel playback repeat
|
||||||
reelPlaybackRepeatParentMethod.immutableClassDef.getReelPlaybackRepeatMethod()
|
// method (its signature changed from "V(L)" to "bL(JLbetu;J)V"), so the parent fingerprint
|
||||||
|
// below no longer resolves on 21.34+. The autoplay restore logic thus only applies to
|
||||||
reelPlaybackRepeatMethod.apply {
|
// versions below 21.34.
|
||||||
// The behavior enums are looked up from an ordinal value to an enum type.
|
if (!is_21_34_or_greater) {
|
||||||
findInstructionIndicesReversedOrThrow {
|
val reelPlaybackRepeatMethod =
|
||||||
val reference = getReference<MethodReference>()
|
reelPlaybackRepeatParentMethod.immutableClassDef.getReelPlaybackRepeatMethod()
|
||||||
reference?.definingClass == reelEnumClass &&
|
|
||||||
reference.parameterTypes.firstOrNull() == "I" &&
|
|
||||||
reference.returnType == reelEnumClass
|
|
||||||
}.forEach { index ->
|
|
||||||
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
index + 2,
|
|
||||||
"""
|
|
||||||
invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->changeShortsRepeatBehavior(Ljava/lang/Enum;)Ljava/lang/Enum;
|
|
||||||
move-result-object v$register
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// As of YouTube 20.09, Google has removed the code for 'Autoplay' and 'Pause' from this method.
|
|
||||||
// Manually restore the removed 'Autoplay' code.
|
|
||||||
if (is_20_09_or_greater) {
|
|
||||||
// Variable names are only a rough guess of what these methods do.
|
|
||||||
val userActionMethodReference =
|
|
||||||
reelPlaybackMethodMatch.method.getInstruction(reelPlaybackMethodMatch[1]).methodReference!!
|
|
||||||
val reelSequenceControllerMethodReference =
|
|
||||||
reelPlaybackMethodMatch.method.getInstruction(reelPlaybackMethodMatch[2]).methodReference!!
|
|
||||||
|
|
||||||
reelPlaybackRepeatMethod.apply {
|
reelPlaybackRepeatMethod.apply {
|
||||||
// Find the first call modified by extension code above.
|
// The behavior enums are looked up from an ordinal value to an enum type.
|
||||||
val extensionReturnResultIndex = indexOfFirstInstructionOrThrow {
|
findInstructionIndicesReversedOrThrow {
|
||||||
opcode == Opcode.INVOKE_STATIC &&
|
val reference = getReference<MethodReference>()
|
||||||
getReference<MethodReference>()?.definingClass == EXTENSION_CLASS_DESCRIPTOR
|
reference?.definingClass == reelEnumClass &&
|
||||||
} + 1
|
reference.parameterTypes.firstOrNull() == "I" &&
|
||||||
val enumRegister =
|
reference.returnType == reelEnumClass
|
||||||
getInstruction<OneRegisterInstruction>(extensionReturnResultIndex).registerA
|
}.forEach { index ->
|
||||||
val getReelSequenceControllerIndex = indexOfFirstInstructionOrThrow {
|
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
|
||||||
val reference = getReference<FieldReference>()
|
|
||||||
opcode == Opcode.IGET_OBJECT &&
|
|
||||||
reference?.definingClass == definingClass &&
|
|
||||||
reference.type == reelSequenceControllerMethodReference.definingClass
|
|
||||||
}
|
|
||||||
val getReelSequenceControllerReference =
|
|
||||||
getInstruction<ReferenceInstruction>(getReelSequenceControllerIndex).reference
|
|
||||||
|
|
||||||
// Add a helper method to avoid finding multiple free registers.
|
addInstructions(
|
||||||
// If enum is autoplay then method performs autoplay and returns null,
|
index + 2,
|
||||||
// otherwise returns the same enum.
|
|
||||||
val helperClass = definingClass
|
|
||||||
val helperName = "patch_handleAutoPlay"
|
|
||||||
val helperReturnType = "Ljava/lang/Enum;"
|
|
||||||
val helperMethod = ImmutableMethod(
|
|
||||||
helperClass,
|
|
||||||
helperName,
|
|
||||||
listOf(ImmutableMethodParameter("Ljava/lang/Enum;", null, null)),
|
|
||||||
helperReturnType,
|
|
||||||
AccessFlags.PRIVATE.value,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
MutableMethodImplementation(7),
|
|
||||||
).toMutable().apply {
|
|
||||||
addInstructionsWithLabels(
|
|
||||||
0,
|
|
||||||
"""
|
"""
|
||||||
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->isAutoPlay(Ljava/lang/Enum;)Z
|
invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->changeShortsRepeatBehavior(Ljava/lang/Enum;)Ljava/lang/Enum;
|
||||||
move-result v0
|
move-result-object v$register
|
||||||
if-eqz v0, :ignore
|
|
||||||
new-instance v0, ${userActionMethodReference.definingClass}
|
|
||||||
const/4 v1, 0x3
|
|
||||||
const/4 v2, 0x0
|
|
||||||
invoke-direct { v0, v1, v2, v2 }, $userActionMethodReference
|
|
||||||
iget-object v3, p0, $getReelSequenceControllerReference
|
|
||||||
invoke-virtual { v3, v0 }, $reelSequenceControllerMethodReference
|
|
||||||
const/4 v4, 0x0
|
|
||||||
return-object v4
|
|
||||||
:ignore
|
|
||||||
return-object p1
|
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
reelPlaybackRepeatMethod.classDef.methods.add(helperMethod)
|
}
|
||||||
|
|
||||||
addInstructionsWithLabels(
|
// As of YouTube 20.09, Google has removed the code for 'Autoplay' and 'Pause' from this method.
|
||||||
extensionReturnResultIndex + 1,
|
// Manually restore the removed 'Autoplay' code.
|
||||||
"""
|
if (is_20_09_or_greater) {
|
||||||
invoke-direct { p0, v$enumRegister }, $helperClass->$helperName(Ljava/lang/Enum;)$helperReturnType
|
// Variable names are only a rough guess of what these methods do.
|
||||||
move-result-object v$enumRegister
|
val userActionMethodReference =
|
||||||
if-nez v$enumRegister, :ignore
|
reelPlaybackMethodMatch.method.getInstruction(reelPlaybackMethodMatch[1]).methodReference!!
|
||||||
return-void # Autoplay was performed.
|
val reelSequenceControllerMethodReference =
|
||||||
:ignore
|
reelPlaybackMethodMatch.method.getInstruction(reelPlaybackMethodMatch[2]).methodReference!!
|
||||||
nop
|
|
||||||
""",
|
reelPlaybackRepeatMethod.apply {
|
||||||
)
|
// Find the first call modified by extension code above.
|
||||||
|
val extensionReturnResultIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
opcode == Opcode.INVOKE_STATIC &&
|
||||||
|
getReference<MethodReference>()?.definingClass == EXTENSION_CLASS_DESCRIPTOR
|
||||||
|
} + 1
|
||||||
|
val enumRegister =
|
||||||
|
getInstruction<OneRegisterInstruction>(extensionReturnResultIndex).registerA
|
||||||
|
val getReelSequenceControllerIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
val reference = getReference<FieldReference>()
|
||||||
|
opcode == Opcode.IGET_OBJECT &&
|
||||||
|
reference?.definingClass == definingClass &&
|
||||||
|
reference.type == reelSequenceControllerMethodReference.definingClass
|
||||||
|
}
|
||||||
|
val getReelSequenceControllerReference =
|
||||||
|
getInstruction<ReferenceInstruction>(getReelSequenceControllerIndex).reference
|
||||||
|
|
||||||
|
// Add a helper method to avoid finding multiple free registers.
|
||||||
|
// If enum is autoplay then method performs autoplay and returns null,
|
||||||
|
// otherwise returns the same enum.
|
||||||
|
val helperClass = definingClass
|
||||||
|
val helperName = "patch_handleAutoPlay"
|
||||||
|
val helperReturnType = "Ljava/lang/Enum;"
|
||||||
|
val helperMethod = ImmutableMethod(
|
||||||
|
helperClass,
|
||||||
|
helperName,
|
||||||
|
listOf(ImmutableMethodParameter("Ljava/lang/Enum;", null, null)),
|
||||||
|
helperReturnType,
|
||||||
|
AccessFlags.PRIVATE.value,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
MutableMethodImplementation(7),
|
||||||
|
).toMutable().apply {
|
||||||
|
addInstructionsWithLabels(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->isAutoPlay(Ljava/lang/Enum;)Z
|
||||||
|
move-result v0
|
||||||
|
if-eqz v0, :ignore
|
||||||
|
new-instance v0, ${userActionMethodReference.definingClass}
|
||||||
|
const/4 v1, 0x3
|
||||||
|
const/4 v2, 0x0
|
||||||
|
invoke-direct { v0, v1, v2, v2 }, $userActionMethodReference
|
||||||
|
iget-object v3, p0, $getReelSequenceControllerReference
|
||||||
|
invoke-virtual { v3, v0 }, $reelSequenceControllerMethodReference
|
||||||
|
const/4 v4, 0x0
|
||||||
|
return-object v4
|
||||||
|
:ignore
|
||||||
|
return-object p1
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
reelPlaybackRepeatMethod.classDef.methods.add(helperMethod)
|
||||||
|
|
||||||
|
addInstructionsWithLabels(
|
||||||
|
extensionReturnResultIndex + 1,
|
||||||
|
"""
|
||||||
|
invoke-direct { p0, v$enumRegister }, $helperClass->$helperName(Ljava/lang/Enum;)$helperReturnType
|
||||||
|
move-result-object v$enumRegister
|
||||||
|
if-nez v$enumRegister, :ignore
|
||||||
|
return-void # Autoplay was performed.
|
||||||
|
:ignore
|
||||||
|
nop
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -36,7 +36,8 @@ val changeStartPagePatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -29,7 +29,8 @@ val announcementsPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -12,6 +12,7 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
|||||||
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
|
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_20_29_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_29_or_greater
|
||||||
|
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
@@ -103,7 +104,10 @@ val removeBackgroundPlaybackRestrictionsPatch = bytecodePatch(
|
|||||||
kidsBackgroundPlaybackPolicyControllerMethod.returnEarly()
|
kidsBackgroundPlaybackPolicyControllerMethod.returnEarly()
|
||||||
|
|
||||||
// Fix PiP buttons not working after locking/unlocking device screen.
|
// Fix PiP buttons not working after locking/unlocking device screen.
|
||||||
if (is_19_34_or_greater) {
|
// YouTube 21.34+ removed the flag this anchors on (the remaining case is an
|
||||||
|
// unfixable Google-side issue, see RVX #2764). Skipping it avoids the
|
||||||
|
// fingerprint being unresolved and crashing the whole patch.
|
||||||
|
if (is_19_34_or_greater && !is_21_34_or_greater) {
|
||||||
pipInputConsumerFeatureFlagMethodMatch.let {
|
pipInputConsumerFeatureFlagMethodMatch.let {
|
||||||
it.method.insertLiteralOverride(
|
it.method.insertLiteralOverride(
|
||||||
it[0],
|
it[0],
|
||||||
|
|||||||
+19
-5
@@ -15,6 +15,7 @@ import app.revanced.patcher.patch.BytecodePatchContext
|
|||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.returnType
|
import app.revanced.patcher.returnType
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||||
import app.revanced.util.cloneMutableAndPreserveParameters
|
import app.revanced.util.cloneMutableAndPreserveParameters
|
||||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||||
@@ -73,12 +74,25 @@ val hookClientContextPatch = bytecodePatch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticationChangeListenerMethod.apply {
|
// The Authentication changed listener method (where the message lite builder method was
|
||||||
val messageLiteBuilderIndex =
|
// sourced from) was removed in YouTube 21.34+. Derive the builder method from the dummy
|
||||||
indexOfMessageLiteBuilderReference(this, messageLiteBuilderField.definingClass)
|
// client context body method instead, which still resolves and builds the same body.
|
||||||
|
if (!is_21_34_or_greater) {
|
||||||
|
authenticationChangeListenerMethod.apply {
|
||||||
|
val messageLiteBuilderIndex =
|
||||||
|
indexOfMessageLiteBuilderReference(this, messageLiteBuilderField.definingClass)
|
||||||
|
|
||||||
messageLiteBuilderMethod =
|
messageLiteBuilderMethod =
|
||||||
getInstruction<ReferenceInstruction>(messageLiteBuilderIndex).methodReference!!
|
getInstruction<ReferenceInstruction>(messageLiteBuilderIndex).methodReference!!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buildDummyClientContextBodyMethodMatch.method.apply {
|
||||||
|
val messageLiteBuilderIndex =
|
||||||
|
indexOfMessageLiteBuilderReference(this, messageLiteBuilderField.definingClass)
|
||||||
|
|
||||||
|
messageLiteBuilderMethod =
|
||||||
|
getInstruction<ReferenceInstruction>(messageLiteBuilderIndex).methodReference!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildClientContextBodyConstructorMethod.immutableClassDef.buildClientContextBodyMethodMatch.let {
|
buildClientContextBodyConstructorMethod.immutableClassDef.buildClientContextBodyMethodMatch.let {
|
||||||
|
|||||||
+2
-1
@@ -24,7 +24,8 @@ val enableDebuggingPatch = enableDebuggingPatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ internal val BytecodePatchContext.specificNetworkErrorViewControllerMethodMatch
|
|||||||
returnType("V")
|
returnType("V")
|
||||||
parameterTypes()
|
parameterTypes()
|
||||||
instructions(
|
instructions(
|
||||||
ResourceType.DRAWABLE("ic_offline_no_content_upside_down"),
|
|
||||||
ResourceType.STRING("offline_no_content_body_text_not_offline_eligible"),
|
ResourceType.STRING("offline_no_content_body_text_not_offline_eligible"),
|
||||||
method { name == "getString" && returnType == "Ljava/lang/String;" },
|
method { name == "getString" && returnType == "Ljava/lang/String;" },
|
||||||
after(Opcode.MOVE_RESULT_OBJECT()),
|
after(Opcode.MOVE_RESULT_OBJECT()),
|
||||||
@@ -20,12 +19,13 @@ internal val BytecodePatchContext.specificNetworkErrorViewControllerMethodMatch
|
|||||||
|
|
||||||
// It's not clear if this second class is ever used, and it may be dead code,
|
// It's not clear if this second class is ever used, and it may be dead code,
|
||||||
// but it is the layout image/text is identical to the network error match above.
|
// but it is the layout image/text is identical to the network error match above.
|
||||||
|
// The offline image drawable (ic_offline_no_content_upside_down) was removed in 21.34,
|
||||||
|
// so only the still-present offline_no_content string is matched.
|
||||||
internal val BytecodePatchContext.loadingFrameLayoutControllerMethodMatch by composingFirstMethod {
|
internal val BytecodePatchContext.loadingFrameLayoutControllerMethodMatch by composingFirstMethod {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
returnType("V")
|
returnType("V")
|
||||||
parameterTypes("L")
|
parameterTypes("Z")
|
||||||
instructions(
|
instructions(
|
||||||
ResourceType.DRAWABLE("ic_offline_no_content_upside_down"),
|
|
||||||
ResourceType.STRING("offline_no_content_body_text_not_offline_eligible"),
|
ResourceType.STRING("offline_no_content_body_text_not_offline_eligible"),
|
||||||
method { name == "getString" && returnType == "Ljava/lang/String;" },
|
method { name == "getString" && returnType == "Ljava/lang/String;" },
|
||||||
after(Opcode.MOVE_RESULT_OBJECT()),
|
after(Opcode.MOVE_RESULT_OBJECT()),
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,8 @@ val bypassURLRedirectsPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -47,7 +47,8 @@ val openLinksExternallyPatch = bytecodePatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+12
-7
@@ -59,13 +59,18 @@ val lithoFilterPatch = lithoFilterPatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Turn off a feature flag that enables native code of protobuf parsing (Upb protobuf).
|
// Turn off a feature flag that enables native code of protobuf parsing (Upb protobuf).
|
||||||
lithoConverterBufferUpbFeatureFlagMethodMatch.let {
|
// The flag (45419603) was removed in 21.34. In 21.34 the native UPB buffer path is the
|
||||||
// 20.22 the flag is still enabled in one location, but what it does is not known.
|
// default (and required) path for the setProtoBuffer([B) hook, so nothing needs to be
|
||||||
// Disable it anyway.
|
// forced off.
|
||||||
it.method.insertLiteralOverride(
|
if (!is_21_34_or_greater) {
|
||||||
it[0],
|
lithoConverterBufferUpbFeatureFlagMethodMatch.let {
|
||||||
false,
|
// 20.22 the flag is still enabled in one location, but what it does is not known.
|
||||||
)
|
// Disable it anyway.
|
||||||
|
it.method.insertLiteralOverride(
|
||||||
|
it[0],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|||||||
+21
@@ -34,6 +34,27 @@ internal val ClassDef.motionEventMethodMatch by ClassDefComposing.composingFirst
|
|||||||
instructions(method("setTranslationY"))
|
instructions(method("setTranslationY"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The player controls overlay was rebuilt in 21.34 and no longer calls `setTranslationY`.
|
||||||
|
// Instead the tap-to-dismiss handler is `nwo.b(Landroid/view/MotionEvent;)V`, which triggers the
|
||||||
|
// visibility change via `nwo.U()V`. These two fingerprints resolve that new hook and keep the
|
||||||
|
// `visibilityNegatedImmediate` injection working for the newer layout.
|
||||||
|
internal val BytecodePatchContext.youtubeControlsOverlayMethod21_34 by gettingFirstImmutableMethodDeclaratively {
|
||||||
|
returnType("V")
|
||||||
|
parameterTypes("Landroid/view/MotionEvent;")
|
||||||
|
instructions(
|
||||||
|
field { definingClass == "Lnwo;" && name == "br" },
|
||||||
|
method { definingClass == "Lnwo;" && name == "U" },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val ClassDef.motionEventMethodMatch21_34 by ClassDefComposing.composingFirstMethod {
|
||||||
|
returnType("V")
|
||||||
|
parameterTypes("Landroid/view/MotionEvent;")
|
||||||
|
instructions(
|
||||||
|
method { definingClass == "Lnwo;" && name == "U" },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
internal val BytecodePatchContext.playerControlsExtensionHookListenersExistMethod by gettingFirstMethodDeclaratively {
|
internal val BytecodePatchContext.playerControlsExtensionHookListenersExistMethod by gettingFirstMethodDeclaratively {
|
||||||
name("fullscreenButtonVisibilityCallbacksExist")
|
name("fullscreenButtonVisibilityCallbacksExist")
|
||||||
definingClass(EXTENSION_CLASS_DESCRIPTOR)
|
definingClass(EXTENSION_CLASS_DESCRIPTOR)
|
||||||
|
|||||||
+7
-1
@@ -268,7 +268,13 @@ val playerControlsPatch = bytecodePatch(
|
|||||||
visibilityImmediateCallbacksExistMethod = playerControlsExtensionHookListenersExistMethod
|
visibilityImmediateCallbacksExistMethod = playerControlsExtensionHookListenersExistMethod
|
||||||
visibilityImmediateMethod = playerControlsExtensionHookMethod
|
visibilityImmediateMethod = playerControlsExtensionHookMethod
|
||||||
|
|
||||||
youtubeControlsOverlayMethod.immutableClassDef.motionEventMethodMatch.let {
|
val motionEventMethodMatch = if (is_21_34_or_greater) {
|
||||||
|
youtubeControlsOverlayMethod21_34.immutableClassDef.motionEventMethodMatch21_34
|
||||||
|
} else {
|
||||||
|
youtubeControlsOverlayMethod.immutableClassDef.motionEventMethodMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
motionEventMethodMatch.let {
|
||||||
visibilityNegatedImmediateMethod = it.method
|
visibilityNegatedImmediateMethod = it.method
|
||||||
visibilityNegatedImmediateInsertIndex = it[0] + 1
|
visibilityNegatedImmediateInsertIndex = it[0] + 1
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -127,6 +127,8 @@ var is_21_07_or_greater : Boolean by Delegates.notNull()
|
|||||||
private set
|
private set
|
||||||
var is_21_08_or_greater : Boolean by Delegates.notNull()
|
var is_21_08_or_greater : Boolean by Delegates.notNull()
|
||||||
private set
|
private set
|
||||||
|
var is_21_34_or_greater: Boolean by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
|
||||||
val versionCheckPatch = resourcePatch(
|
val versionCheckPatch = resourcePatch(
|
||||||
description = "Uses the Play Store service version to find the major/minor version of the YouTube target app.",
|
description = "Uses the Play Store service version to find the major/minor version of the YouTube target app.",
|
||||||
@@ -184,5 +186,6 @@ val versionCheckPatch = resourcePatch(
|
|||||||
is_21_06_or_greater = 260705000 <= playStoreServicesVersion
|
is_21_06_or_greater = 260705000 <= playStoreServicesVersion
|
||||||
is_21_07_or_greater = 260805000 <= playStoreServicesVersion
|
is_21_07_or_greater = 260805000 <= playStoreServicesVersion
|
||||||
is_21_08_or_greater = 260905000 <= playStoreServicesVersion
|
is_21_08_or_greater = 260905000 <= playStoreServicesVersion
|
||||||
|
is_21_34_or_greater = 263410000 <= playStoreServicesVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -20,7 +20,8 @@ val sanitizeSharingLinksPatch = sanitizeSharingLinksPatch(
|
|||||||
"20.26.46",
|
"20.26.46",
|
||||||
"20.31.42",
|
"20.31.42",
|
||||||
"20.37.48",
|
"20.37.48",
|
||||||
"20.40.45"
|
"20.40.45",
|
||||||
|
"21.34.248"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,6 +21,27 @@ internal val BytecodePatchContext.setThemeMethod by gettingFirstMethodDeclarativ
|
|||||||
instructions(ResourceType.STRING("app_theme_appearance_dark"))
|
instructions(ResourceType.STRING("app_theme_appearance_dark"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* YouTube 21.34+ appearance resolver `Latyq;->t()Lkll;`.
|
||||||
|
*
|
||||||
|
* Returns the theme appearance enum (whose ordinal 1 is dark), resolving between the
|
||||||
|
* `app_theme_appearance_*` prefs, the dark-mode flag and the system UI mode. Among all
|
||||||
|
* `public final ()Lkll` methods it is uniquely identified by reading `Lklm;->f` (the stored
|
||||||
|
* appearance value string): the sibling helpers `u()`/`v()` read `Lklm;->e`/the system UI mode
|
||||||
|
* instead, while `qyn` reads `Lklm;->f` but returns `V`.
|
||||||
|
*/
|
||||||
|
internal val BytecodePatchContext.setThemeMethod21_34 by gettingFirstMethodDeclaratively {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
returnType("Lkll;")
|
||||||
|
parameterTypes()
|
||||||
|
instructions(
|
||||||
|
allOf(
|
||||||
|
Opcode.IGET_OBJECT(),
|
||||||
|
field { name == "f" && type == "Ljava/lang/String;" && definingClass == "Lklm;" },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
internal val BytecodePatchContext.cairoFragmentConfigMethodMatch by composingFirstMethod {
|
internal val BytecodePatchContext.cairoFragmentConfigMethodMatch by composingFirstMethod {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
returnType("Z")
|
returnType("Z")
|
||||||
@@ -29,3 +50,22 @@ internal val BytecodePatchContext.cairoFragmentConfigMethodMatch by composingFir
|
|||||||
afterAtMost(10, Opcode.MOVE_RESULT()),
|
afterAtMost(10, Opcode.MOVE_RESULT()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* YouTube 21.34+ Cairo settings gate `Loqy;->run()V`.
|
||||||
|
*
|
||||||
|
* The legacy `public final ()Z` feature-flag gate no longer exists. In 21.34 the Cairo settings
|
||||||
|
* screen is selected by feature flag `0x2b6c3c4` inside `Loqy;->run()V`, which chooses between the
|
||||||
|
* legacy (`xml/settings_fragment`, 0x7f180027) and Cairo (`xml/settings_fragment_cairo`,
|
||||||
|
* 0x7f180028) settings screens. The `0x7f180028` literal only appears in this method, which keeps
|
||||||
|
* it unambiguous among the other `0x2b6c3c4` references.
|
||||||
|
*/
|
||||||
|
internal val BytecodePatchContext.cairoFragmentConfigMethodMatch21_34 by composingFirstMethod {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
returnType("V")
|
||||||
|
instructions(
|
||||||
|
45532100L(),
|
||||||
|
afterAtMost(10, Opcode.MOVE_RESULT()),
|
||||||
|
afterAtMost(10, 2132279336L()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
+76
-5
@@ -23,6 +23,7 @@ import app.revanced.patches.youtube.misc.fix.contentprovider.fixContentProviderP
|
|||||||
import app.revanced.patches.youtube.misc.fix.playbackspeed.fixPlaybackSpeedWhilePlayingPatch
|
import app.revanced.patches.youtube.misc.fix.playbackspeed.fixPlaybackSpeedWhilePlayingPatch
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_20_31_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_31_or_greater
|
||||||
|
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||||
import app.revanced.util.*
|
import app.revanced.util.*
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
@@ -214,6 +215,74 @@ val settingsPatch = bytecodePatch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
PreferenceScreen.MISC.addPreferences(
|
PreferenceScreen.MISC.addPreferences(
|
||||||
|
PreferenceCategory(
|
||||||
|
titleKey = "revanced_custom_title",
|
||||||
|
sorting = Sorting.UNSORTED,
|
||||||
|
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
|
||||||
|
preferences = setOf(
|
||||||
|
NonInteractivePreference(
|
||||||
|
key = "revanced_custom_reset_title",
|
||||||
|
titleKey = "revanced_custom_reset_title",
|
||||||
|
summaryKey = "revanced_custom_reset_summary",
|
||||||
|
tag = "app.revanced.extension.shared.settings.preference.CustomSettingsResetPreference",
|
||||||
|
),
|
||||||
|
SwitchPreference(
|
||||||
|
"revanced_custom_force_amoled",
|
||||||
|
),
|
||||||
|
SwitchPreference(
|
||||||
|
"revanced_custom_pure_black_player",
|
||||||
|
),
|
||||||
|
SwitchPreference(
|
||||||
|
"revanced_custom_disable_ambient_mode",
|
||||||
|
),
|
||||||
|
SwitchPreference(
|
||||||
|
"revanced_custom_hide_shorts",
|
||||||
|
),
|
||||||
|
// TEMPORARILY DISABLED FOR TESTING (revanced-cli verification pass): this
|
||||||
|
// ListPreference references "${key}_entries"/"${key}_entry_values" array
|
||||||
|
// resources that don't exist anywhere and can't be added via addresources/
|
||||||
|
// values/strings.xml, since patches/strings-processing.gradle.kts only
|
||||||
|
// extracts <string> elements (see its `"string" -> transform(...)` case
|
||||||
|
// vs. the `else -> walk children` fallthrough that silently drops <array>/
|
||||||
|
// <string-array>). Needs a real app.revanced.util.resource.ArrayResource
|
||||||
|
// wired through the ListPreference(entries=, entryValues=) constructor
|
||||||
|
// (see patches/.../shared/misc/settings/preference/ListPreference.kt) or
|
||||||
|
// another array-resource source before this can ship.
|
||||||
|
// ListPreference(
|
||||||
|
// key = "revanced_custom_default_playback_speed",
|
||||||
|
// tag = "app.revanced.extension.shared.settings.preference.SortedListPreference",
|
||||||
|
// ),
|
||||||
|
SwitchPreference(
|
||||||
|
"revanced_custom_remember_playback_speed",
|
||||||
|
),
|
||||||
|
// ListPreference(
|
||||||
|
// key = "revanced_custom_default_video_quality",
|
||||||
|
// tag = "app.revanced.extension.shared.settings.preference.SortedListPreference",
|
||||||
|
// ),
|
||||||
|
SwitchPreference(
|
||||||
|
"revanced_custom_remember_video_quality",
|
||||||
|
),
|
||||||
|
SwitchPreference(
|
||||||
|
"revanced_custom_force_highest_quality",
|
||||||
|
),
|
||||||
|
SwitchPreference(
|
||||||
|
"revanced_custom_debug_video_info",
|
||||||
|
),
|
||||||
|
TextPreference(
|
||||||
|
key = "revanced_custom_app_name",
|
||||||
|
titleKey = "revanced_custom_app_name_title",
|
||||||
|
inputType = InputType.TEXT_CAP_CHARACTERS,
|
||||||
|
tag = "app.revanced.extension.shared.settings.preference.TextInputPreference",
|
||||||
|
),
|
||||||
|
// ListPreference(
|
||||||
|
// key = "revanced_custom_startup_tab",
|
||||||
|
// tag = "app.revanced.extension.shared.settings.preference.SortedListPreference",
|
||||||
|
// ),
|
||||||
|
SwitchPreference(
|
||||||
|
"revanced_custom_clean_urls",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
TextPreference(
|
TextPreference(
|
||||||
key = null,
|
key = null,
|
||||||
titleKey = "revanced_pref_import_export_title",
|
titleKey = "revanced_pref_import_export_title",
|
||||||
@@ -230,7 +299,7 @@ val settingsPatch = bytecodePatch(
|
|||||||
// Update shared dark mode status based on YT theme.
|
// Update shared dark mode status based on YT theme.
|
||||||
// This is needed because YT allows forcing light/dark mode
|
// This is needed because YT allows forcing light/dark mode
|
||||||
// which then differs from the system dark mode status.
|
// which then differs from the system dark mode status.
|
||||||
setThemeMethod.apply {
|
(if (is_21_34_or_greater) setThemeMethod21_34 else setThemeMethod).apply {
|
||||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
|
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
|
||||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||||
addInstructionsAtControlFlowLabel(
|
addInstructionsAtControlFlowLabel(
|
||||||
@@ -241,10 +310,12 @@ val settingsPatch = bytecodePatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add setting to force Cairo settings fragment on/off.
|
// Add setting to force Cairo settings fragment on/off.
|
||||||
cairoFragmentConfigMethodMatch.method.insertLiteralOverride(
|
(if (is_21_34_or_greater) cairoFragmentConfigMethodMatch21_34 else cairoFragmentConfigMethodMatch).let {
|
||||||
cairoFragmentConfigMethodMatch[0],
|
it.method.insertLiteralOverride(
|
||||||
"$YOUTUBE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->useCairoSettingsFragment(Z)Z",
|
it[0],
|
||||||
)
|
"$YOUTUBE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->useCairoSettingsFragment(Z)Z",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Bold icon resources are found starting in 20.23, but many YT icons are not bold.
|
// Bold icon resources are found starting in 20.23, but many YT icons are not bold.
|
||||||
// 20.31 is the first version that seems to have all the bold icons.
|
// 20.31 is the first version that seems to have all the bold icons.
|
||||||
|
|||||||
+26
@@ -183,6 +183,32 @@ internal val BytecodePatchContext.playbackSpeedClassMethod by gettingFirstMethod
|
|||||||
opcodes(Opcode.RETURN_OBJECT)
|
opcodes(Opcode.RETURN_OBJECT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* YouTube 21.34+ playback-speed bottom sheet fragment factory `Lpzy;->aW(Lce;Ljava/lang/String;)Lpzy;`.
|
||||||
|
*
|
||||||
|
* In 21.34 the `PLAYBACK_RATE_MENU_BOTTOM_SHEET_FRAGMENT` string moved into the large state-holder
|
||||||
|
* factory `Lhxz;->p()Ljava/lang/Object;` (which returns `Ljava/lang/Object;`, not the fragment type),
|
||||||
|
* so the legacy `playbackSpeedClassMethod` fingerprint no longer resolves. The fragment is instead
|
||||||
|
* obtained from its static factory, lookup by tag with fallback creation, which returns the exact
|
||||||
|
* fragment instance (`Lpzy;`). It is uniquely identified by the exact return type `Lpzy;` together
|
||||||
|
* with the `getSupportFragmentManager` -> `f(Ljava/lang/String;)L` lookup sequence.
|
||||||
|
*/
|
||||||
|
internal val BytecodePatchContext.playbackSpeedClassMethod21_34 by gettingFirstMethodDeclaratively {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||||
|
returnType("Lpzy;")
|
||||||
|
parameterTypes("L", "Ljava/lang/String;")
|
||||||
|
instructions(
|
||||||
|
method("getSupportFragmentManager"),
|
||||||
|
after(Opcode.MOVE_RESULT_OBJECT()),
|
||||||
|
after(method {
|
||||||
|
returnType.startsWith("L") && parameterTypes.size == 1 && parameterTypes.first() == "Ljava/lang/String;"
|
||||||
|
}),
|
||||||
|
after(Opcode.MOVE_RESULT_OBJECT()),
|
||||||
|
after(Opcode.IF_EQZ()),
|
||||||
|
after(Opcode.CHECK_CAST()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YouTube 20.19 and lower.
|
* YouTube 20.19 and lower.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+2
-1
@@ -12,6 +12,7 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
|||||||
import app.revanced.patches.youtube.misc.playservice.is_20_19_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_19_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_20_20_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_20_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_20_49_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_49_or_greater
|
||||||
|
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||||
import app.revanced.patches.youtube.shared.videoQualityChangedMethodMatch
|
import app.revanced.patches.youtube.shared.videoQualityChangedMethodMatch
|
||||||
import app.revanced.patches.youtube.video.playerresponse.Hook
|
import app.revanced.patches.youtube.video.playerresponse.Hook
|
||||||
@@ -283,7 +284,7 @@ val videoInformationPatch = bytecodePatch(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackSpeedClassMethod.apply {
|
(if (is_21_34_or_greater) playbackSpeedClassMethod21_34 else playbackSpeedClassMethod).apply {
|
||||||
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT)
|
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT)
|
||||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||||
val playbackSpeedClass = this.returnType
|
val playbackSpeedClass = this.returnType
|
||||||
|
|||||||
@@ -241,6 +241,42 @@ However, enabling this will also log some user data such as your IP address."</s
|
|||||||
<string name="revanced_restore_old_settings_menus_title">Restore old settings menus</string>
|
<string name="revanced_restore_old_settings_menus_title">Restore old settings menus</string>
|
||||||
<string name="revanced_restore_old_settings_menus_summary_on">Old settings menus are shown</string>
|
<string name="revanced_restore_old_settings_menus_summary_on">Old settings menus are shown</string>
|
||||||
<string name="revanced_restore_old_settings_menus_summary_off">Old settings menus are not shown</string>
|
<string name="revanced_restore_old_settings_menus_summary_off">Old settings menus are not shown</string>
|
||||||
|
<!-- Custom settings -->
|
||||||
|
<string name="revanced_custom_title">Custom</string>
|
||||||
|
<string name="revanced_custom_reset_title">Reset Custom Settings</string>
|
||||||
|
<string name="revanced_custom_reset_summary">Restore custom preferences to defaults</string>
|
||||||
|
<string name="revanced_custom_force_amoled_title">Force AMOLED</string>
|
||||||
|
<string name="revanced_custom_force_amoled_summary_on">True black is used in the YouTube UI</string>
|
||||||
|
<string name="revanced_custom_force_amoled_summary_off">True black is not used in the YouTube UI</string>
|
||||||
|
<string name="revanced_custom_pure_black_player_title">Pure Black Player</string>
|
||||||
|
<string name="revanced_custom_pure_black_player_summary_on">Player background is pure black</string>
|
||||||
|
<string name="revanced_custom_pure_black_player_summary_off">Player background is not pure black</string>
|
||||||
|
<string name="revanced_custom_disable_ambient_mode_title">Disable Ambient Mode</string>
|
||||||
|
<string name="revanced_custom_disable_ambient_mode_summary_on">Ambient Mode is prevented from activating</string>
|
||||||
|
<string name="revanced_custom_disable_ambient_mode_summary_off">Ambient Mode can activate</string>
|
||||||
|
<string name="revanced_custom_hide_shorts_title">Hide Shorts</string>
|
||||||
|
<string name="revanced_custom_hide_shorts_summary_on">Shorts UI entries are removed</string>
|
||||||
|
<string name="revanced_custom_hide_shorts_summary_off">Shorts UI entries are shown</string>
|
||||||
|
<string name="revanced_custom_default_playback_speed_title">Default Playback Speed</string>
|
||||||
|
<string name="revanced_custom_remember_playback_speed_title">Remember Playback Speed</string>
|
||||||
|
<string name="revanced_custom_remember_playback_speed_summary_on">Playback speed is remembered across videos</string>
|
||||||
|
<string name="revanced_custom_remember_playback_speed_summary_off">Playback speed is not remembered across videos</string>
|
||||||
|
<string name="revanced_custom_default_video_quality_title">Default Video Quality</string>
|
||||||
|
<string name="revanced_custom_remember_video_quality_title">Remember Video Quality</string>
|
||||||
|
<string name="revanced_custom_remember_video_quality_summary_on">Video quality is remembered across videos</string>
|
||||||
|
<string name="revanced_custom_remember_video_quality_summary_off">Video quality is not remembered across videos</string>
|
||||||
|
<string name="revanced_custom_force_highest_quality_title">Force Highest Available Quality</string>
|
||||||
|
<string name="revanced_custom_force_highest_quality_summary_on">Highest quality option is preferred</string>
|
||||||
|
<string name="revanced_custom_force_highest_quality_summary_off">Highest quality option is not preferred</string>
|
||||||
|
<string name="revanced_custom_debug_video_info_title">Debug Video Information</string>
|
||||||
|
<string name="revanced_custom_debug_video_info_summary_on">Technical video details are shown</string>
|
||||||
|
<string name="revanced_custom_debug_video_info_summary_off">Technical video details are hidden</string>
|
||||||
|
<string name="revanced_custom_app_name_title">Custom App Name</string>
|
||||||
|
<string name="revanced_custom_app_name_summary">Change the visible application name</string>
|
||||||
|
<string name="revanced_custom_startup_tab_title">Default Startup Tab</string>
|
||||||
|
<string name="revanced_custom_clean_urls_title">Clean YouTube URLs</string>
|
||||||
|
<string name="revanced_custom_clean_urls_summary_on">Tracking parameters are removed from shared links</string>
|
||||||
|
<string name="revanced_custom_clean_urls_summary_off">Tracking parameters are not removed from shared links</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
|
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
|
||||||
<string name="revanced_shorts_disable_background_playback_title">Disable Shorts background play</string>
|
<string name="revanced_shorts_disable_background_playback_title">Disable Shorts background play</string>
|
||||||
@@ -1516,6 +1552,7 @@ Limitation: Using the back button on the toolbar may not work"</string>
|
|||||||
<string name="revanced_miniplayer_type_entry_5">Modern 2</string>
|
<string name="revanced_miniplayer_type_entry_5">Modern 2</string>
|
||||||
<string name="revanced_miniplayer_type_entry_6">Modern 3</string>
|
<string name="revanced_miniplayer_type_entry_6">Modern 3</string>
|
||||||
<string name="revanced_miniplayer_type_entry_7">Modern 4</string>
|
<string name="revanced_miniplayer_type_entry_7">Modern 4</string>
|
||||||
|
<string name="revanced_miniplayer_type_not_available_summary">Selecting a miniplayer type is no longer available on your YouTube version.</string>
|
||||||
<string name="revanced_miniplayer_disable_rounded_corners_title">Disable rounded corners</string>
|
<string name="revanced_miniplayer_disable_rounded_corners_title">Disable rounded corners</string>
|
||||||
<string name="revanced_miniplayer_disable_rounded_corners_summary_on">Corners are square</string>
|
<string name="revanced_miniplayer_disable_rounded_corners_summary_on">Corners are square</string>
|
||||||
<string name="revanced_miniplayer_disable_rounded_corners_summary_off">Corners are rounded</string>
|
<string name="revanced_miniplayer_disable_rounded_corners_summary_off">Corners are rounded</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user