Compare commits

...

13 Commits

Author SHA1 Message Date
Pun Butrach
12b819d20e ci: Schedule Crowdin to runs weekly instead of every 12 hours (#6466) 2026-01-12 02:40:19 +01:00
Pun Butrach
004b5908db build: Use Gradle credentials system (#6467) 2026-01-11 16:59:48 +01:00
semantic-release-bot
f4af27dfec chore: Release v5.48.0-dev.9 [skip ci]
# [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](4cc315952d))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](6312fe8d60))
2026-01-08 00:11:57 +00:00
xehpuk
4cc315952d feat: Add Disable Sentry telemetry patch (#6416) 2026-01-08 01:08:52 +01:00
1fexd
6312fe8d60 feat: Disable Play Integrity patch (#6412)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-08 01:06:05 +01:00
Ushie
3d754575a4 ci: Simplify Crowdin translation file destination path (#6463) 2026-01-07 22:31:15 +01:00
semantic-release-bot
a3f7609fe3 chore: Release v5.48.0-dev.8 [skip ci]
# [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](d25dcfe49a))
2026-01-04 13:17:54 +00:00
Swakshan
d25dcfe49a feat(Letterboxd): Add Unlock app icons patch (#6415) 2026-01-04 14:14:29 +01:00
semantic-release-bot
1cc2cb9cb2 chore: Release v5.48.0-dev.7 [skip ci]
# [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](f5cbb31724))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](b42ae27ce6))
2026-01-04 02:43:29 +00:00
xehpuk
f5cbb31724 feat(Strava): Add Disable Quick Edit patch (#6452)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-04 03:38:04 +01:00
xehpuk
b42ae27ce6 feat(Strava): Add Overwrite media upload parameters patch (#6410) 2026-01-04 03:36:08 +01:00
semantic-release-bot
43ab29d03d chore: Release v5.48.0-dev.6 [skip ci]
# [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](789f0a5628))
2026-01-04 02:07:07 +00:00
xehpuk
789f0a5628 fix: Fix build error introduced in 4046bee (#6417) 2026-01-04 03:03:44 +01:00
38 changed files with 972 additions and 245 deletions

View File

@@ -25,7 +25,8 @@ jobs:
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid --no-daemon
- name: Upload artifacts

View File

@@ -2,7 +2,7 @@ name: Pull strings
on:
schedule:
- cron: "0 */12 * * *"
- cron: "0 0 * * 0"
workflow_dispatch:
jobs:

View File

@@ -31,7 +31,8 @@ jobs:
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid clean
- name: Setup Node.js

View File

@@ -1,3 +1,33 @@
# [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)

View File

@@ -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

View File

@@ -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)
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -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;
}

View File

@@ -0,0 +1,5 @@
package com.google.android.play.core.integrity.protocol;
interface IExpressIntegrityServiceCallback {
oneway void onRequestExpressIntegrityTokenResult(in Bundle result) = 2;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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";
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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<IBinder> 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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<IBinder> binderOverride;
public ServiceConnectionWrapper(ServiceConnection base, UnaryOperator<IBinder> 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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.5
version = 5.48.0-dev.9

View File

@@ -104,6 +104,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
@@ -352,6 +356,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;
}
@@ -936,6 +944,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;
@@ -1188,14 +1200,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;
}
@@ -1971,6 +1991,7 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V
public static final fun literal (Lapp/revanced/patcher/FingerprintBuilder;Lkotlin/jvm/functions/Function0;)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V
@@ -1980,7 +2001,6 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V
public static synthetic fun returnEarly$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V

View File

@@ -50,12 +50,9 @@ kotlin {
publishing {
repositories {
maven {
name = "GitHubPackages"
name = "githubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-patches")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
credentials(PasswordCredentials::class)
}
}
}

View File

@@ -0,0 +1,55 @@
package app.revanced.patches.all.misc.playintegrity
import app.revanced.patcher.extensions.InstructionExtensions.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.Opcode
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<MethodReference>()
?.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"
)
}
)
)
}

View File

@@ -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;")
}
}

View File

@@ -0,0 +1,16 @@
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")
execute {
getCanChangeAppIconFingerprint.method.returnEarly(true)
}
}

View File

@@ -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,
) {
execute {
fun Element.replaceOrCreate(tagName: String, attributeName: String, attributeValue: String) {
val childElements = getElementsByTagName(tagName).asSequence().filterIsInstance<Element>()
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", "")
}
}
}

