mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-26 20:21:04 +00:00
Compare commits
4 Commits
v5.27.0
...
v5.28.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69df47602f | ||
|
|
1cea6bfdff | ||
|
|
e2a9552f91 | ||
|
|
7bbaca77ad |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
|||||||
|
# [5.28.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.1...v5.28.0-dev.2) (2025-06-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([a93d74d](https://github.com/ReVanced/revanced-patches/commit/a93d74d26e7ef87a3745df2b9fe82722d65a0e59))
|
||||||
|
|
||||||
|
# [5.28.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0-dev.1) (2025-06-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([8736b6a](https://github.com/ReVanced/revanced-patches/commit/8736b6a80b48cb1f4562c9f9919804006ddb18bd))
|
||||||
|
|
||||||
# [5.27.0](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.27.0) (2025-06-09)
|
# [5.27.0](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.27.0) (2025-06-09)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
|||||||
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
|
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
|
||||||
* and additionally accessible thru {@link LogBufferManager}.
|
* and additionally accessible thru {@link LogBufferManager}.
|
||||||
*
|
*
|
||||||
* All methods are thread safe.
|
* All methods are thread safe, and are safe to call even
|
||||||
|
* if {@link Utils#getContext()} is not available.
|
||||||
*/
|
*/
|
||||||
public class Logger {
|
public class Logger {
|
||||||
|
|
||||||
@@ -138,6 +139,14 @@ public class Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean includeStackTrace() {
|
||||||
|
return Utils.context != null && DEBUG_STACKTRACE.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldShowErrorToast() {
|
||||||
|
return Utils.context != null && DEBUG_TOAST_ON_ERROR.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs debug messages under the outer class name of the code calling this method.
|
* Logs debug messages under the outer class name of the code calling this method.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -158,7 +167,7 @@ public class Logger {
|
|||||||
*/
|
*/
|
||||||
public static void printDebug(LogMessage message, @Nullable Exception ex) {
|
public static void printDebug(LogMessage message, @Nullable Exception ex) {
|
||||||
if (DEBUG.get()) {
|
if (DEBUG.get()) {
|
||||||
logInternal(LogLevel.DEBUG, message, ex, DEBUG_STACKTRACE.get(), false);
|
logInternal(LogLevel.DEBUG, message, ex, includeStackTrace(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +182,7 @@ public class Logger {
|
|||||||
* Logs information messages using the outer class name of the code calling this method.
|
* Logs information messages using the outer class name of the code calling this method.
|
||||||
*/
|
*/
|
||||||
public static void printInfo(LogMessage message, @Nullable Exception ex) {
|
public static void printInfo(LogMessage message, @Nullable Exception ex) {
|
||||||
logInternal(LogLevel.INFO, message, ex, DEBUG_STACKTRACE.get(), false);
|
logInternal(LogLevel.INFO, message, ex, includeStackTrace(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,22 +203,6 @@ public class Logger {
|
|||||||
* @param ex exception (optional)
|
* @param ex exception (optional)
|
||||||
*/
|
*/
|
||||||
public static void printException(LogMessage message, @Nullable Throwable ex) {
|
public static void printException(LogMessage message, @Nullable Throwable ex) {
|
||||||
logInternal(LogLevel.ERROR, message, ex, DEBUG_STACKTRACE.get(), DEBUG_TOAST_ON_ERROR.get());
|
logInternal(LogLevel.ERROR, message, ex, includeStackTrace(), shouldShowErrorToast());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
|
|
||||||
* Normally this method should not be used.
|
|
||||||
*/
|
|
||||||
public static void initializationInfo(LogMessage message) {
|
|
||||||
logInternal(LogLevel.INFO, message, null, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
|
|
||||||
* Normally this method should not be used.
|
|
||||||
*/
|
|
||||||
public static void initializationException(LogMessage message, @Nullable Exception ex) {
|
|
||||||
logInternal(LogLevel.ERROR, message, ex, false, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference
|
|||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private static volatile Context context;
|
static volatile Context context;
|
||||||
|
|
||||||
private static String versionName;
|
private static String versionName;
|
||||||
private static String applicationLabel;
|
private static String applicationLabel;
|
||||||
@@ -363,15 +363,15 @@ public class Utils {
|
|||||||
|
|
||||||
public static Context getContext() {
|
public static Context getContext() {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
Logger.initializationException(() -> "Context is not set by extension hook, returning null", null);
|
Logger.printException(() -> "Context is not set by extension hook, returning null", null);
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setContext(Context appContext) {
|
public static void setContext(Context appContext) {
|
||||||
// Intentionally use logger before context is set,
|
// Intentionally use logger before context is set,
|
||||||
// to expose any bugs in the 'no context available' logger method.
|
// to expose any bugs in the 'no context available' logger code.
|
||||||
Logger.initializationInfo(() -> "Set context: " + appContext);
|
Logger.printInfo(() -> "Set context: " + appContext);
|
||||||
// Must initially set context to check the app language.
|
// Must initially set context to check the app language.
|
||||||
context = appContext;
|
context = appContext;
|
||||||
|
|
||||||
@@ -554,7 +554,7 @@ public class Utils {
|
|||||||
Context currentContext = context;
|
Context currentContext = context;
|
||||||
|
|
||||||
if (currentContext == null) {
|
if (currentContext == null) {
|
||||||
Logger.initializationException(() -> "Cannot show toast (context is null): " + messageToToast, null);
|
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast, null);
|
||||||
} else {
|
} else {
|
||||||
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
||||||
Toast.makeText(currentContext, messageToToast, toastDuration).show();
|
Toast.makeText(currentContext, messageToToast, toastDuration).show();
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package app.revanced.extension.spotify.shared;
|
package app.revanced.extension.spotify.shared;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
public final class ComponentFilters {
|
public final class ComponentFilters {
|
||||||
|
|
||||||
public interface ComponentFilter {
|
public interface ComponentFilter {
|
||||||
|
@NonNull
|
||||||
String getFilterValue();
|
String getFilterValue();
|
||||||
String getFilterRepresentation();
|
String getFilterRepresentation();
|
||||||
default boolean filterUnavailable() {
|
default boolean filterUnavailable() {
|
||||||
@@ -20,7 +23,8 @@ public final class ComponentFilters {
|
|||||||
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
|
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
|
||||||
// 0 is returned when a resource has not been found.
|
// 0 is returned when a resource has not been found.
|
||||||
private int resourceId = -1;
|
private int resourceId = -1;
|
||||||
private String stringfiedResourceId = null;
|
@Nullable
|
||||||
|
private String stringfiedResourceId;
|
||||||
|
|
||||||
public ResourceIdComponentFilter(String resourceName, String resourceType) {
|
public ResourceIdComponentFilter(String resourceName, String resourceType) {
|
||||||
this.resourceName = resourceName;
|
this.resourceName = resourceName;
|
||||||
@@ -34,6 +38,7 @@ public final class ComponentFilters {
|
|||||||
return resourceId;
|
return resourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getFilterValue() {
|
public String getFilterValue() {
|
||||||
if (stringfiedResourceId == null) {
|
if (stringfiedResourceId == null) {
|
||||||
@@ -66,6 +71,7 @@ public final class ComponentFilters {
|
|||||||
this.string = string;
|
this.string = string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getFilterValue() {
|
public String getFilterValue() {
|
||||||
return string;
|
return string;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class SpoofSimPatch {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.initializationException(() -> "Context is not yet set, cannot spoof: " + fieldSpoofed, null);
|
Logger.printException(() -> "Context is not yet set, cannot spoof: " + fieldSpoofed, null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.27.0
|
version = 5.28.0-dev.2
|
||||||
|
|||||||
@@ -921,6 +921,10 @@ public final class app/revanced/patches/spotify/misc/fix/login/FixFacebookLoginP
|
|||||||
public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/spotify/misc/lyrics/ChangeLyricsProviderPatchKt {
|
||||||
|
public static final fun getChangeLyricsProviderPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt {
|
public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt {
|
||||||
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,10 +94,10 @@ val hideCreateButtonPatch = bytecodePatch(
|
|||||||
"""
|
"""
|
||||||
invoke-static { v$oldNavigationBarItemTitleResIdRegister }, $isOldCreateButtonDescriptor
|
invoke-static { v$oldNavigationBarItemTitleResIdRegister }, $isOldCreateButtonDescriptor
|
||||||
move-result v0
|
move-result v0
|
||||||
|
|
||||||
# If this navigation bar item is not the Create button, jump to the normal method logic.
|
# If this navigation bar item is not the Create button, jump to the normal method logic.
|
||||||
if-eqz v0, :normal-method-logic
|
if-eqz v0, :normal-method-logic
|
||||||
|
|
||||||
# Return null early because this method return value is a BottomNavigationItemView.
|
# Return null early because this method return value is a BottomNavigationItemView.
|
||||||
const/4 v0, 0
|
const/4 v0, 0
|
||||||
return-object v0
|
return-object v0
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ val fixFacebookLoginPatch = bytecodePatch(
|
|||||||
// signature checks.
|
// signature checks.
|
||||||
|
|
||||||
val katanaProxyLoginMethodHandlerClass = katanaProxyLoginMethodHandlerClassFingerprint.originalClassDef
|
val katanaProxyLoginMethodHandlerClass = katanaProxyLoginMethodHandlerClassFingerprint.originalClassDef
|
||||||
// Always return 0 (no Intent was launched) as the result of trying to authorize with the Facebook app to
|
// Always return 0 (no Intent was launched) as the result of trying to authorize with the Facebook app to
|
||||||
// make the login fallback to a web browser window.
|
// make the login fallback to a web browser window.
|
||||||
katanaProxyLoginMethodTryAuthorizeFingerprint
|
katanaProxyLoginMethodTryAuthorizeFingerprint
|
||||||
.match(katanaProxyLoginMethodHandlerClass)
|
.match(katanaProxyLoginMethodHandlerClass)
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package app.revanced.patches.spotify.misc.lyrics
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patcher.patch.stringOption
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val changeLyricsProviderPatch = bytecodePatch(
|
||||||
|
name = "Change lyrics provider",
|
||||||
|
description = "Changes the lyrics provider to a custom one.",
|
||||||
|
use = false,
|
||||||
|
) {
|
||||||
|
compatibleWith("com.spotify.music")
|
||||||
|
|
||||||
|
val lyricsProviderHost by stringOption(
|
||||||
|
key = "lyricsProviderHost",
|
||||||
|
default = "lyrics.natanchiodi.fr",
|
||||||
|
title = "Lyrics provider host",
|
||||||
|
description = "The domain name or IP address of a custom lyrics provider.",
|
||||||
|
required = false,
|
||||||
|
) {
|
||||||
|
// Fix bad data if the user enters a URL (https://whatever.com/path).
|
||||||
|
val host = try {
|
||||||
|
URI(it!!).host ?: it
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
return@stringOption false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a courtesy check if the host can be resolved.
|
||||||
|
// If it does not resolve, then print a warning but use the host anyway.
|
||||||
|
// Unresolvable hosts should not be rejected, since the patching environment
|
||||||
|
// may not allow network connections or the network may be down.
|
||||||
|
try {
|
||||||
|
InetAddress.getByName(host)
|
||||||
|
} catch (e: UnknownHostException) {
|
||||||
|
Logger.getLogger(this::class.java.name).warning(
|
||||||
|
"Host \"$host\" did not resolve to any domain."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
execute {
|
||||||
|
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||||
|
Logger.getLogger(this::class.java.name).severe(
|
||||||
|
"Change lyrics provider patch is not supported for this target version."
|
||||||
|
)
|
||||||
|
return@execute
|
||||||
|
}
|
||||||
|
|
||||||
|
val httpClientBuilderMethod = httpClientBuilderFingerprint.originalMethod
|
||||||
|
|
||||||
|
// region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.
|
||||||
|
val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
|
||||||
|
val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
getReference<MethodReference>()?.returnType == "Lokhttp3/HttpUrl;"
|
||||||
|
}
|
||||||
|
val setUrlBuilderHostIndex = indexOfFirstInstructionReversedOrThrow(invokeBuildUrlIndex) {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.definingClass == "Lokhttp3/HttpUrl${"$"}Builder;" &&
|
||||||
|
reference.parameterTypes.firstOrNull() == "Ljava/lang/String;"
|
||||||
|
}
|
||||||
|
val hostRegister = getInstruction<FiveRegisterInstruction>(setUrlBuilderHostIndex).registerD
|
||||||
|
|
||||||
|
MutableMethod(this).apply {
|
||||||
|
name = "rv_getCustomLyricsProviderHttpClient"
|
||||||
|
addInstruction(
|
||||||
|
setUrlBuilderHostIndex,
|
||||||
|
"const-string v$hostRegister, \"$lyricsProviderHost\""
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add the patched method to the class.
|
||||||
|
httpClientBuilderFingerprint.classDef.methods.add(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
// region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.
|
||||||
|
getLyricsHttpClientFingerprint(httpClientBuilderMethod).method.apply {
|
||||||
|
val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
getReference<MethodReference>() == httpClientBuilderMethod
|
||||||
|
}
|
||||||
|
val getLyricsHttpClientInstruction = getInstruction<BuilderInstruction35c>(getLyricsHttpClientIndex)
|
||||||
|
|
||||||
|
// Replace the original method call with a call to our patched method.
|
||||||
|
replaceInstruction(
|
||||||
|
getLyricsHttpClientIndex,
|
||||||
|
BuilderInstruction35c(
|
||||||
|
getLyricsHttpClientInstruction.opcode,
|
||||||
|
getLyricsHttpClientInstruction.registerCount,
|
||||||
|
getLyricsHttpClientInstruction.registerC,
|
||||||
|
getLyricsHttpClientInstruction.registerD,
|
||||||
|
getLyricsHttpClientInstruction.registerE,
|
||||||
|
getLyricsHttpClientInstruction.registerF,
|
||||||
|
getLyricsHttpClientInstruction.registerG,
|
||||||
|
ImmutableMethodReference(
|
||||||
|
patchedHttpClientBuilderMethod.definingClass,
|
||||||
|
patchedHttpClientBuilderMethod.name, // Only difference from the original method.
|
||||||
|
patchedHttpClientBuilderMethod.parameters,
|
||||||
|
patchedHttpClientBuilderMethod.returnType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package app.revanced.patches.spotify.misc.lyrics
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
|
internal val httpClientBuilderFingerprint = fingerprint {
|
||||||
|
strings("client == null", "scheduler == null")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getLyricsHttpClientFingerprint(httpClientBuilderMethodReference: MethodReference) =
|
||||||
|
fingerprint {
|
||||||
|
returns(httpClientBuilderMethodReference.returnType)
|
||||||
|
parameters()
|
||||||
|
custom { method, _ ->
|
||||||
|
method.indexOfFirstInstruction {
|
||||||
|
getReference<MethodReference>() == httpClientBuilderMethodReference
|
||||||
|
} >= 0
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user