diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4a594855..b70fb7efc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,40 @@
+# [5.48.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.8...v5.48.0-dev.9) (2026-01-08)
+
+
+### Features
+
+* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305))
+* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c))
+
+# [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04)
+
+
+### Features
+
+* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc))
+
+# [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04)
+
+
+### Features
+
+* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc))
+* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567))
+
+# [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04)
+
+
+### Bug Fixes
+
+* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8))
+
+# [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30)
+
+
+### Bug Fixes
+
+* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154))
+
# [5.48.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.3...v5.48.0-dev.4) (2025-12-29)
diff --git a/crowdin.yml b/crowdin.yml
index 148f321cd..81022c88c 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -1,8 +1,9 @@
project_id_env: "CROWDIN_PROJECT_ID"
api_token_env: "CROWDIN_PERSONAL_TOKEN"
-preserve_hierarchy: false
+preserve_hierarchy: true
files:
- source: patches/src/main/resources/addresources/values/strings.xml
+ dest: patches.xml
translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml
skip_untranslated_strings: true
diff --git a/extensions/all/misc/disable-play-integrity/build.gradle.kts b/extensions/all/misc/disable-play-integrity/build.gradle.kts
new file mode 100644
index 000000000..549297227
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/build.gradle.kts
@@ -0,0 +1,20 @@
+android {
+ namespace = "app.revanced.extension"
+
+ defaultConfig {
+ minSdk = 21
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ buildFeatures {
+ aidl = true
+ }
+}
+
+dependencies {
+ compileOnly(libs.annotation)
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/AndroidManifest.xml b/extensions/all/misc/disable-play-integrity/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..9b65eb06c
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityService.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityService.aidl
new file mode 100644
index 000000000..7b8f59f1d
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityService.aidl
@@ -0,0 +1,8 @@
+package com.google.android.play.core.integrity.protocol;
+
+import android.os.Bundle;
+import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback;
+
+interface IExpressIntegrityService {
+ oneway void requestIntegrityToken(in Bundle request, IExpressIntegrityServiceCallback callback) = 2;
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityServiceCallback.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityServiceCallback.aidl
new file mode 100644
index 000000000..624167afb
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityServiceCallback.aidl
@@ -0,0 +1,5 @@
+package com.google.android.play.core.integrity.protocol;
+
+interface IExpressIntegrityServiceCallback {
+ oneway void onRequestExpressIntegrityTokenResult(in Bundle result) = 2;
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityService.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityService.aidl
new file mode 100644
index 000000000..bb1bcd551
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityService.aidl
@@ -0,0 +1,8 @@
+package com.google.android.play.core.integrity.protocol;
+
+import android.os.Bundle;
+import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback;
+
+interface IIntegrityService {
+ oneway void requestIntegrityToken(in Bundle request, IIntegrityServiceCallback callback) = 1;
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityServiceCallback.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityServiceCallback.aidl
new file mode 100644
index 000000000..9485ec169
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityServiceCallback.aidl
@@ -0,0 +1,7 @@
+package com.google.android.play.core.integrity.protocol;
+
+import android.os.Bundle;
+
+interface IIntegrityServiceCallback {
+ oneway void onResult(in Bundle result) = 1;
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/android/ext/PackageId.java b/extensions/all/misc/disable-play-integrity/src/main/java/android/ext/PackageId.java
new file mode 100644
index 000000000..31c2ca6db
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/java/android/ext/PackageId.java
@@ -0,0 +1,10 @@
+package android.ext;
+/** @hide */
+// Int values that are assigned to packages in this interface can be retrieved at runtime from
+// ApplicationInfo.ext().getPackageId() or from AndroidPackage.ext().getPackageId() (in system_server).
+//
+// PackageIds are assigned to parsed APKs only after they are verified, either by a certificate check
+// or by a check that the APK is stored on an immutable OS partition.
+public interface PackageId {
+ String PLAY_STORE_NAME = "com.android.vending";
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/android/os/BinderWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/android/os/BinderWrapper.java
new file mode 100644
index 000000000..a01806441
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/java/android/os/BinderWrapper.java
@@ -0,0 +1,62 @@
+package android.os;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.FileDescriptor;
+
+/** @hide */
+public class BinderWrapper implements IBinder {
+ protected final IBinder base;
+
+ public BinderWrapper(IBinder base) {
+ this.base = base;
+ }
+
+ @Override
+ public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
+ return base.transact(code, data, reply, flags);
+ }
+
+ @Nullable
+ @Override
+ public IInterface queryLocalInterface(@NonNull String descriptor) {
+ return base.queryLocalInterface(descriptor);
+ }
+
+ @Nullable
+ @Override
+ public String getInterfaceDescriptor() throws RemoteException {
+ return base.getInterfaceDescriptor();
+ }
+
+ @Override
+ public boolean pingBinder() {
+ return base.pingBinder();
+ }
+
+ @Override
+ public boolean isBinderAlive() {
+ return base.isBinderAlive();
+ }
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException {
+ base.dump(fd, args);
+ }
+
+ @Override
+ public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException {
+ base.dumpAsync(fd, args);
+ }
+
+ @Override
+ public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException {
+ base.linkToDeath(recipient, flags);
+ }
+
+ @Override
+ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
+ return base.unlinkToDeath(recipient, flags);
+ }
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/ClassicPlayIntegrityServiceWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/ClassicPlayIntegrityServiceWrapper.java
new file mode 100644
index 000000000..3bd88d2a6
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/ClassicPlayIntegrityServiceWrapper.java
@@ -0,0 +1,41 @@
+package app.grapheneos.gmscompat.lib.playintegrity;
+
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.os.FakeBackgroundHandler;
+import com.google.android.play.core.integrity.protocol.IIntegrityService;
+import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback;
+
+class ClassicPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper {
+
+ ClassicPlayIntegrityServiceWrapper(IBinder base) {
+ super(base);
+ requestIntegrityTokenTxnCode = 2; // IIntegrityService.Stub.TRANSACTION_requestIntegrityToken
+ }
+
+ static class TokenRequestStub extends IIntegrityService.Stub {
+ public void requestIntegrityToken(Bundle request, IIntegrityServiceCallback callback) {
+ Runnable r = () -> {
+ var result = new Bundle();
+ // https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/IntegrityErrorCode.html#API_NOT_AVAILABLE
+ final int API_NOT_AVAILABLE = -1;
+ result.putInt("error", API_NOT_AVAILABLE);
+ try {
+ callback.onResult(result);
+ } catch (RemoteException e) {
+ Log.e("IIntegrityService.Stub", "", e);
+ }
+ };
+ FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay());
+ }
+ };
+
+ @Override
+ protected Binder createTokenRequestStub() {
+ return new TokenRequestStub();
+ }
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityServiceWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityServiceWrapper.java
new file mode 100644
index 000000000..0418b4fe7
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityServiceWrapper.java
@@ -0,0 +1,48 @@
+package app.grapheneos.gmscompat.lib.playintegrity;
+
+import android.os.Binder;
+import android.os.BinderWrapper;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+abstract class PlayIntegrityServiceWrapper extends BinderWrapper {
+ final String TAG;
+ protected int requestIntegrityTokenTxnCode;
+
+ public PlayIntegrityServiceWrapper(IBinder base) {
+ super(base);
+ TAG = getClass().getSimpleName();
+ }
+
+ protected abstract Binder createTokenRequestStub();
+
+ @Override
+ public boolean transact(int code, Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
+ if (code == requestIntegrityTokenTxnCode) {
+ if (maybeStubOutIntegrityTokenRequest(code, data, reply, flags)) {
+ return true;
+ }
+ }
+ return super.transact(code, data, reply, flags);
+ }
+
+ private boolean maybeStubOutIntegrityTokenRequest(int code, Parcel data, @Nullable Parcel reply, int flags) {
+ Log.d(TAG, "integrity token request detected");
+
+ try {
+ createTokenRequestStub().transact(code, data, reply, flags);
+ } catch (RemoteException e) {
+ // this is a local call
+ throw new IllegalStateException(e);
+ }
+ return true;
+ }
+
+ protected static long getTokenRequestResultDelay() {
+ return 500L;
+ }
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityUtils.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityUtils.java
new file mode 100644
index 000000000..6ff4720cc
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityUtils.java
@@ -0,0 +1,35 @@
+package app.grapheneos.gmscompat.lib.playintegrity;
+
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.ext.PackageId;
+import android.os.IBinder;
+import androidx.annotation.Nullable;
+import app.grapheneos.gmscompat.lib.util.ServiceConnectionWrapper;
+import java.util.function.UnaryOperator;
+
+public class PlayIntegrityUtils {
+
+ public static @Nullable ServiceConnection maybeReplaceServiceConnection(Intent service, ServiceConnection orig) {
+ if (PackageId.PLAY_STORE_NAME.equals(service.getPackage())) {
+ UnaryOperator binderOverride = null;
+
+ final String CLASSIC_SERVICE =
+ "com.google.android.play.core.integrityservice.BIND_INTEGRITY_SERVICE";
+ final String STANDARD_SERVICE =
+ "com.google.android.play.core.expressintegrityservice.BIND_EXPRESS_INTEGRITY_SERVICE";
+
+ String action = service.getAction();
+ if (STANDARD_SERVICE.equals(action)) {
+ binderOverride = StandardPlayIntegrityServiceWrapper::new;
+ } else if (CLASSIC_SERVICE.equals(action)) {
+ binderOverride = ClassicPlayIntegrityServiceWrapper::new;
+ }
+
+ if (binderOverride != null) {
+ return new ServiceConnectionWrapper(orig, binderOverride);
+ }
+ }
+ return null;
+ }
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/StandardPlayIntegrityServiceWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/StandardPlayIntegrityServiceWrapper.java
new file mode 100644
index 000000000..c1c4937f0
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/StandardPlayIntegrityServiceWrapper.java
@@ -0,0 +1,42 @@
+package app.grapheneos.gmscompat.lib.playintegrity;
+
+import android.annotation.SuppressLint;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import com.android.internal.os.FakeBackgroundHandler;
+import com.google.android.play.core.integrity.protocol.IExpressIntegrityService;
+import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback;
+
+@SuppressLint("LongLogTag")
+class StandardPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper {
+
+ StandardPlayIntegrityServiceWrapper(IBinder base) {
+ super(base);
+ requestIntegrityTokenTxnCode = 3; // IExpressIntegrityService.Stub.TRANSACTION_requestIntegrityToken
+ }
+
+ static class TokenRequestStub extends IExpressIntegrityService.Stub {
+ public void requestIntegrityToken(Bundle request, IExpressIntegrityServiceCallback callback) {
+ Runnable r = () -> {
+ var result = new Bundle();
+ // https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/StandardIntegrityErrorCode.html#API_NOT_AVAILABLE
+ final int API_NOT_AVAILABLE = -1;
+ result.putInt("error", API_NOT_AVAILABLE);
+ try {
+ callback.onRequestExpressIntegrityTokenResult(result);
+ } catch (RemoteException e) {
+ Log.e("IExpressIntegrityService.Stub", "", e);
+ }
+ };
+ FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay());
+ }
+ };
+
+ @Override
+ protected Binder createTokenRequestStub() {
+ return new TokenRequestStub();
+ }
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/util/ServiceConnectionWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/util/ServiceConnectionWrapper.java
new file mode 100644
index 000000000..9edfc39f8
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/util/ServiceConnectionWrapper.java
@@ -0,0 +1,49 @@
+package app.grapheneos.gmscompat.lib.util;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.Build;
+import android.os.IBinder;
+
+import java.util.function.UnaryOperator;
+
+public class ServiceConnectionWrapper implements ServiceConnection {
+ private final ServiceConnection base;
+ private final UnaryOperator binderOverride;
+
+ public ServiceConnectionWrapper(ServiceConnection base, UnaryOperator binderOverride) {
+ this.base = base;
+ this.binderOverride = binderOverride;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ IBinder override = binderOverride.apply(service);
+ if (override != null) {
+ service = override;
+ }
+ }
+
+ base.onServiceConnected(name, service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ base.onServiceDisconnected(name);
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ base.onBindingDied(name);
+ }
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ base.onNullBinding(name);
+ }
+ }
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/playintegrity/DisablePlayIntegrityPatch.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/playintegrity/DisablePlayIntegrityPatch.java
new file mode 100644
index 000000000..a27e56be9
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/playintegrity/DisablePlayIntegrityPatch.java
@@ -0,0 +1,17 @@
+package app.revanced.extension.playintegrity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import app.grapheneos.gmscompat.lib.playintegrity.PlayIntegrityUtils;
+
+public class DisablePlayIntegrityPatch {
+ public static boolean bindService(Context context, Intent service, ServiceConnection conn, int flags) {
+ ServiceConnection override = PlayIntegrityUtils.maybeReplaceServiceConnection(service, conn);
+ if (override != null) {
+ conn = override;
+ }
+
+ return context.bindService(service, conn, flags);
+ }
+}
diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/com/android/internal/os/FakeBackgroundHandler.java b/extensions/all/misc/disable-play-integrity/src/main/java/com/android/internal/os/FakeBackgroundHandler.java
new file mode 100644
index 000000000..6b4cb92b4
--- /dev/null
+++ b/extensions/all/misc/disable-play-integrity/src/main/java/com/android/internal/os/FakeBackgroundHandler.java
@@ -0,0 +1,11 @@
+package com.android.internal.os;
+
+import android.os.Handler;
+import android.os.Looper;
+
+public class FakeBackgroundHandler {
+
+ public static Handler getHandler() {
+ return new Handler(Looper.getMainLooper());
+ }
+}
diff --git a/gradle.properties b/gradle.properties
index 00a1ebc1d..816fb3e47 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
-version = 5.48.0-dev.4
+version = 5.48.0-dev.9
diff --git a/patches/api/patches.api b/patches/api/patches.api
index 3c0aacd25..f4c66bc75 100644
--- a/patches/api/patches.api
+++ b/patches/api/patches.api
@@ -100,6 +100,10 @@ public final class app/revanced/patches/all/misc/packagename/ChangePackageNamePa
public static final fun setPackageNameOption (Lapp/revanced/patcher/patch/Option;)V
}
+public final class app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrityKt {
+ public static final fun getDisablePlayIntegrityPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
+}
+
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 addResources (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;)Z
@@ -340,6 +344,10 @@ public final class app/revanced/patches/letterboxd/ads/HideAdsPatchKt {
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
+public final class app/revanced/patches/letterboxd/unlock/unlockAppIcons/UnlockAppIconsPatchKt {
+ public static final fun getUnlockAppIconsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
+}
+
public final class app/revanced/patches/lightroom/misc/login/DisableMandatoryLoginPatchKt {
public static final fun getDisableMandatoryLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -920,6 +928,10 @@ public final class app/revanced/patches/shared/misc/pairip/license/DisableLicens
public static final fun getDisableLicenseCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
+public final class app/revanced/patches/shared/misc/privacy/DisableSentryTelemetryKt {
+ public static final fun getDisableSentryTelemetryPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
+}
+
public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt {
public static final fun overrideThemeColors (Ljava/lang/String;Ljava/lang/String;)V
public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch;
@@ -1149,14 +1161,22 @@ public final class app/revanced/patches/stocard/layout/HideStoryBubblesPatchKt {
public static final fun getHideStoryBubblesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
+public final class app/revanced/patches/strava/mediaupload/OverwriteMediaUploadParametersPatchKt {
+ public static final fun getOverwriteMediaUploadParametersPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
+}
+
public final class app/revanced/patches/strava/password/EnablePasswordLoginPatchKt {
public static final fun getEnablePasswordLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
-public final class app/revanced/patches/strava/snowplow/BlockSnowplowTrackingPatchKt {
+public final class app/revanced/patches/strava/privacy/BlockSnowplowTrackingPatchKt {
public static final fun getBlockSnowplowTrackingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
+public final class app/revanced/patches/strava/quickedit/DisableQuickEditPatchKt {
+ public static final fun getDisableQuickEditPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
+}
+
public final class app/revanced/patches/strava/subscription/UnlockSubscriptionPatchKt {
public static final fun getUnlockSubscriptionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrity.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrity.kt
new file mode 100644
index 000000000..c7a41cb74
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrity.kt
@@ -0,0 +1,54 @@
+package app.revanced.patches.all.misc.playintegrity
+
+import app.revanced.patcher.extensions.replaceInstruction
+import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
+import app.revanced.util.getReference
+import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
+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
+
+private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/playintegrity/DisablePlayIntegrityPatch;"
+
+private val CONTEXT_BIND_SERVICE_METHOD_REFERENCE = ImmutableMethodReference(
+ "Landroid/content/Context;",
+ "bindService",
+ listOf("Landroid/content/Intent;", "Landroid/content/ServiceConnection;", "I"),
+ "Z"
+)
+
+
+@Suppress("unused")
+val disablePlayIntegrityPatch = bytecodePatch(
+ name = "Disable Play Integrity",
+ description = "Prevents apps from using Play Integrity by pretending it is not available.",
+ use = false,
+) {
+ extendWith("extensions/all/misc/disable-play-integrity.rve")
+
+ dependsOn(
+ transformInstructionsPatch(
+ filterMap = filterMap@{ classDef, method, instruction, instructionIndex ->
+ val reference = instruction
+ .getReference()
+ ?.takeIf {
+ MethodUtil.methodSignaturesMatch(CONTEXT_BIND_SERVICE_METHOD_REFERENCE, it)
+ }
+ ?: return@filterMap null
+
+ Triple(instruction as Instruction35c, instructionIndex, reference.parameterTypes)
+ },
+ transform = { method, entry ->
+ val (instruction, index, parameterTypes) = entry
+ val parameterString = parameterTypes.joinToString(separator = "")
+ val registerString = "v${instruction.registerC}, v${instruction.registerD}, v${instruction.registerE}, v${instruction.registerF}"
+
+ method.replaceInstruction(
+ index,
+ "invoke-static { $registerString }, $EXTENSION_CLASS_DESCRIPTOR->bindService(Landroid/content/Context;$parameterString)Z"
+ )
+ }
+ )
+ )
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/disneyplus/SkipAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/disneyplus/SkipAdsPatch.kt
index 4735d4265..c5e58a48f 100644
--- a/patches/src/main/kotlin/app/revanced/patches/disneyplus/SkipAdsPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/disneyplus/SkipAdsPatch.kt
@@ -8,11 +8,7 @@ val skipAdsPatch = bytecodePatch(
name = "Skip ads",
description = "Automatically skips ads.",
) {
- compatibleWith(
- "com.disney.disneyplus",
- "in.startv.hotstar",
- "in.startv.hotstaronly",
- )
+ compatibleWith("com.disney.disneyplus")
apply {
arrayOf(insertionGetPointsFingerprint, insertionGetRangesFingerprint).forEach {
diff --git a/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/Fingerprints.kt
new file mode 100644
index 000000000..1b549cd57
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/Fingerprints.kt
@@ -0,0 +1,9 @@
+package app.revanced.patches.letterboxd.unlock.unlockAppIcons
+
+import app.revanced.patcher.fingerprint
+
+internal val getCanChangeAppIconFingerprint = fingerprint {
+ custom { method, classDef ->
+ method.name == "getCanChangeAppIcon" && classDef.type.endsWith("SettingsAppIconFragment;")
+ }
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/UnlockAppIconsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/UnlockAppIconsPatch.kt
new file mode 100644
index 000000000..ca8d7ac55
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/UnlockAppIconsPatch.kt
@@ -0,0 +1,15 @@
+package app.revanced.patches.letterboxd.unlock.unlockAppIcons
+
+import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.util.returnEarly
+
+@Suppress("unused")
+val unlockAppIconsPatch = bytecodePatch(
+ name = "Unlock app icons",
+) {
+ compatibleWith("com.letterboxd.letterboxd")
+
+ apply {
+ getCanChangeAppIconFingerprint.method.returnEarly(true)
+ }
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt
index 076a62431..f62d738e1 100644
--- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt
@@ -1,10 +1,10 @@
package app.revanced.patches.shared.misc.extension
+import app.revanced.patcher.BytecodePatchContextClassDefMatching.firstMutableClassDef
import app.revanced.patcher.Fingerprint
import app.revanced.patcher.FingerprintBuilder
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.fingerprint
-import app.revanced.patcher.firstClassDefMutable
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@@ -41,7 +41,7 @@ fun sharedExtensionPatch(
apply {
// Verify the extension class exists.
- firstClassDefMutable(EXTENSION_CLASS_DESCRIPTOR)
+ firstMutableClassDef(EXTENSION_CLASS_DESCRIPTOR)
}
afterDependents {
@@ -86,10 +86,10 @@ fun sharedExtensionPatch(
class ExtensionHook internal constructor(
internal val fingerprint: Fingerprint,
- private val insertIndexResolver: BytecodePatchContext.(Method) -> Int,
- private val contextRegisterResolver: BytecodePatchContext.(Method) -> String,
+ private val insertIndexResolver: context(BytecodePatchContext) (Method) -> Int,
+ private val contextRegisterResolver: context(BytecodePatchContext) (Method) -> String,
) {
- context(BytecodePatchContext)
+ context(_: BytecodePatchContext)
operator fun invoke(extensionClassDescriptor: String) {
val insertIndex = insertIndexResolver(fingerprint.method)
val contextRegister = contextRegisterResolver(fingerprint.method)
@@ -103,16 +103,17 @@ class ExtensionHook internal constructor(
}
fun extensionHook(
- insertIndexResolver: BytecodePatchContext.(Method) -> Int = { 0 },
- contextRegisterResolver: BytecodePatchContext.(Method) -> String = { "p0" },
+ insertIndexResolver: context(BytecodePatchContext) (Method) -> Int = { 0 },
+ contextRegisterResolver: context(BytecodePatchContext) (Method) -> String = { "p0" },
fingerprint: Fingerprint,
) = ExtensionHook(fingerprint, insertIndexResolver, contextRegisterResolver)
fun extensionHook(
- insertIndexResolver: BytecodePatchContext.(Method) -> Int = { 0 },
- contextRegisterResolver: BytecodePatchContext.(Method) -> String = { "p0" },
+ insertIndexResolver: context(BytecodePatchContext) (Method) -> Int = { 0 },
+ contextRegisterResolver: context(BytecodePatchContext) (Method) -> String = { "p0" },
fingerprintBuilderBlock: FingerprintBuilder.() -> Unit,
) = {
+ ->
ExtensionHook(fingerprint(block = fingerprintBuilderBlock), insertIndexResolver, contextRegisterResolver)
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/privacy/DisableSentryTelemetry.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/privacy/DisableSentryTelemetry.kt
new file mode 100644
index 000000000..8dbc7504c
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/privacy/DisableSentryTelemetry.kt
@@ -0,0 +1,36 @@
+package app.revanced.patches.shared.misc.privacy
+
+import app.revanced.patcher.patch.resourcePatch
+import app.revanced.util.asSequence
+import app.revanced.util.getNode
+import org.w3c.dom.Element
+
+@Suppress("unused")
+val disableSentryTelemetryPatch = resourcePatch(
+ name = "Disable Sentry telemetry",
+ description = "Disables Sentry telemetry. See https://sentry.io/for/android/ for more information.",
+ use = false,
+) {
+ apply {
+ fun Element.replaceOrCreate(tagName: String, attributeName: String, attributeValue: String) {
+ val childElements = getElementsByTagName(tagName).asSequence().filterIsInstance()
+ val targetChild = childElements.find { childElement ->
+ childElement.getAttribute("android:name") == attributeName
+ }
+ if (targetChild != null) {
+ targetChild.setAttribute("android:value", attributeValue)
+ } else {
+ appendChild(ownerDocument.createElement(tagName).apply {
+ setAttribute("android:name", attributeName)
+ setAttribute("android:value", attributeValue)
+ })
+ }
+ }
+
+ document("AndroidManifest.xml").use { document ->
+ val application = document.getNode("application") as Element
+ application.replaceOrCreate("meta-data", "io.sentry.enabled", "false")
+ application.replaceOrCreate("meta-data", "io.sentry.dsn", "")
+ }
+ }
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/Fingerprints.kt
new file mode 100644
index 000000000..7901d6cc5
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/Fingerprints.kt
@@ -0,0 +1,21 @@
+package app.revanced.patches.strava.mediaupload
+
+import app.revanced.patcher.fingerprint
+
+internal val getCompressionQualityFingerprint = fingerprint {
+ custom { method, _ ->
+ method.name == "getCompressionQuality"
+ }
+}
+
+internal val getMaxDurationFingerprint = fingerprint {
+ custom { method, _ ->
+ method.name == "getMaxDuration"
+ }
+}
+
+internal val getMaxSizeFingerprint = fingerprint {
+ custom { method, _ ->
+ method.name == "getMaxSize"
+ }
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/OverwriteMediaUploadParametersPatch.kt b/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/OverwriteMediaUploadParametersPatch.kt
new file mode 100644
index 000000000..decc0bcab
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/OverwriteMediaUploadParametersPatch.kt
@@ -0,0 +1,46 @@
+package app.revanced.patches.strava.mediaupload
+
+import app.revanced.patcher.BytecodePatchContextClassDefMatching.firstClassDef
+import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.patcher.patch.intOption
+import app.revanced.patcher.patch.longOption
+import app.revanced.util.returnEarly
+
+@Suppress("unused")
+val overwriteMediaUploadParametersPatch = bytecodePatch(
+ name = "Overwrite media upload parameters",
+ description = "Overwrites the compression, resize and trim media (images and videos) parameters returned by Strava's server before upload.",
+) {
+ compatibleWith("com.strava")
+
+ val compressionQuality by intOption(
+ name = "Compression quality (percent)",
+ description = "This is used as the JPEG quality setting (≤ 100).",
+ ) { it == null || it in 1..100 }
+
+ val maxDuration by longOption(
+ name = "maxDuration",
+ description = "The maximum length (≤ ${60 * 60}) of a video before it gets trimmed.",
+ ) { it == null || it in 1..60 * 60 }
+
+ val maxSize by intOption(
+ name = "Max size (pixels)",
+ description = "The image gets resized so that the smaller dimension (width/height) does not exceed this value (≤ 10000).",
+ ) { it == null || it in 1..10000 }
+
+ apply {
+ val mediaUploadParametersClass = firstClassDef { type.endsWith("/MediaUploadParameters;") }
+
+ compressionQuality?.let { compressionQuality ->
+ getCompressionQualityFingerprint.match(mediaUploadParametersClass).method.returnEarly(compressionQuality / 100f)
+ }
+
+ maxDuration?.let { maxDuration ->
+ getMaxDurationFingerprint.match(mediaUploadParametersClass).method.returnEarly(maxDuration)
+ }
+
+ maxSize?.let {
+ getMaxSizeFingerprint.match(mediaUploadParametersClass).method.returnEarly(it)
+ }
+ }
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/password/EnablePasswordLoginPatch.kt b/patches/src/main/kotlin/app/revanced/patches/strava/password/EnablePasswordLoginPatch.kt
index 9b6a04ac1..e01a6816b 100644
--- a/patches/src/main/kotlin/app/revanced/patches/strava/password/EnablePasswordLoginPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/strava/password/EnablePasswordLoginPatch.kt
@@ -1,8 +1,8 @@
package app.revanced.patches.strava.password
import app.revanced.patcher.Fingerprint
-import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.util.returnEarly
@Suppress("unused")
val enablePasswordLoginPatch = bytecodePatch(
@@ -12,10 +12,9 @@ val enablePasswordLoginPatch = bytecodePatch(
compatibleWith("com.strava")
apply {
- fun Fingerprint.loadTrueInsteadOfField() =
- method.replaceInstruction(patternMatch!!.startIndex, "const/4 v0, 0x1")
+ fun Fingerprint.returnTrue() = method.returnEarly(true)
- logInGetUsePasswordFingerprint.loadTrueInsteadOfField()
- emailChangeGetUsePasswordFingerprint.loadTrueInsteadOfField()
+ logInGetUsePasswordFingerprint.returnTrue()
+ emailChangeGetUsePasswordFingerprint.returnTrue()
}
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/password/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/strava/password/Fingerprints.kt
index f5ce86f18..94c88490a 100644
--- a/patches/src/main/kotlin/app/revanced/patches/strava/password/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/strava/password/Fingerprints.kt
@@ -1,17 +1,14 @@
package app.revanced.patches.strava.password
import app.revanced.patcher.fingerprint
-import com.android.tools.smali.dexlib2.Opcode
internal val logInGetUsePasswordFingerprint = fingerprint {
- opcodes(Opcode.IGET_BOOLEAN)
custom { method, classDef ->
method.name == "getUsePassword" && classDef.endsWith("/RequestOtpLogInNetworkResponse;")
}
}
internal val emailChangeGetUsePasswordFingerprint = fingerprint {
- opcodes(Opcode.IGET_BOOLEAN)
custom { method, classDef ->
method.name == "getUsePassword" && classDef.endsWith("/RequestEmailChangeWithOtpOrPasswordResponse;")
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/privacy/BlockSnowplowTrackingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/strava/privacy/BlockSnowplowTrackingPatch.kt
index 96ad093c3..d917511c0 100644
--- a/patches/src/main/kotlin/app/revanced/patches/strava/privacy/BlockSnowplowTrackingPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/strava/privacy/BlockSnowplowTrackingPatch.kt
@@ -11,7 +11,7 @@ val blockSnowplowTrackingPatch = bytecodePatch(
compatibleWith("com.strava")
apply {
- // Keep events list empty, otherwise sent to https://c.strava.com/com.snowplowanalytics.snowplow/tp2.
+ // Keep events list empty, otherwise sent to https://c.strava.com/com.snowplowanalytics.snowplow/tp2.
insertEventFingerprint.method.returnEarly()
}
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/DisableQuickEditPatch.kt b/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/DisableQuickEditPatch.kt
new file mode 100644
index 000000000..a7d713e73
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/DisableQuickEditPatch.kt
@@ -0,0 +1,16 @@
+package app.revanced.patches.strava.quickedit
+
+import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.util.returnEarly
+
+@Suppress("unused")
+val disableQuickEditPatch = bytecodePatch(
+ name = "Disable Quick Edit",
+ description = "Prevents the Quick Edit prompt from popping up.",
+) {
+ compatibleWith("com.strava")
+
+ apply {
+ getHasAccessToQuickEditFingerprint.method.returnEarly()
+ }
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/Fingerprints.kt
new file mode 100644
index 000000000..acd48542b
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/Fingerprints.kt
@@ -0,0 +1,10 @@
+package app.revanced.patches.strava.quickedit
+
+import app.revanced.patcher.fingerprint
+
+internal val getHasAccessToQuickEditFingerprint = fingerprint {
+ returns("Z")
+ custom { method, _ ->
+ method.name == "getHasAccessToQuickEdit"
+ }
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/subscription/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/strava/subscription/Fingerprints.kt
index 0458f45d3..45583ce4e 100644
--- a/patches/src/main/kotlin/app/revanced/patches/strava/subscription/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/strava/subscription/Fingerprints.kt
@@ -1,11 +1,9 @@
package app.revanced.patches.strava.subscription
import app.revanced.patcher.fingerprint
-import com.android.tools.smali.dexlib2.Opcode
internal val getSubscribedFingerprint = fingerprint {
- opcodes(Opcode.IGET_BOOLEAN)
custom { method, classDef ->
- classDef.endsWith("/SubscriptionDetailResponse;") && method.name == "getSubscribed"
+ method.name == "getSubscribed" && classDef.endsWith("/SubscriptionDetailResponse;")
}
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/subscription/UnlockSubscriptionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/strava/subscription/UnlockSubscriptionPatch.kt
index 472992a20..467de6c26 100644
--- a/patches/src/main/kotlin/app/revanced/patches/strava/subscription/UnlockSubscriptionPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/strava/subscription/UnlockSubscriptionPatch.kt
@@ -1,7 +1,7 @@
package app.revanced.patches.strava.subscription
-import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.util.returnEarly
@Suppress("unused")
val unlockSubscriptionPatch = bytecodePatch(
@@ -11,9 +11,6 @@ val unlockSubscriptionPatch = bytecodePatch(
compatibleWith("com.strava")
apply {
- getSubscribedFingerprint.method.replaceInstruction(
- getSubscribedFingerprint.instructionMatches.first().index,
- "const/4 v0, 0x1",
- )
+ getSubscribedFingerprint.method.returnEarly(true)
}
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt
index 4e6384eb3..7c7f23507 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt
@@ -1,14 +1,13 @@
package app.revanced.patches.youtube.video.information
+import app.revanced.patcher.BytecodePatchContextClassDefMatching.firstMutableClassDef
import com.android.tools.smali.dexlib2.mutable.MutableClassDef
import com.android.tools.smali.dexlib2.mutable.MutableMethod
import com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.getInstruction
-import app.revanced.patcher.firstClassDefMutable
import app.revanced.patcher.patch.bytecodePatch
-import app.revanced.patcher.util.toInstructions
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_20_or_greater
@@ -141,7 +140,7 @@ val videoInformationPatch = bytecodePatch(
addInstruction(
videoLengthMethodMatch.instructionMatches.last().index,
"invoke-static {v$videoLengthRegister, v$dummyRegisterForLong}, " +
- "$EXTENSION_CLASS_DESCRIPTOR->setVideoLength(J)V",
+ "$EXTENSION_CLASS_DESCRIPTOR->setVideoLength(J)V",
)
}
}
@@ -164,7 +163,7 @@ val videoInformationPatch = bytecodePatch(
addPlayerResponseMethodHook(
Hook.ProtoBufferParameterBeforeVideoId(
"$EXTENSION_CLASS_DESCRIPTOR->" +
- "newPlayerResponseSignature(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
+ "newPlayerResponseSignature(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
),
)
@@ -199,7 +198,7 @@ val videoInformationPatch = bytecodePatch(
getInstruction(indexOfFirstInstructionOrThrow(Opcode.IF_EQZ) - 1).reference as FieldReference
setPlaybackSpeedMethod =
- firstClassDefMutable(setPlaybackSpeedMethodReference.definingClass)
+ firstMutableClassDef(setPlaybackSpeedMethodReference.definingClass)
.methods.first { it.name == setPlaybackSpeedMethodReference.name }
setPlaybackSpeedMethodIndex = 0
diff --git a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt
index bab55684c..35cffca6e 100644
--- a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt
+++ b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt
@@ -1,9 +1,9 @@
package app.revanced.util
+import app.revanced.patcher.BytecodePatchContextClassDefMatching.firstMutableClassDef
+import app.revanced.patcher.BytecodePatchContextClassDefMatching.firstMutableClassDefOrNull
import app.revanced.patcher.FingerprintBuilder
import app.revanced.patcher.extensions.*
-import app.revanced.patcher.firstClassDefMutable
-import app.revanced.patcher.firstClassDefMutableOrNull
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patches.shared.misc.mapping.ResourceType
@@ -15,13 +15,17 @@ import app.revanced.util.InstructionUtils.Companion.writeOpcodes
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.Opcode.*
+import com.android.tools.smali.dexlib2.analysis.reflection.util.ReflectionUtils
+import com.android.tools.smali.dexlib2.formatter.DexFormatter
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.*
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.iface.reference.StringReference
+import com.android.tools.smali.dexlib2.iface.value.*
import com.android.tools.smali.dexlib2.immutable.ImmutableField
+import com.android.tools.smali.dexlib2.immutable.value.*
import com.android.tools.smali.dexlib2.mutable.MutableClassDef
import com.android.tools.smali.dexlib2.mutable.MutableField
import com.android.tools.smali.dexlib2.mutable.MutableField.Companion.toMutable
@@ -227,10 +231,10 @@ private fun Method.findInstructionIndexFromToString(fieldName: String): Int {
*
* @param fieldName The name of the field to find. Partial matches are allowed.
*/
-context(BytecodePatchContext)
+context(context: BytecodePatchContext)
internal fun Method.findMethodFromToString(fieldName: String): MutableMethod {
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
- return navigate(this).to(methodUsageIndex).stop()
+ return context.navigate(this).to(methodUsageIndex).stop()
}
/**
@@ -538,7 +542,7 @@ fun BytecodePatchContext.traverseClassHierarchy(targetClass: MutableClassDef, ca
targetClass.superclass ?: return
- firstClassDefMutableOrNull(targetClass.superclass!!)?.let {
+ firstMutableClassDefOrNull(targetClass.superclass!!)?.let {
traverseClassHierarchy(it, callback)
}
}
@@ -827,216 +831,173 @@ fun BytecodePatchContext.forEachInstructionAsSequence(
}.forEach { it() }
}
-private const val RETURN_TYPE_MISMATCH = "Mismatch between override type and Method return type"
+private fun MutableMethod.checkReturnType(expectedTypes: Iterable>) {
+ val returnTypeJava = ReflectionUtils.dexToJavaName(returnType)
+ check(expectedTypes.any { returnTypeJava == it.name }) {
+ "Actual return type $returnTypeJava is not contained in expected types: $expectedTypes"
+ }
+}
/**
- * Overrides the first instruction of a method with a return-void instruction.
+ * Overrides the first instruction of a method with returning the default value for the type (or `void`).
* None of the method code will ever execute.
*
* @see returnLate
*/
fun MutableMethod.returnEarly() {
- check(returnType.first() == 'V') {
- RETURN_TYPE_MISMATCH
- }
- overrideReturnValue(false.toHexString(), false)
-}
-
-/**
- * Overrides the first instruction of a method with a constant `Boolean` return value.
- * None of the original method code will execute.
- *
- * For methods that return an object or any array type, calling this method with `false`
- * will force the method to return a `null` value.
- *
- * @see returnLate
- */
-fun MutableMethod.returnEarly(value: Boolean) {
- check(returnType.first() == 'Z') {
- RETURN_TYPE_MISMATCH
- }
- overrideReturnValue(value.toHexString(), false)
-}
-
-/**
- * Overrides the first instruction of a method with a constant `Byte` return value.
- * None of the original method code will execute.
- *
- * @see returnLate
- */
-fun MutableMethod.returnEarly(value: Byte) {
- check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), false)
-}
-
-/**
- * Overrides the first instruction of a method with a constant `Short` return value.
- * None of the original method code will execute.
- *
- * @see returnLate
- */
-fun MutableMethod.returnEarly(value: Short) {
- check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), false)
-}
-
-/**
- * Overrides the first instruction of a method with a constant `Char` return value.
- * None of the original method code will execute.
- *
- * @see returnLate
- */
-fun MutableMethod.returnEarly(value: Char) {
- check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.code.toString(), false)
-}
-
-/**
- * Overrides the first instruction of a method with a constant `Int` return value.
- * None of the original method code will execute.
- *
- * @see returnLate
- */
-fun MutableMethod.returnEarly(value: Int) {
- check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), false)
-}
-
-/**
- * Overrides the first instruction of a method with a constant `Long` return value.
- * None of the original method code will execute.
- *
- * @see returnLate
- */
-fun MutableMethod.returnEarly(value: Long) {
- check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), false)
-}
-
-/**
- * Overrides the first instruction of a method with a constant `Float` return value.
- * None of the original method code will execute.
- *
- * @see returnLate
- */
-fun MutableMethod.returnEarly(value: Float) {
- check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), false)
-}
-
-/**
- * Overrides the first instruction of a method with a constant `Double` return value.
- * None of the original method code will execute.
- *
- * @see returnLate
- */
-fun MutableMethod.returnEarly(value: Double) {
- check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), false)
-}
-
-/**
- * Overrides the first instruction of a method with a constant String return value.
- * None of the original method code will execute.
- *
- * Target method must have return type
- * Ljava/lang/String; or Ljava/lang/CharSequence;
- *
- * @see returnLate
- */
-fun MutableMethod.returnEarly(value: String) {
- check(returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;") {
- RETURN_TYPE_MISMATCH
+ val value = when (returnType) {
+ "V" -> null
+ "Z" -> ImmutableBooleanEncodedValue.FALSE_VALUE
+ "B" -> ImmutableByteEncodedValue(0)
+ "S" -> ImmutableShortEncodedValue(0)
+ "C" -> ImmutableCharEncodedValue(Char.MIN_VALUE)
+ "I" -> ImmutableIntEncodedValue(0)
+ "F" -> ImmutableFloatEncodedValue(0f)
+ "J" -> ImmutableLongEncodedValue(0)
+ "D" -> ImmutableDoubleEncodedValue(0.0)
+ else -> ImmutableNullEncodedValue.INSTANCE
}
overrideReturnValue(value, false)
}
-/**
- * Overrides the first instruction of a method with a constant `NULL` return value.
- * None of the original method code will execute.
- *
- * @param value Value must be `Null`.
- * @see returnLate
- */
-fun MutableMethod.returnEarly(value: Void?) {
- val returnType = returnType.first()
- check(returnType == 'L' || returnType != '[') {
- RETURN_TYPE_MISMATCH
- }
- overrideReturnValue(false.toHexString(), false)
+private fun MutableMethod.returnString(value: String, late: Boolean) {
+ checkReturnType(String::class.java.allAssignableTypes())
+ overrideReturnValue(ImmutableStringEncodedValue(value), late)
}
/**
- * Overrides all return statements with a constant `Boolean` value.
- * All method code is executed the same as unpatched.
+ * Overrides the first instruction of a method with a constant `String` return value.
+ * None of the method code will ever execute.
*
- * For methods that return an object or any array type, calling this method with `false`
- * will force the method to return a `null` value.
+ * @see returnLate
+ */
+fun MutableMethod.returnEarly(value: String) = returnString(value, false)
+
+/**
+ * Overrides all return statements with a constant `String` value.
+ * All method code is executed the same as unpatched.
*
* @see returnEarly
*/
-fun MutableMethod.returnLate(value: Boolean) {
- check(this.returnType.first() == 'Z') {
- RETURN_TYPE_MISMATCH
- }
+fun MutableMethod.returnLate(value: String) = returnString(value, true)
- overrideReturnValue(value.toHexString(), true)
+private fun MutableMethod.returnByte(value: Byte, late: Boolean) {
+ checkReturnType(Byte::class.javaObjectType.allAssignableTypes() + Byte::class.javaPrimitiveType!!)
+ overrideReturnValue(ImmutableByteEncodedValue(value), late)
}
+/**
+ * Overrides the first instruction of a method with a constant `Byte` return value.
+ * None of the method code will ever execute.
+ *
+ * @see returnLate
+ */
+fun MutableMethod.returnEarly(value: Byte) = returnByte(value, false)
+
/**
* Overrides all return statements with a constant `Byte` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
-fun MutableMethod.returnLate(value: Byte) {
- check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), true)
+fun MutableMethod.returnLate(value: Byte) = returnByte(value, true)
+
+private fun MutableMethod.returnBoolean(value: Boolean, late: Boolean) {
+ checkReturnType(Boolean::class.javaObjectType.allAssignableTypes() + Boolean::class.javaPrimitiveType!!)
+ overrideReturnValue(ImmutableBooleanEncodedValue.forBoolean(value), late)
}
+/**
+ * Overrides the first instruction of a method with a constant `Boolean` return value.
+ * None of the method code will ever execute.
+ *
+ * @see returnLate
+ */
+fun MutableMethod.returnEarly(value: Boolean) = returnBoolean(value, false)
+
+/**
+ * Overrides all return statements with a constant `Boolean` value.
+ * All method code is executed the same as unpatched.
+ *
+ * @see returnEarly
+ */
+fun MutableMethod.returnLate(value: Boolean) = returnBoolean(value, true)
+
+private fun MutableMethod.returnShort(value: Short, late: Boolean) {
+ checkReturnType(Short::class.javaObjectType.allAssignableTypes() + Short::class.javaPrimitiveType!!)
+ overrideReturnValue(ImmutableShortEncodedValue(value), late)
+}
+
+/**
+ * Overrides the first instruction of a method with a constant `Short` return value.
+ * None of the method code will ever execute.
+ *
+ * @see returnLate
+ */
+fun MutableMethod.returnEarly(value: Short) = returnShort(value, false)
+
/**
* Overrides all return statements with a constant `Short` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
-fun MutableMethod.returnLate(value: Short) {
- check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), true)
+fun MutableMethod.returnLate(value: Short) = returnShort(value, true)
+
+private fun MutableMethod.returnChar(value: Char, late: Boolean) {
+ checkReturnType(Char::class.javaObjectType.allAssignableTypes() + Char::class.javaPrimitiveType!!)
+ overrideReturnValue(ImmutableCharEncodedValue(value), late)
}
+/**
+ * Overrides the first instruction of a method with a constant `Char` return value.
+ * None of the method code will ever execute.
+ *
+ * @see returnLate
+ */
+fun MutableMethod.returnEarly(value: Char) = returnChar(value, false)
+
/**
* Overrides all return statements with a constant `Char` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
-fun MutableMethod.returnLate(value: Char) {
- check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.code.toString(), true)
+fun MutableMethod.returnLate(value: Char) = returnChar(value, true)
+
+private fun MutableMethod.returnInt(value: Int, late: Boolean) {
+ checkReturnType(Int::class.javaObjectType.allAssignableTypes() + Int::class.javaPrimitiveType!!)
+ overrideReturnValue(ImmutableIntEncodedValue(value), late)
}
+/**
+ * Overrides the first instruction of a method with a constant `Int` return value.
+ * None of the method code will ever execute.
+ *
+ * @see returnLate
+ */
+fun MutableMethod.returnEarly(value: Int) = returnInt(value, false)
+
/**
* Overrides all return statements with a constant `Int` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
-fun MutableMethod.returnLate(value: Int) {
- check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), true)
+fun MutableMethod.returnLate(value: Int) = returnInt(value, true)
+
+private fun MutableMethod.returnFloat(value: Float, late: Boolean) {
+ checkReturnType(Float::class.javaObjectType.allAssignableTypes() + Float::class.javaPrimitiveType!!)
+ overrideReturnValue(ImmutableFloatEncodedValue(value), late)
}
/**
- * Overrides all return statements with a constant `Long` value.
- * All method code is executed the same as unpatched.
+ * Overrides the first instruction of a method with a constant `Float` return value.
+ * None of the method code will ever execute.
*
- * @see returnEarly
+ * @see returnLate
*/
-fun MutableMethod.returnLate(value: Long) {
- check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), true)
-}
+fun MutableMethod.returnEarly(value: Float) = returnFloat(value, false)
/**
* Overrides all return statements with a constant `Float` value.
@@ -1044,102 +1005,206 @@ fun MutableMethod.returnLate(value: Long) {
*
* @see returnEarly
*/
-fun MutableMethod.returnLate(value: Float) {
- check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), true)
+fun MutableMethod.returnLate(value: Float) = returnFloat(value, true)
+
+private fun MutableMethod.returnLong(value: Long, late: Boolean) {
+ checkReturnType(Long::class.javaObjectType.allAssignableTypes() + Long::class.javaPrimitiveType!!)
+ overrideReturnValue(ImmutableLongEncodedValue(value), late)
}
+/**
+ * Overrides the first instruction of a method with a constant `Long` return value.
+ * None of the method code will ever execute.
+ *
+ * @see returnLate
+ */
+fun MutableMethod.returnEarly(value: Long) = returnLong(value, false)
+
+/**
+ * Overrides all return statements with a constant `Long` value.
+ * All method code is executed the same as unpatched.
+ *
+ * @see returnEarly
+ */
+fun MutableMethod.returnLate(value: Long) = returnLong(value, true)
+
+private fun MutableMethod.returnDouble(value: Double, late: Boolean) {
+ checkReturnType(Double::class.javaObjectType.allAssignableTypes() + Double::class.javaPrimitiveType!!)
+ overrideReturnValue(ImmutableDoubleEncodedValue(value), late)
+}
+
+/**
+ * Overrides the first instruction of a method with a constant `Double` return value.
+ * None of the method code will ever execute.
+ *
+ * @see returnLate
+ */
+fun MutableMethod.returnEarly(value: Double) = returnDouble(value, false)
+
/**
* Overrides all return statements with a constant `Double` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
-fun MutableMethod.returnLate(value: Double) {
- check(returnType.first() == 'D') { RETURN_TYPE_MISMATCH }
- overrideReturnValue(value.toString(), true)
-}
+fun MutableMethod.returnLate(value: Double) = returnDouble(value, true)
-/**
- * Overrides all return statements with a constant String value.
- * All method code is executed the same as unpatched.
- *
- * Target method must have return type
- * Ljava/lang/String; or Ljava/lang/CharSequence;
- *
- * @see returnEarly
- */
-fun MutableMethod.returnLate(value: String) {
- check(returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;") {
- RETURN_TYPE_MISMATCH
- }
- overrideReturnValue(value, true)
-}
-
-/**
- * Overrides all return statements with a constant `Null` value.
- * All method code is executed the same as unpatched.
- *
- * @param value Value must be `Null`.
- * @see returnEarly
- */
-fun MutableMethod.returnLate(value: Void?) {
- val returnType = returnType.first()
- check(returnType == 'L' || returnType == '[') {
- RETURN_TYPE_MISMATCH
- }
-
- overrideReturnValue(false.toHexString(), true)
-}
-
-private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean) {
- val instructions = if (returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;") {
- """
- const-string v0, "$value"
- return-object v0
- """
- } else when (returnType.first()) {
- // If return type is an object, always return null.
- 'L', '[' -> {
- """
+private fun MutableMethod.overrideReturnValue(value: EncodedValue?, returnLate: Boolean) {
+ val instructions = if (value == null) {
+ require(!returnLate) {
+ "Cannot return late for method of void type"
+ }
+ "return-void"
+ } else {
+ val encodedValue = DexFormatter.INSTANCE.getEncodedValue(value)
+ when (value) {
+ is NullEncodedValue -> {
+ """
const/4 v0, 0x0
return-object v0
- """
- }
+ """
+ }
- 'V' -> {
- "return-void"
- }
+ is StringEncodedValue -> {
+ """
+ const-string v0, $encodedValue
+ return-object v0
+ """
+ }
- 'B', 'Z' -> {
- """
- const/4 v0, $value
- return v0
- """
- }
+ is ByteEncodedValue -> {
+ if (returnType == "B") {
+ """
+ const/4 v0, $encodedValue
+ return v0
+ """
+ } else {
+ """
+ const/4 v0, $encodedValue
+ invoke-static { v0 }, Ljava/lang/Byte;->valueOf(B)Ljava/lang/Byte;
+ move-result-object v0
+ return-object v0
+ """
+ }
+ }
- 'S', 'C' -> {
- """
- const/16 v0, $value
- return v0
- """
- }
+ is BooleanEncodedValue -> {
+ val encodedValue = value.value.toHexString()
+ if (returnType == "Z") {
+ """
+ const/4 v0, $encodedValue
+ return v0
+ """
+ } else {
+ """
+ const/4 v0, $encodedValue
+ invoke-static { v0 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;
+ move-result-object v0
+ return-object v0
+ """
+ }
+ }
- 'I', 'F' -> {
- """
- const v0, $value
- return v0
- """
- }
+ is ShortEncodedValue -> {
+ if (returnType == "S") {
+ """
+ const/16 v0, $encodedValue
+ return v0
+ """
+ } else {
+ """
+ const/16 v0, $encodedValue
+ invoke-static { v0 }, Ljava/lang/Short;->valueOf(S)Ljava/lang/Short;
+ move-result-object v0
+ return-object v0
+ """
+ }
+ }
- 'J', 'D' -> {
- """
- const-wide v0, $value
- return-wide v0
- """
- }
+ is CharEncodedValue -> {
+ if (returnType == "C") {
+ """
+ const/16 v0, $encodedValue
+ return v0
+ """
+ } else {
+ """
+ const/16 v0, $encodedValue
+ invoke-static { v0 }, Ljava/lang/Character;->valueOf(C)Ljava/lang/Character;
+ move-result-object v0
+ return-object v0
+ """
+ }
+ }
- else -> throw Exception("Return type is not supported: $this")
+ is IntEncodedValue -> {
+ if (returnType == "I") {
+ """
+ const v0, $encodedValue
+ return v0
+ """
+ } else {
+ """
+ const v0, $encodedValue
+ invoke-static { v0 }, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
+ move-result-object v0
+ return-object v0
+ """
+ }
+ }
+
+ is FloatEncodedValue -> {
+ val encodedValue = "${encodedValue}f"
+ if (returnType == "F") {
+ """
+ const v0, $encodedValue
+ return v0
+ """
+ } else {
+ """
+ const v0, $encodedValue
+ invoke-static { v0 }, Ljava/lang/Float;->valueOf(F)Ljava/lang/Float;
+ move-result-object v0
+ return-object v0
+ """
+ }
+ }
+
+ is LongEncodedValue -> {
+ val encodedValue = "${encodedValue}L"
+ if (returnType == "J") {
+ """
+ const-wide v0, $encodedValue
+ return-wide v0
+ """
+ } else {
+ """
+ const-wide v0, $encodedValue
+ invoke-static { v0 }, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;
+ move-result-object v0
+ return-object v0
+ """
+ }
+ }
+
+ is DoubleEncodedValue -> {
+ if (returnType == "D") {
+ """
+ const-wide v0, $encodedValue
+ return-wide v0
+ """
+ } else {
+ """
+ const-wide v0, $encodedValue
+ invoke-static { v0 }, Ljava/lang/Double;->valueOf(D)Ljava/lang/Double;
+ move-result-object v0
+ return-object v0
+ """
+ }
+ }
+
+ else -> throw IllegalArgumentException("Value $value cannot be returned from $this")
+ }
}
if (returnLate) {
@@ -1168,7 +1233,7 @@ internal fun BytecodePatchContext.addStaticFieldToExtension(
objectClass: String,
smaliInstructions: String
) {
- val mutableClass = firstClassDefMutable(type)
+ val mutableClass = firstMutableClassDef(type)
val objectCall = "$mutableClass->$fieldName:$objectClass"
mutableClass.apply {
diff --git a/patches/src/main/kotlin/app/revanced/util/Utils.kt b/patches/src/main/kotlin/app/revanced/util/Utils.kt
index ef7d0ef1a..6305809ad 100644
--- a/patches/src/main/kotlin/app/revanced/util/Utils.kt
+++ b/patches/src/main/kotlin/app/revanced/util/Utils.kt
@@ -7,4 +7,21 @@ internal object Utils {
.trimIndent() // Remove the leading newline.
}
-internal fun Boolean.toHexString(): String = if (this) "0x1" else "0x0"
\ No newline at end of file
+internal fun Boolean.toHexString(): String = if (this) "0x1" else "0x0"
+
+internal fun Class<*>.allAssignableTypes(): Set> {
+ val result = mutableSetOf>()
+
+ fun visit(child: Class<*>?) {
+ if (child == null || !result.add(child)) {
+ return
+ }
+
+ child.interfaces.forEach(::visit)
+ visit(child.superclass)
+ }
+
+ visit(this)
+
+ return result
+}