View File

@@ -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"
}
}

View File

@@ -0,0 +1,48 @@
package app.revanced.patches.strava.mediaupload
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(
key = "compressionQuality",
title = "Compression quality (percent)",
description = "This is used as the JPEG quality setting (≤ 100).",
) { it == null || it in 1..100 }
val maxDuration by longOption(
key = "maxDuration",
title = "Max duration (seconds)",
description = "The maximum length (≤ ${60 * 60}) of a video before it gets trimmed.",
) { it == null || it in 1..60 * 60 }
val maxSize by intOption(
key = "maxSize",
title = "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 }
execute {
val mediaUploadParametersClass = classes.single { it.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)
}
}
}

View File

@@ -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")
execute {
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()
}
}

View File

@@ -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;")
}

View File

@@ -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")
execute {
getHasAccessToQuickEditFingerprint.method.returnEarly()
}
}

View File

@@ -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"
}
}

View File

@@ -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;")
}
}

View File

@@ -1,7 +1,7 @@
package app.revanced.patches.strava.subscription
import app.revanced.patcher.extensions.InstructionExtensions.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")
execute {
getSubscribedFingerprint.method.replaceInstruction(
getSubscribedFingerprint.patternMatch!!.startIndex,
"const/4 v0, 0x1",
)
getSubscribedFingerprint.method.returnEarly(true)
}
}

View File

@@ -23,22 +23,19 @@ 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.FiveRegisterInstruction
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.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
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.util.MethodUtil
import java.util.EnumSet
import java.util.*
/**
* Starting from and including the instruction at index [startIndex],
@@ -180,7 +177,7 @@ internal val Instruction.isReturnInstruction: Boolean
*
* @param fieldName The name of the field to find. Partial matches are allowed.
*/
private fun Method.findInstructionIndexFromToString(fieldName: String) : Int {
private fun Method.findInstructionIndexFromToString(fieldName: String): Int {
val stringIndex = indexOfFirstInstruction {
val reference = getReference<StringReference>()
reference?.string?.contains(fieldName) == true
@@ -233,7 +230,7 @@ private fun Method.findInstructionIndexFromToString(fieldName: String) : Int {
* @param fieldName The name of the field to find. Partial matches are allowed.
*/
context(BytecodePatchContext)
internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod {
internal fun Method.findMethodFromToString(fieldName: String): MutableMethod {
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
return navigate(this).to(methodUsageIndex).stop()
}
@@ -243,7 +240,7 @@ internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod {
*
* @param fieldName The name of the field to find. Partial matches are allowed.
*/
internal fun Method.findFieldFromToString(fieldName: String) : FieldReference {
internal fun Method.findFieldFromToString(fieldName: String): FieldReference {
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
return getInstruction<ReferenceInstruction>(methodUsageIndex).getReference<FieldReference>()!!
}
@@ -838,23 +835,59 @@ fun BytecodePatchContext.forEachLiteralValueInstruction(
}
private const val RETURN_TYPE_MISMATCH = "Mismatch between override type and Method return type"
private fun MutableMethod.checkReturnType(expectedTypes: Iterable<Class<*>>) {
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 constant `Boolean` return value.
* 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.
*
* 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 = false) {
val returnType = returnType.first()
check(returnType == 'Z' || (!value && (returnType == 'V' || returnType == 'L' || returnType != '['))) {
RETURN_TYPE_MISMATCH
fun MutableMethod.returnEarly() {
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.toHexString(), false)
overrideReturnValue(value, false)
}
private fun MutableMethod.returnString(value: String, late: Boolean) {
checkReturnType(String::class.java.allAssignableTypes())
overrideReturnValue(ImmutableStringEncodedValue(value), late)
}
/**
* Overrides the first instruction of a method with a constant `String` return value.
* None of the method code will ever execute.
*
* @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: String) = returnString(value, true)
private fun MutableMethod.returnByte(value: Byte, late: Boolean) {
checkReturnType(Byte::class.javaObjectType.allAssignableTypes() + Byte::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableByteEncodedValue(value), late)
}
/**
@@ -863,9 +896,40 @@ fun MutableMethod.returnEarly(value: Boolean = false) {
*
* @see returnLate
*/
fun MutableMethod.returnEarly(value: Byte) {
check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
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) = 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)
}
/**
@@ -874,9 +938,19 @@ fun MutableMethod.returnEarly(value: Byte) {
*
* @see returnLate
*/
fun MutableMethod.returnEarly(value: Short) {
check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
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) = returnShort(value, true)
private fun MutableMethod.returnChar(value: Char, late: Boolean) {
checkReturnType(Char::class.javaObjectType.allAssignableTypes() + Char::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableCharEncodedValue(value), late)
}
/**
@@ -885,9 +959,19 @@ fun MutableMethod.returnEarly(value: Short) {
*
* @see returnLate
*/
fun MutableMethod.returnEarly(value: Char) {
check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.code.toString(), false)
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) = returnChar(value, true)
private fun MutableMethod.returnInt(value: Int, late: Boolean) {
checkReturnType(Int::class.javaObjectType.allAssignableTypes() + Int::class.javaPrimitiveType!!)
overrideReturnValue(ImmutableIntEncodedValue(value), late)
}
/**
@@ -896,20 +980,19 @@ fun MutableMethod.returnEarly(value: Char) {
*
* @see returnLate
*/
fun MutableMethod.returnEarly(value: Int) {
check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
}
fun MutableMethod.returnEarly(value: Int) = returnInt(value, false)
/**
* Overrides the first instruction of a method with a constant `Long` return value.
* None of the method code will ever execute.
* Overrides all return statements with a constant `Int` value.
* All method code is executed the same as unpatched.
*
* @see returnLate
* @see returnEarly
*/
fun MutableMethod.returnEarly(value: Long) {
check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
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)
}
/**
@@ -918,9 +1001,40 @@ fun MutableMethod.returnEarly(value: Long) {
*
* @see returnLate
*/
fun MutableMethod.returnEarly(value: Float) {
check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), false)
fun MutableMethod.returnEarly(value: Float) = returnFloat(value, false)
/**
* Overrides all return statements with a constant `Float` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
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)
}
/**
@@ -929,113 +1043,7 @@ fun MutableMethod.returnEarly(value: Float) {
*
* @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 method code will ever 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
}
overrideReturnValue(value, false)
}
/**
* Overrides all return statements with a constant `Boolean` value.
* All method code is executed the same as unpatched.
*
* 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 returnEarly
*/
fun MutableMethod.returnLate(value: Boolean) {
val returnType = returnType.first()
if (returnType == 'V') {
error("Cannot return late for Method of void type")
}
check(returnType == 'Z' || (!value && (returnType == 'L' || returnType == '['))) {
RETURN_TYPE_MISMATCH
}
overrideReturnValue(value.toHexString(), true)
}
/**
* 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)
}
/**
* 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)
}
/**
* 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)
}
/**
* 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)
}
/**
* 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) {
check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
/**
* Overrides all return statements with a constant `Float` value.
* All method code is executed the same as unpatched.
*
* @see returnEarly
*/
fun MutableMethod.returnLate(value: Float) {
check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH }
overrideReturnValue(value.toString(), true)
}
fun MutableMethod.returnEarly(value: Double) = returnDouble(value, false)
/**
* Overrides all return statements with a constant `Double` value.
@@ -1043,75 +1051,164 @@ fun MutableMethod.returnLate(value: Float) {
*
* @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)
}
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) {

View File

@@ -7,4 +7,21 @@ internal object Utils {
.trimIndent() // Remove the leading newline.
}
internal fun Boolean.toHexString(): String = if (this) "0x1" else "0x0"
internal fun Boolean.toHexString(): String = if (this) "0x1" else "0x0"
internal fun Class<*>.allAssignableTypes(): Set<Class<*>> {
val result = mutableSetOf<Class<*>>()
fun visit(child: Class<*>?) {
if (child == null || !result.add(child)) {
return
}
child.interfaces.forEach(::visit)
visit(child.superclass)
}
visit(this)
return result
}

View File

@@ -5,12 +5,9 @@ pluginManagement {
gradlePluginPortal()
google()
maven {
name = "GitHubPackages"
name = "githubPackages"
url = uri("https://maven.pkg.github.com/revanced/registry")
credentials {
username = providers.gradleProperty("gpr.user").getOrElse(System.getenv("GITHUB_ACTOR"))
password = providers.gradleProperty("gpr.key").getOrElse(System.getenv("GITHUB_TOKEN"))
}
credentials(PasswordCredentials::class)
}
}
}