mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-04-29 22:24:27 +02:00
feat(Spotify - Custom theme): Add option to use unmodified player background gradient (#4741)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
parent
b99c2e5eb7
commit
0ee36939f4
@ -1525,7 +1525,11 @@ public final class app/revanced/patches/yuka/misc/unlockpremium/UnlockPremiumPat
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/util/BytecodeUtilsKt {
|
public final class app/revanced/util/BytecodeUtilsKt {
|
||||||
|
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||||
|
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;D)Z
|
||||||
|
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;F)Z
|
||||||
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z
|
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z
|
||||||
|
public static final fun findFreeRegister (Lcom/android/tools/smali/dexlib2/iface/Method;I[I)I
|
||||||
public static final fun findInstructionIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List;
|
public static final fun findInstructionIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List;
|
||||||
public static final fun findInstructionIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
|
public static final fun findInstructionIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
|
||||||
public static final fun findInstructionIndicesReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List;
|
public static final fun findInstructionIndicesReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List;
|
||||||
@ -1552,9 +1556,17 @@ public final class app/revanced/util/BytecodeUtilsKt {
|
|||||||
public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I
|
public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I
|
||||||
public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
|
public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
|
||||||
public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
|
public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
|
||||||
|
public static final fun indexOfFirstLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;D)I
|
||||||
|
public static final fun indexOfFirstLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;F)I
|
||||||
public static final fun indexOfFirstLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
public static final fun indexOfFirstLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
||||||
|
public static final fun indexOfFirstLiteralInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;D)I
|
||||||
|
public static final fun indexOfFirstLiteralInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;F)I
|
||||||
public static final fun indexOfFirstLiteralInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
public static final fun indexOfFirstLiteralInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
||||||
|
public static final fun indexOfFirstLiteralInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;D)I
|
||||||
|
public static final fun indexOfFirstLiteralInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;F)I
|
||||||
public static final fun indexOfFirstLiteralInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
public static final fun indexOfFirstLiteralInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
||||||
|
public static final fun indexOfFirstLiteralInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;D)I
|
||||||
|
public static final fun indexOfFirstLiteralInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;F)I
|
||||||
public static final fun indexOfFirstLiteralInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
public static final fun indexOfFirstLiteralInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
|
||||||
public static final fun indexOfFirstResourceId (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
|
public static final fun indexOfFirstResourceId (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
|
||||||
public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
|
public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
package app.revanced.patches.spotify.layout.theme
|
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
|
||||||
import app.revanced.patcher.fingerprint
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
|
||||||
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET
|
|
||||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
|
||||||
import app.revanced.util.*
|
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
|
||||||
|
|
||||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/layout/theme/CustomThemePatch;"
|
|
||||||
|
|
||||||
internal val customThemeByteCodePatch = bytecodePatch {
|
|
||||||
dependsOn(sharedExtensionPatch)
|
|
||||||
|
|
||||||
val backgroundColor by spotifyBackgroundColor
|
|
||||||
val backgroundColorSecondary by spotifyBackgroundColorSecondary
|
|
||||||
|
|
||||||
execute {
|
|
||||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
|
||||||
// Bytecode changes are not needed for legacy app target.
|
|
||||||
// Player background color is changed with existing resource patch.
|
|
||||||
return@execute
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MutableMethod.addColorChangeInstructions(literal: Long, colorString: String) {
|
|
||||||
val index = indexOfFirstLiteralInstructionOrThrow(literal)
|
|
||||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
index + 1,
|
|
||||||
"""
|
|
||||||
const-string v$register, "$colorString"
|
|
||||||
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getThemeColor(Ljava/lang/String;)J
|
|
||||||
move-result-wide v$register
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val encoreColorsClassName = with(encoreThemeFingerprint) {
|
|
||||||
// Find index of the first static get found after the string constant.
|
|
||||||
val encoreColorsFieldReferenceIndex = originalMethod.indexOfFirstInstructionOrThrow(
|
|
||||||
stringMatches!!.first().index,
|
|
||||||
Opcode.SGET_OBJECT
|
|
||||||
)
|
|
||||||
|
|
||||||
originalMethod.getInstruction(encoreColorsFieldReferenceIndex)
|
|
||||||
.getReference<FieldReference>()!!.definingClass
|
|
||||||
}
|
|
||||||
|
|
||||||
val encoreColorsConstructorFingerprint = fingerprint {
|
|
||||||
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
|
||||||
custom { method, classDef ->
|
|
||||||
classDef.type == encoreColorsClassName &&
|
|
||||||
method.containsLiteralInstruction(PLAYLIST_BACKGROUND_COLOR_LITERAL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
encoreColorsConstructorFingerprint.method.apply {
|
|
||||||
// Playlist song list background color.
|
|
||||||
addColorChangeInstructions(PLAYLIST_BACKGROUND_COLOR_LITERAL, backgroundColor!!)
|
|
||||||
|
|
||||||
// Share menu background color.
|
|
||||||
addColorChangeInstructions(SHARE_MENU_BACKGROUND_COLOR_LITERAL, backgroundColorSecondary!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
homeCategoryPillColorsFingerprint.method.apply {
|
|
||||||
// Home category pills background color.
|
|
||||||
addColorChangeInstructions(HOME_CATEGORY_PILL_COLOR_LITERAL, backgroundColorSecondary!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsHeaderColorFingerprint.method.apply {
|
|
||||||
// Settings header background color.
|
|
||||||
addColorChangeInstructions(SETTINGS_HEADER_COLOR_LITERAL, backgroundColorSecondary!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,133 @@
|
|||||||
package app.revanced.patches.spotify.layout.theme
|
package app.revanced.patches.spotify.layout.theme
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.patcher.patch.booleanOption
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
|
import app.revanced.patcher.patch.stringOption
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||||
|
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.util.*
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/layout/theme/CustomThemePatch;"
|
||||||
|
|
||||||
|
internal val spotifyBackgroundColor = stringOption(
|
||||||
|
key = "backgroundColor",
|
||||||
|
default = "@android:color/black",
|
||||||
|
title = "Primary background color",
|
||||||
|
description = "The background color. Can be a hex color or a resource reference.",
|
||||||
|
required = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val overridePlayerGradientColor = booleanOption(
|
||||||
|
key = "overridePlayerGradientColor",
|
||||||
|
default = false,
|
||||||
|
title = "Override player gradient color",
|
||||||
|
description = "Apply primary background color to the player gradient color, which changes dynamically with the song.",
|
||||||
|
required = false
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val spotifyBackgroundColorSecondary = stringOption(
|
||||||
|
key = "backgroundColorSecondary",
|
||||||
|
default = "#FF121212",
|
||||||
|
title = "Secondary background color",
|
||||||
|
description =
|
||||||
|
"The secondary background color. (e.g. playlist list in home, player artist, song credits). Can be a hex color or a resource reference.",
|
||||||
|
required = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val spotifyAccentColor = stringOption(
|
||||||
|
key = "accentColor",
|
||||||
|
default = "#FF1ED760",
|
||||||
|
title = "Accent color",
|
||||||
|
description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.",
|
||||||
|
required = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val spotifyAccentColorPressed = stringOption(
|
||||||
|
key = "accentColorPressed",
|
||||||
|
default = "#FF169C46",
|
||||||
|
title = "Pressed dark theme accent color",
|
||||||
|
description =
|
||||||
|
"The color when accented buttons are pressed, by default slightly darker than accent. Can be a hex color or a resource reference.",
|
||||||
|
required = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val customThemeBytecodePatch = bytecodePatch {
|
||||||
|
dependsOn(sharedExtensionPatch)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||||
|
// Bytecode changes are not needed for legacy app target.
|
||||||
|
// Player background color is changed with existing resource patch.
|
||||||
|
return@execute
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableMethod.addColorChangeInstructions(literal: Long, colorString: String) {
|
||||||
|
val index = indexOfFirstLiteralInstructionOrThrow(literal)
|
||||||
|
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
index + 1,
|
||||||
|
"""
|
||||||
|
const-string v$register, "$colorString"
|
||||||
|
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getThemeColor(Ljava/lang/String;)J
|
||||||
|
move-result-wide v$register
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val encoreColorsClassName = with(encoreThemeFingerprint.originalMethod) {
|
||||||
|
// "Encore" colors are referenced right before the value of POSITIVE_INFINITY is returned.
|
||||||
|
// Begin the instruction find using the index of where POSITIVE_INFINITY is set into the register.
|
||||||
|
val positiveInfinityIndex = indexOfFirstLiteralInstructionOrThrow(
|
||||||
|
Float.POSITIVE_INFINITY
|
||||||
|
)
|
||||||
|
val encoreColorsFieldReferenceIndex = indexOfFirstInstructionReversedOrThrow(
|
||||||
|
positiveInfinityIndex,
|
||||||
|
Opcode.SGET_OBJECT
|
||||||
|
)
|
||||||
|
|
||||||
|
getInstruction(encoreColorsFieldReferenceIndex)
|
||||||
|
.getReference<FieldReference>()!!.definingClass
|
||||||
|
}
|
||||||
|
|
||||||
|
val encoreColorsConstructorFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
||||||
|
custom { method, classDef ->
|
||||||
|
classDef.type == encoreColorsClassName &&
|
||||||
|
method.containsLiteralInstruction(PLAYLIST_BACKGROUND_COLOR_LITERAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val backgroundColor by spotifyBackgroundColor
|
||||||
|
val backgroundColorSecondary by spotifyBackgroundColorSecondary
|
||||||
|
|
||||||
|
encoreColorsConstructorFingerprint.method.apply {
|
||||||
|
addColorChangeInstructions(PLAYLIST_BACKGROUND_COLOR_LITERAL, backgroundColor!!)
|
||||||
|
addColorChangeInstructions(SHARE_MENU_BACKGROUND_COLOR_LITERAL, backgroundColorSecondary!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
homeCategoryPillColorsFingerprint.method.addColorChangeInstructions(
|
||||||
|
HOME_CATEGORY_PILL_COLOR_LITERAL,
|
||||||
|
backgroundColorSecondary!!
|
||||||
|
)
|
||||||
|
|
||||||
|
settingsHeaderColorFingerprint.method.addColorChangeInstructions(
|
||||||
|
SETTINGS_HEADER_COLOR_LITERAL,
|
||||||
|
backgroundColorSecondary!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val customThemePatch = resourcePatch(
|
val customThemePatch = resourcePatch(
|
||||||
name = "Custom theme",
|
name = "Custom theme",
|
||||||
@ -11,9 +136,10 @@ val customThemePatch = resourcePatch(
|
|||||||
) {
|
) {
|
||||||
compatibleWith("com.spotify.music")
|
compatibleWith("com.spotify.music")
|
||||||
|
|
||||||
dependsOn(customThemeByteCodePatch)
|
dependsOn(customThemeBytecodePatch)
|
||||||
|
|
||||||
val backgroundColor by spotifyBackgroundColor()
|
val backgroundColor by spotifyBackgroundColor()
|
||||||
|
val overridePlayerGradientColor by overridePlayerGradientColor()
|
||||||
val backgroundColorSecondary by spotifyBackgroundColorSecondary()
|
val backgroundColorSecondary by spotifyBackgroundColorSecondary()
|
||||||
val accentColor by spotifyAccentColor()
|
val accentColor by spotifyAccentColor()
|
||||||
val accentColorPressed by spotifyAccentColorPressed()
|
val accentColorPressed by spotifyAccentColorPressed()
|
||||||
@ -25,31 +151,39 @@ val customThemePatch = resourcePatch(
|
|||||||
val childNodes = resourcesNode.childNodes
|
val childNodes = resourcesNode.childNodes
|
||||||
for (i in 0 until childNodes.length) {
|
for (i in 0 until childNodes.length) {
|
||||||
val node = childNodes.item(i) as? Element ?: continue
|
val node = childNodes.item(i) as? Element ?: continue
|
||||||
|
val name = node.getAttribute("name")
|
||||||
|
|
||||||
node.textContent = when (node.getAttribute("name")) {
|
// Skip overriding song/player gradient start color if the option is disabled.
|
||||||
// Gradient next to user photo and "All" in home page
|
// Gradient end color should be themed regardless to allow the gradient to connect with
|
||||||
|
// our primary background color.
|
||||||
|
if (name == "bg_gradient_start_color" && !overridePlayerGradientColor!!) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
node.textContent = when (name) {
|
||||||
|
// Gradient next to user photo and "All" in home page.
|
||||||
"dark_base_background_base",
|
"dark_base_background_base",
|
||||||
// Main background
|
// Main background.
|
||||||
"gray_7",
|
"gray_7",
|
||||||
// Left sidebar background in tablet mode
|
// Left sidebar background in tablet mode.
|
||||||
"gray_10",
|
"gray_10",
|
||||||
// Add account, Settings and privacy, View Profile left sidebar background
|
// "Add account", "Settings and privacy", "View Profile" left sidebar background.
|
||||||
"dark_base_background_elevated_base",
|
"dark_base_background_elevated_base",
|
||||||
// Song/player background
|
// Song/player gradient start/end color.
|
||||||
"bg_gradient_start_color", "bg_gradient_end_color",
|
"bg_gradient_start_color", "bg_gradient_end_color",
|
||||||
// Login screen
|
// Login screen background and gradient start.
|
||||||
"sthlm_blk", "sthlm_blk_grad_start", "stockholm_black",
|
"sthlm_blk", "sthlm_blk_grad_start",
|
||||||
// Misc
|
// Misc.
|
||||||
"image_placeholder_color",
|
"image_placeholder_color",
|
||||||
-> backgroundColor
|
-> backgroundColor
|
||||||
|
|
||||||
// Track credits, merch in song player
|
// Track credits, merch background in song player.
|
||||||
"track_credits_card_bg", "benefit_list_default_color", "merch_card_background",
|
"track_credits_card_bg", "benefit_list_default_color", "merch_card_background",
|
||||||
// Playlist list background in home page
|
// Playlist list background in home page.
|
||||||
"opacity_white_10",
|
"opacity_white_10",
|
||||||
// About artist background in song player
|
// "About the artist" background in song player.
|
||||||
"gray_15",
|
"gray_15",
|
||||||
// What's New pills background
|
// "What's New" pills background.
|
||||||
"dark_base_background_tinted_highlight"
|
"dark_base_background_tinted_highlight"
|
||||||
-> backgroundColorSecondary
|
-> backgroundColorSecondary
|
||||||
|
|
||||||
@ -59,5 +193,13 @@ val customThemePatch = resourcePatch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Login screen gradient.
|
||||||
|
document("res/drawable/start_screen_gradient.xml").use { document ->
|
||||||
|
val gradientNode = document.getElementsByTagName("gradient").item(0) as Element
|
||||||
|
|
||||||
|
gradientNode.setAttribute("android:startColor", backgroundColor)
|
||||||
|
gradientNode.setAttribute("android:endColor", backgroundColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,13 @@ import app.revanced.util.containsLiteralInstruction
|
|||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
|
||||||
internal val encoreThemeFingerprint = fingerprint {
|
internal val encoreThemeFingerprint = fingerprint {
|
||||||
strings("Encore theme was not provided.") // Partial string match.
|
strings("No EncoreLayoutTheme provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal const val SETTINGS_HEADER_COLOR_LITERAL = 0xFF282828
|
|
||||||
internal const val HOME_CATEGORY_PILL_COLOR_LITERAL = 0xFF333333
|
|
||||||
internal const val PLAYLIST_BACKGROUND_COLOR_LITERAL = 0xFF121212
|
internal const val PLAYLIST_BACKGROUND_COLOR_LITERAL = 0xFF121212
|
||||||
internal const val SHARE_MENU_BACKGROUND_COLOR_LITERAL = 0xFF1F1F1F
|
internal const val SHARE_MENU_BACKGROUND_COLOR_LITERAL = 0xFF1F1F1F
|
||||||
|
internal const val HOME_CATEGORY_PILL_COLOR_LITERAL = 0xFF333333
|
||||||
|
internal const val SETTINGS_HEADER_COLOR_LITERAL = 0xFF282828
|
||||||
|
|
||||||
internal val homeCategoryPillColorsFingerprint = fingerprint{
|
internal val homeCategoryPillColorsFingerprint = fingerprint{
|
||||||
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package app.revanced.patches.spotify.layout.theme
|
|
||||||
|
|
||||||
import app.revanced.patcher.patch.stringOption
|
|
||||||
|
|
||||||
internal val spotifyBackgroundColor = stringOption(
|
|
||||||
key = "backgroundColor",
|
|
||||||
default = "@android:color/black",
|
|
||||||
title = "Primary background color",
|
|
||||||
description = "The background color. Can be a hex color or a resource reference.",
|
|
||||||
required = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
internal val spotifyBackgroundColorSecondary = stringOption(
|
|
||||||
key = "backgroundColorSecondary",
|
|
||||||
default = "#FF121212",
|
|
||||||
title = "Secondary background color",
|
|
||||||
description = "The secondary background color. (e.g. playlist list, player arist, credits). Can be a hex color or a resource reference.",
|
|
||||||
required = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
internal val spotifyAccentColor = stringOption(
|
|
||||||
key = "accentColor",
|
|
||||||
default = "#FF1ED760",
|
|
||||||
title = "Accent color",
|
|
||||||
description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.",
|
|
||||||
required = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
internal val spotifyAccentColorPressed = stringOption(
|
|
||||||
key = "accentColorPressed",
|
|
||||||
default = "#FF169C46",
|
|
||||||
title = "Pressed dark theme accent color",
|
|
||||||
description =
|
|
||||||
"The color when accented buttons are pressed, by default slightly darker than accent. Can be a hex color or a resource reference.",
|
|
||||||
required = true,
|
|
||||||
)
|
|
@ -49,7 +49,7 @@ val unlockPremiumPatch = bytecodePatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Override the attributes map in the getter method.
|
// Override the attributes map in the getter method.
|
||||||
with(productStateProtoFingerprint.method) {
|
productStateProtoFingerprint.method.apply {
|
||||||
val getAttributesMapIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
|
val getAttributesMapIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
|
||||||
val attributesMapRegister = getInstruction<TwoRegisterInstruction>(getAttributesMapIndex).registerA
|
val attributesMapRegister = getInstruction<TwoRegisterInstruction>(getAttributesMapIndex).registerA
|
||||||
|
|
||||||
@ -62,10 +62,11 @@ val unlockPremiumPatch = bytecodePatch(
|
|||||||
|
|
||||||
|
|
||||||
// Add the query parameter trackRows to show popular tracks in the artist page.
|
// Add the query parameter trackRows to show popular tracks in the artist page.
|
||||||
with(buildQueryParametersFingerprint) {
|
buildQueryParametersFingerprint.apply {
|
||||||
val addQueryParameterConditionIndex = method.indexOfFirstInstructionReversedOrThrow(
|
val addQueryParameterConditionIndex = method.indexOfFirstInstructionReversedOrThrow(
|
||||||
stringMatches!!.first().index, Opcode.IF_EQZ
|
stringMatches!!.first().index, Opcode.IF_EQZ
|
||||||
)
|
)
|
||||||
|
|
||||||
method.replaceInstruction(addQueryParameterConditionIndex, "nop")
|
method.replaceInstruction(addQueryParameterConditionIndex, "nop")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,17 +115,19 @@ val unlockPremiumPatch = bytecodePatch(
|
|||||||
|
|
||||||
|
|
||||||
// Disable the "Spotify Premium" upsell experiment in context menus.
|
// Disable the "Spotify Premium" upsell experiment in context menus.
|
||||||
with(contextMenuExperimentsFingerprint) {
|
contextMenuExperimentsFingerprint.apply {
|
||||||
val moveIsEnabledIndex = method.indexOfFirstInstructionOrThrow(
|
val moveIsEnabledIndex = method.indexOfFirstInstructionOrThrow(
|
||||||
stringMatches!!.first().index, Opcode.MOVE_RESULT
|
stringMatches!!.first().index, Opcode.MOVE_RESULT
|
||||||
)
|
)
|
||||||
val isUpsellEnabledRegister = method.getInstruction<OneRegisterInstruction>(moveIsEnabledIndex).registerA
|
val isUpsellEnabledRegister = method.getInstruction<OneRegisterInstruction>(moveIsEnabledIndex).registerA
|
||||||
|
|
||||||
method.replaceInstruction(moveIsEnabledIndex, "const/4 v$isUpsellEnabledRegister, 0")
|
method.replaceInstruction(moveIsEnabledIndex, "const/4 v$isUpsellEnabledRegister, 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Make featureTypeCase_ accessible so we can check the home section type in the extension.
|
// Make featureTypeCase_ accessible so we can check the home section type in the extension.
|
||||||
homeSectionFingerprint.classDef.fields.first { it.name == "featureTypeCase_" }.apply {
|
homeSectionFingerprint.classDef.fields.first { it.name == "featureTypeCase_" }.apply {
|
||||||
|
// Add public flag and remove private.
|
||||||
accessFlags = accessFlags.or(AccessFlags.PUBLIC.value).and(AccessFlags.PRIVATE.value.inv())
|
accessFlags = accessFlags.or(AccessFlags.PUBLIC.value).and(AccessFlags.PRIVATE.value.inv())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +146,7 @@ val unlockPremiumPatch = bytecodePatch(
|
|||||||
// Protobuffer list has an 'isMutable' boolean parameter that sets the mutability.
|
// Protobuffer list has an 'isMutable' boolean parameter that sets the mutability.
|
||||||
// Forcing that always on breaks unrelated code in strange ways.
|
// Forcing that always on breaks unrelated code in strange ways.
|
||||||
// Instead, remove the method call that checks if the list is unmodifiable.
|
// Instead, remove the method call that checks if the list is unmodifiable.
|
||||||
with(protobufListRemoveFingerprint.method) {
|
protobufListRemoveFingerprint.method.apply {
|
||||||
val invokeThrowUnmodifiableIndex = indexOfFirstInstructionOrThrow {
|
val invokeThrowUnmodifiableIndex = indexOfFirstInstructionOrThrow {
|
||||||
val reference = getReference<MethodReference>()
|
val reference = getReference<MethodReference>()
|
||||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||||
@ -155,7 +158,7 @@ val unlockPremiumPatch = bytecodePatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove ads sections from home.
|
// Remove ads sections from home.
|
||||||
with(homeStructureFingerprint.method) {
|
homeStructureFingerprint.method.apply {
|
||||||
val getSectionsIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
|
val getSectionsIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
|
||||||
val sectionsRegister = getInstruction<TwoRegisterInstruction>(getSectionsIndex).registerA
|
val sectionsRegister = getInstruction<TwoRegisterInstruction>(getSectionsIndex).registerA
|
||||||
|
|
||||||
|
@ -121,11 +121,11 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
|||||||
|
|
||||||
// Override the min/max speeds that can be used.
|
// Override the min/max speeds that can be used.
|
||||||
speedLimiterFingerprint.method.apply {
|
speedLimiterFingerprint.method.apply {
|
||||||
val limitMinIndex = indexOfFirstLiteralInstructionOrThrow(0.25f.toRawBits().toLong())
|
val limitMinIndex = indexOfFirstLiteralInstructionOrThrow(0.25f)
|
||||||
var limitMaxIndex = indexOfFirstLiteralInstruction(2.0f.toRawBits().toLong())
|
var limitMaxIndex = indexOfFirstLiteralInstruction(2.0f)
|
||||||
// Newer targets have 4x max speed.
|
// Newer targets have 4x max speed.
|
||||||
if (limitMaxIndex < 0) {
|
if (limitMaxIndex < 0) {
|
||||||
limitMaxIndex = indexOfFirstLiteralInstructionOrThrow(4.0f.toRawBits().toLong())
|
limitMaxIndex = indexOfFirstLiteralInstructionOrThrow(4.0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
val limitMinRegister = getInstruction<OneRegisterInstruction>(limitMinIndex).registerA
|
val limitMinRegister = getInstruction<OneRegisterInstruction>(limitMinIndex).registerA
|
||||||
|
@ -14,6 +14,9 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
|||||||
import app.revanced.patches.shared.misc.mapping.get
|
import app.revanced.patches.shared.misc.mapping.get
|
||||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||||
|
import app.revanced.util.InstructionUtils.Companion.branchOpcodes
|
||||||
|
import app.revanced.util.InstructionUtils.Companion.returnOpcodes
|
||||||
|
import app.revanced.util.InstructionUtils.Companion.writeOpcodes
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.Opcode.*
|
import com.android.tools.smali.dexlib2.Opcode.*
|
||||||
import com.android.tools.smali.dexlib2.iface.Method
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
@ -43,7 +46,7 @@ import java.util.EnumSet
|
|||||||
* @throws IllegalArgumentException If a branch or conditional statement is encountered
|
* @throws IllegalArgumentException If a branch or conditional statement is encountered
|
||||||
* before a suitable register is found.
|
* before a suitable register is found.
|
||||||
*/
|
*/
|
||||||
internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude: Int): Int {
|
fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude: Int): Int {
|
||||||
if (implementation == null) {
|
if (implementation == null) {
|
||||||
throw IllegalArgumentException("Method has no implementation: $this")
|
throw IllegalArgumentException("Method has no implementation: $this")
|
||||||
}
|
}
|
||||||
@ -51,82 +54,6 @@ internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude:
|
|||||||
throw IllegalArgumentException("startIndex out of bounds: $startIndex")
|
throw IllegalArgumentException("startIndex out of bounds: $startIndex")
|
||||||
}
|
}
|
||||||
|
|
||||||
// All registers used by an instruction.
|
|
||||||
fun Instruction.getRegistersUsed() = when (this) {
|
|
||||||
is FiveRegisterInstruction -> {
|
|
||||||
when (registerCount) {
|
|
||||||
1 -> listOf(registerC)
|
|
||||||
2 -> listOf(registerC, registerD)
|
|
||||||
3 -> listOf(registerC, registerD, registerE)
|
|
||||||
4 -> listOf(registerC, registerD, registerE, registerF)
|
|
||||||
else -> listOf(registerC, registerD, registerE, registerF, registerG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is ThreeRegisterInstruction -> listOf(registerA, registerB, registerC)
|
|
||||||
is TwoRegisterInstruction -> listOf(registerA, registerB)
|
|
||||||
is OneRegisterInstruction -> listOf(registerA)
|
|
||||||
is RegisterRangeInstruction -> (startRegister until (startRegister + registerCount)).toList()
|
|
||||||
else -> emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register that is written to by an instruction.
|
|
||||||
fun Instruction.getWriteRegister() : Int {
|
|
||||||
// Two and three register instructions extend OneRegisterInstruction.
|
|
||||||
if (this is OneRegisterInstruction) return registerA
|
|
||||||
throw IllegalStateException("Not a write instruction: $this")
|
|
||||||
}
|
|
||||||
|
|
||||||
val writeOpcodes = EnumSet.of(
|
|
||||||
ARRAY_LENGTH,
|
|
||||||
INSTANCE_OF,
|
|
||||||
NEW_INSTANCE, NEW_ARRAY,
|
|
||||||
MOVE, MOVE_FROM16, MOVE_16, MOVE_WIDE, MOVE_WIDE_FROM16, MOVE_WIDE_16, MOVE_OBJECT,
|
|
||||||
MOVE_OBJECT_FROM16, MOVE_OBJECT_16, MOVE_RESULT, MOVE_RESULT_WIDE, MOVE_RESULT_OBJECT, MOVE_EXCEPTION,
|
|
||||||
CONST, CONST_4, CONST_16, CONST_HIGH16, CONST_WIDE_16, CONST_WIDE_32,
|
|
||||||
CONST_WIDE, CONST_WIDE_HIGH16, CONST_STRING, CONST_STRING_JUMBO,
|
|
||||||
IGET, IGET_WIDE, IGET_OBJECT, IGET_BOOLEAN, IGET_BYTE, IGET_CHAR, IGET_SHORT,
|
|
||||||
IGET_VOLATILE, IGET_WIDE_VOLATILE, IGET_OBJECT_VOLATILE,
|
|
||||||
SGET, SGET_WIDE, SGET_OBJECT, SGET_BOOLEAN, SGET_BYTE, SGET_CHAR, SGET_SHORT,
|
|
||||||
SGET_VOLATILE, SGET_WIDE_VOLATILE, SGET_OBJECT_VOLATILE,
|
|
||||||
AGET, AGET_WIDE, AGET_OBJECT, AGET_BOOLEAN, AGET_BYTE, AGET_CHAR, AGET_SHORT,
|
|
||||||
// Arithmetic and logical operations.
|
|
||||||
ADD_DOUBLE_2ADDR, ADD_DOUBLE, ADD_FLOAT_2ADDR, ADD_FLOAT, ADD_INT_2ADDR,
|
|
||||||
ADD_INT_LIT8, ADD_INT, ADD_LONG_2ADDR, ADD_LONG, ADD_INT_LIT16,
|
|
||||||
AND_INT_2ADDR, AND_INT_LIT8, AND_INT_LIT16, AND_INT, AND_LONG_2ADDR, AND_LONG,
|
|
||||||
DIV_DOUBLE_2ADDR, DIV_DOUBLE, DIV_FLOAT_2ADDR, DIV_FLOAT, DIV_INT_2ADDR,
|
|
||||||
DIV_INT_LIT16, DIV_INT_LIT8, DIV_INT, DIV_LONG_2ADDR, DIV_LONG,
|
|
||||||
DOUBLE_TO_FLOAT, DOUBLE_TO_INT, DOUBLE_TO_LONG,
|
|
||||||
FLOAT_TO_DOUBLE, FLOAT_TO_INT, FLOAT_TO_LONG,
|
|
||||||
INT_TO_BYTE, INT_TO_CHAR, INT_TO_DOUBLE, INT_TO_FLOAT, INT_TO_LONG, INT_TO_SHORT,
|
|
||||||
LONG_TO_DOUBLE, LONG_TO_FLOAT, LONG_TO_INT,
|
|
||||||
MUL_DOUBLE_2ADDR, MUL_DOUBLE, MUL_FLOAT_2ADDR, MUL_FLOAT, MUL_INT_2ADDR,
|
|
||||||
MUL_INT_LIT16, MUL_INT_LIT8, MUL_INT, MUL_LONG_2ADDR, MUL_LONG,
|
|
||||||
NEG_DOUBLE, NEG_FLOAT, NEG_INT, NEG_LONG,
|
|
||||||
NOT_INT, NOT_LONG,
|
|
||||||
OR_INT_2ADDR, OR_INT_LIT16, OR_INT_LIT8, OR_INT, OR_LONG_2ADDR, OR_LONG,
|
|
||||||
REM_DOUBLE_2ADDR, REM_DOUBLE, REM_FLOAT_2ADDR, REM_FLOAT, REM_INT_2ADDR,
|
|
||||||
REM_INT_LIT16, REM_INT_LIT8, REM_INT, REM_LONG_2ADDR, REM_LONG,
|
|
||||||
RSUB_INT_LIT8, RSUB_INT,
|
|
||||||
SHL_INT_2ADDR, SHL_INT_LIT8, SHL_INT, SHL_LONG_2ADDR, SHL_LONG,
|
|
||||||
SHR_INT_2ADDR, SHR_INT_LIT8, SHR_INT, SHR_LONG_2ADDR, SHR_LONG,
|
|
||||||
SUB_DOUBLE_2ADDR, SUB_DOUBLE, SUB_FLOAT_2ADDR, SUB_FLOAT, SUB_INT_2ADDR,
|
|
||||||
SUB_INT, SUB_LONG_2ADDR, SUB_LONG,
|
|
||||||
USHR_INT_2ADDR, USHR_INT_LIT8, USHR_INT, USHR_LONG_2ADDR, USHR_LONG,
|
|
||||||
XOR_INT_2ADDR, XOR_INT_LIT16, XOR_INT_LIT8, XOR_INT, XOR_LONG_2ADDR, XOR_LONG,
|
|
||||||
)
|
|
||||||
|
|
||||||
val branchOpcodes = EnumSet.of(
|
|
||||||
GOTO, GOTO_16, GOTO_32,
|
|
||||||
IF_EQ, IF_NE, IF_LT, IF_GE, IF_GT, IF_LE,
|
|
||||||
IF_EQZ, IF_NEZ, IF_LTZ, IF_GEZ, IF_GTZ, IF_LEZ,
|
|
||||||
PACKED_SWITCH_PAYLOAD, SPARSE_SWITCH_PAYLOAD
|
|
||||||
)
|
|
||||||
|
|
||||||
val returnOpcodes = EnumSet.of(
|
|
||||||
RETURN_VOID, RETURN, RETURN_WIDE, RETURN_OBJECT, RETURN_VOID_NO_BARRIER,
|
|
||||||
THROW
|
|
||||||
)
|
|
||||||
|
|
||||||
// Highest 4-bit register available, exclusive. Ideally return a free register less than this.
|
// Highest 4-bit register available, exclusive. Ideally return a free register less than this.
|
||||||
val maxRegister4Bits = 16
|
val maxRegister4Bits = 16
|
||||||
var bestFreeRegisterFound: Int? = null
|
var bestFreeRegisterFound: Int? = null
|
||||||
@ -134,10 +61,9 @@ internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude:
|
|||||||
|
|
||||||
for (i in startIndex until instructions.count()) {
|
for (i in startIndex until instructions.count()) {
|
||||||
val instruction = getInstruction(i)
|
val instruction = getInstruction(i)
|
||||||
val instructionRegisters = instruction.getRegistersUsed()
|
val instructionRegisters = instruction.registersUsed
|
||||||
|
|
||||||
if (instruction.opcode in returnOpcodes) {
|
if (instruction.isReturnInstruction) {
|
||||||
// Method returns.
|
|
||||||
usedRegisters.addAll(instructionRegisters)
|
usedRegisters.addAll(instructionRegisters)
|
||||||
|
|
||||||
// Use lowest register that hasn't been encountered.
|
// Use lowest register that hasn't been encountered.
|
||||||
@ -157,7 +83,7 @@ internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude:
|
|||||||
"$startIndex excluding: $registersToExclude")
|
"$startIndex excluding: $registersToExclude")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instruction.opcode in branchOpcodes) {
|
if (instruction.isBranchInstruction) {
|
||||||
if (bestFreeRegisterFound != null) {
|
if (bestFreeRegisterFound != null) {
|
||||||
return bestFreeRegisterFound
|
return bestFreeRegisterFound
|
||||||
}
|
}
|
||||||
@ -165,9 +91,9 @@ internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude:
|
|||||||
throw IllegalArgumentException("Encountered a branch statement before a free register could be found")
|
throw IllegalArgumentException("Encountered a branch statement before a free register could be found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instruction.opcode in writeOpcodes) {
|
|
||||||
val writeRegister = instruction.getWriteRegister()
|
|
||||||
|
|
||||||
|
val writeRegister = instruction.writeRegister
|
||||||
|
if (writeRegister != null) {
|
||||||
if (writeRegister !in usedRegisters) {
|
if (writeRegister !in usedRegisters) {
|
||||||
// Verify the register is only used for write and not also as a parameter.
|
// Verify the register is only used for write and not also as a parameter.
|
||||||
// If the instruction uses the write register once then it's not also a read register.
|
// If the instruction uses the write register once then it's not also a read register.
|
||||||
@ -194,6 +120,53 @@ internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude:
|
|||||||
throw IllegalArgumentException("Start index is outside the range of normal control flow: $startIndex")
|
throw IllegalArgumentException("Start index is outside the range of normal control flow: $startIndex")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The registers used by this instruction.
|
||||||
|
*/
|
||||||
|
internal val Instruction.registersUsed: List<Int>
|
||||||
|
get() = when (this) {
|
||||||
|
is FiveRegisterInstruction -> {
|
||||||
|
when (registerCount) {
|
||||||
|
1 -> listOf(registerC)
|
||||||
|
2 -> listOf(registerC, registerD)
|
||||||
|
3 -> listOf(registerC, registerD, registerE)
|
||||||
|
4 -> listOf(registerC, registerD, registerE, registerF)
|
||||||
|
else -> listOf(registerC, registerD, registerE, registerF, registerG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ThreeRegisterInstruction -> listOf(registerA, registerB, registerC)
|
||||||
|
is TwoRegisterInstruction -> listOf(registerA, registerB)
|
||||||
|
is OneRegisterInstruction -> listOf(registerA)
|
||||||
|
is RegisterRangeInstruction -> (startRegister until (startRegister + registerCount)).toList()
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The register that is written to by this instruction,
|
||||||
|
* or NULL if this is not a write opcode.
|
||||||
|
*/
|
||||||
|
internal val Instruction.writeRegister: Int?
|
||||||
|
get() {
|
||||||
|
if (this.opcode !in writeOpcodes) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (this !is OneRegisterInstruction) {
|
||||||
|
throw IllegalStateException("Not a write instruction: $this")
|
||||||
|
}
|
||||||
|
return registerA
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If this instruction is an unconditional or conditional branch opcode.
|
||||||
|
*/
|
||||||
|
internal val Instruction.isBranchInstruction: Boolean
|
||||||
|
get() = this.opcode in branchOpcodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If this instruction returns or throws.
|
||||||
|
*/
|
||||||
|
internal val Instruction.isReturnInstruction: Boolean
|
||||||
|
get() = this.opcode in returnOpcodes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the [MutableMethod] from a given [Method] in a [MutableClass].
|
* Find the [MutableMethod] from a given [Method] in a [MutableClass].
|
||||||
@ -247,7 +220,7 @@ fun MutableMethod.injectHideViewCall(
|
|||||||
* (patch code)
|
* (patch code)
|
||||||
* (original code)
|
* (original code)
|
||||||
*/
|
*/
|
||||||
internal fun MutableMethod.addInstructionsAtControlFlowLabel(
|
fun MutableMethod.addInstructionsAtControlFlowLabel(
|
||||||
insertIndex: Int,
|
insertIndex: Int,
|
||||||
instructions: String,
|
instructions: String,
|
||||||
) {
|
) {
|
||||||
@ -298,7 +271,7 @@ fun Method.indexOfFirstResourceIdOrThrow(resourceName: String): Int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the index of the first literal instruction with the given value.
|
* Find the index of the first literal instruction with the given long value.
|
||||||
*
|
*
|
||||||
* @return the first literal instruction with the value, or -1 if not found.
|
* @return the first literal instruction with the value, or -1 if not found.
|
||||||
* @see indexOfFirstLiteralInstructionOrThrow
|
* @see indexOfFirstLiteralInstructionOrThrow
|
||||||
@ -310,14 +283,56 @@ fun Method.indexOfFirstLiteralInstruction(literal: Long) = implementation?.let {
|
|||||||
} ?: -1
|
} ?: -1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the index of the first literal instruction with the given value,
|
* Find the index of the first literal instruction with the given long value,
|
||||||
* or throw an exception if not found.
|
* or throw an exception if not found.
|
||||||
*
|
*
|
||||||
* @return the first literal instruction with the value, or throws [PatchException] if not found.
|
* @return the first literal instruction with the value, or throws [PatchException] if not found.
|
||||||
*/
|
*/
|
||||||
fun Method.indexOfFirstLiteralInstructionOrThrow(literal: Long): Int {
|
fun Method.indexOfFirstLiteralInstructionOrThrow(literal: Long): Int {
|
||||||
val index = indexOfFirstLiteralInstruction(literal)
|
val index = indexOfFirstLiteralInstruction(literal)
|
||||||
if (index < 0) throw PatchException("Could not find literal value: $literal")
|
if (index < 0) throw PatchException("Could not find long literal: $literal")
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the index of the first literal instruction with the given float value.
|
||||||
|
*
|
||||||
|
* @return the first literal instruction with the value, or -1 if not found.
|
||||||
|
* @see indexOfFirstLiteralInstructionOrThrow
|
||||||
|
*/
|
||||||
|
fun Method.indexOfFirstLiteralInstruction(literal: Float) =
|
||||||
|
indexOfFirstLiteralInstruction(literal.toRawBits().toLong())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the index of the first literal instruction with the given float value,
|
||||||
|
* or throw an exception if not found.
|
||||||
|
*
|
||||||
|
* @return the first literal instruction with the value, or throws [PatchException] if not found.
|
||||||
|
*/
|
||||||
|
fun Method.indexOfFirstLiteralInstructionOrThrow(literal: Float): Int {
|
||||||
|
val index = indexOfFirstLiteralInstruction(literal)
|
||||||
|
if (index < 0) throw PatchException("Could not find float literal: $literal")
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the index of the first literal instruction with the given double value.
|
||||||
|
*
|
||||||
|
* @return the first literal instruction with the value, or -1 if not found.
|
||||||
|
* @see indexOfFirstLiteralInstructionOrThrow
|
||||||
|
*/
|
||||||
|
fun Method.indexOfFirstLiteralInstruction(literal: Double) =
|
||||||
|
indexOfFirstLiteralInstruction(literal.toRawBits().toLong())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the index of the first literal instruction with the given double value,
|
||||||
|
* or throw an exception if not found.
|
||||||
|
*
|
||||||
|
* @return the first literal instruction with the value, or throws [PatchException] if not found.
|
||||||
|
*/
|
||||||
|
fun Method.indexOfFirstLiteralInstructionOrThrow(literal: Double): Int {
|
||||||
|
val index = indexOfFirstLiteralInstruction(literal)
|
||||||
|
if (index < 0) throw PatchException("Could not find double literal: $literal")
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,24 +349,80 @@ fun Method.indexOfFirstLiteralInstructionReversed(literal: Long) = implementatio
|
|||||||
} ?: -1
|
} ?: -1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the index of the last wide literal instruction with the given value,
|
* Find the index of the last wide literal instruction with the given long value,
|
||||||
* or throw an exception if not found.
|
* or throw an exception if not found.
|
||||||
*
|
*
|
||||||
* @return the last literal instruction with the value, or throws [PatchException] if not found.
|
* @return the last literal instruction with the value, or throws [PatchException] if not found.
|
||||||
*/
|
*/
|
||||||
fun Method.indexOfFirstLiteralInstructionReversedOrThrow(literal: Long): Int {
|
fun Method.indexOfFirstLiteralInstructionReversedOrThrow(literal: Long): Int {
|
||||||
val index = indexOfFirstLiteralInstructionReversed(literal)
|
val index = indexOfFirstLiteralInstructionReversed(literal)
|
||||||
if (index < 0) throw PatchException("Could not find literal value: $literal")
|
if (index < 0) throw PatchException("Could not find long literal: $literal")
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the method contains a literal with the given value.
|
* Find the index of the last literal instruction with the given float value.
|
||||||
|
*
|
||||||
|
* @return the last literal instruction with the value, or -1 if not found.
|
||||||
|
* @see indexOfFirstLiteralInstructionOrThrow
|
||||||
|
*/
|
||||||
|
fun Method.indexOfFirstLiteralInstructionReversed(literal: Float) =
|
||||||
|
indexOfFirstLiteralInstructionReversed(literal.toRawBits().toLong())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the index of the last wide literal instruction with the given float value,
|
||||||
|
* or throw an exception if not found.
|
||||||
|
*
|
||||||
|
* @return the last literal instruction with the value, or throws [PatchException] if not found.
|
||||||
|
*/
|
||||||
|
fun Method.indexOfFirstLiteralInstructionReversedOrThrow(literal: Float): Int {
|
||||||
|
val index = indexOfFirstLiteralInstructionReversed(literal)
|
||||||
|
if (index < 0) throw PatchException("Could not find float literal: $literal")
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the index of the last literal instruction with the given double value.
|
||||||
|
*
|
||||||
|
* @return the last literal instruction with the value, or -1 if not found.
|
||||||
|
* @see indexOfFirstLiteralInstructionOrThrow
|
||||||
|
*/
|
||||||
|
fun Method.indexOfFirstLiteralInstructionReversed(literal: Double) =
|
||||||
|
indexOfFirstLiteralInstructionReversed(literal.toRawBits().toLong())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the index of the last wide literal instruction with the given double value,
|
||||||
|
* or throw an exception if not found.
|
||||||
|
*
|
||||||
|
* @return the last literal instruction with the value, or throws [PatchException] if not found.
|
||||||
|
*/
|
||||||
|
fun Method.indexOfFirstLiteralInstructionReversedOrThrow(literal: Double): Int {
|
||||||
|
val index = indexOfFirstLiteralInstructionReversed(literal)
|
||||||
|
if (index < 0) throw PatchException("Could not find double literal: $literal")
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the method contains a literal with the given long value.
|
||||||
*
|
*
|
||||||
* @return if the method contains a literal with the given value.
|
* @return if the method contains a literal with the given value.
|
||||||
*/
|
*/
|
||||||
fun Method.containsLiteralInstruction(literal: Long) = indexOfFirstLiteralInstruction(literal) >= 0
|
fun Method.containsLiteralInstruction(literal: Long) = indexOfFirstLiteralInstruction(literal) >= 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the method contains a literal with the given float value.
|
||||||
|
*
|
||||||
|
* @return if the method contains a literal with the given value.
|
||||||
|
*/
|
||||||
|
fun Method.containsLiteralInstruction(literal: Float) = indexOfFirstLiteralInstruction(literal) >= 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the method contains a literal with the given double value.
|
||||||
|
*
|
||||||
|
* @return if the method contains a literal with the given value.
|
||||||
|
*/
|
||||||
|
fun Method.containsLiteralInstruction(literal: Double) = indexOfFirstLiteralInstruction(literal) >= 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Traverse the class hierarchy starting from the given root class.
|
* Traverse the class hierarchy starting from the given root class.
|
||||||
*
|
*
|
||||||
@ -643,3 +714,58 @@ fun FingerprintBuilder.literal(literalSupplier: () -> Long) {
|
|||||||
method.containsLiteralInstruction(literalSupplier())
|
method.containsLiteralInstruction(literalSupplier())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class InstructionUtils {
|
||||||
|
companion object {
|
||||||
|
val branchOpcodes: EnumSet<Opcode> = EnumSet.of(
|
||||||
|
GOTO, GOTO_16, GOTO_32,
|
||||||
|
IF_EQ, IF_NE, IF_LT, IF_GE, IF_GT, IF_LE,
|
||||||
|
IF_EQZ, IF_NEZ, IF_LTZ, IF_GEZ, IF_GTZ, IF_LEZ,
|
||||||
|
PACKED_SWITCH_PAYLOAD, SPARSE_SWITCH_PAYLOAD
|
||||||
|
)
|
||||||
|
|
||||||
|
val returnOpcodes: EnumSet<Opcode> = EnumSet.of(
|
||||||
|
RETURN_VOID, RETURN, RETURN_WIDE, RETURN_OBJECT, RETURN_VOID_NO_BARRIER,
|
||||||
|
THROW
|
||||||
|
)
|
||||||
|
|
||||||
|
val writeOpcodes: EnumSet<Opcode> = EnumSet.of(
|
||||||
|
ARRAY_LENGTH,
|
||||||
|
INSTANCE_OF,
|
||||||
|
NEW_INSTANCE, NEW_ARRAY,
|
||||||
|
MOVE, MOVE_FROM16, MOVE_16, MOVE_WIDE, MOVE_WIDE_FROM16, MOVE_WIDE_16, MOVE_OBJECT,
|
||||||
|
MOVE_OBJECT_FROM16, MOVE_OBJECT_16, MOVE_RESULT, MOVE_RESULT_WIDE, MOVE_RESULT_OBJECT, MOVE_EXCEPTION,
|
||||||
|
CONST, CONST_4, CONST_16, CONST_HIGH16, CONST_WIDE_16, CONST_WIDE_32,
|
||||||
|
CONST_WIDE, CONST_WIDE_HIGH16, CONST_STRING, CONST_STRING_JUMBO,
|
||||||
|
IGET, IGET_WIDE, IGET_OBJECT, IGET_BOOLEAN, IGET_BYTE, IGET_CHAR, IGET_SHORT,
|
||||||
|
IGET_VOLATILE, IGET_WIDE_VOLATILE, IGET_OBJECT_VOLATILE,
|
||||||
|
SGET, SGET_WIDE, SGET_OBJECT, SGET_BOOLEAN, SGET_BYTE, SGET_CHAR, SGET_SHORT,
|
||||||
|
SGET_VOLATILE, SGET_WIDE_VOLATILE, SGET_OBJECT_VOLATILE,
|
||||||
|
AGET, AGET_WIDE, AGET_OBJECT, AGET_BOOLEAN, AGET_BYTE, AGET_CHAR, AGET_SHORT,
|
||||||
|
// Arithmetic and logical operations.
|
||||||
|
ADD_DOUBLE_2ADDR, ADD_DOUBLE, ADD_FLOAT_2ADDR, ADD_FLOAT, ADD_INT_2ADDR,
|
||||||
|
ADD_INT_LIT8, ADD_INT, ADD_LONG_2ADDR, ADD_LONG, ADD_INT_LIT16,
|
||||||
|
AND_INT_2ADDR, AND_INT_LIT8, AND_INT_LIT16, AND_INT, AND_LONG_2ADDR, AND_LONG,
|
||||||
|
DIV_DOUBLE_2ADDR, DIV_DOUBLE, DIV_FLOAT_2ADDR, DIV_FLOAT, DIV_INT_2ADDR,
|
||||||
|
DIV_INT_LIT16, DIV_INT_LIT8, DIV_INT, DIV_LONG_2ADDR, DIV_LONG,
|
||||||
|
DOUBLE_TO_FLOAT, DOUBLE_TO_INT, DOUBLE_TO_LONG,
|
||||||
|
FLOAT_TO_DOUBLE, FLOAT_TO_INT, FLOAT_TO_LONG,
|
||||||
|
INT_TO_BYTE, INT_TO_CHAR, INT_TO_DOUBLE, INT_TO_FLOAT, INT_TO_LONG, INT_TO_SHORT,
|
||||||
|
LONG_TO_DOUBLE, LONG_TO_FLOAT, LONG_TO_INT,
|
||||||
|
MUL_DOUBLE_2ADDR, MUL_DOUBLE, MUL_FLOAT_2ADDR, MUL_FLOAT, MUL_INT_2ADDR,
|
||||||
|
MUL_INT_LIT16, MUL_INT_LIT8, MUL_INT, MUL_LONG_2ADDR, MUL_LONG,
|
||||||
|
NEG_DOUBLE, NEG_FLOAT, NEG_INT, NEG_LONG,
|
||||||
|
NOT_INT, NOT_LONG,
|
||||||
|
OR_INT_2ADDR, OR_INT_LIT16, OR_INT_LIT8, OR_INT, OR_LONG_2ADDR, OR_LONG,
|
||||||
|
REM_DOUBLE_2ADDR, REM_DOUBLE, REM_FLOAT_2ADDR, REM_FLOAT, REM_INT_2ADDR,
|
||||||
|
REM_INT_LIT16, REM_INT_LIT8, REM_INT, REM_LONG_2ADDR, REM_LONG,
|
||||||
|
RSUB_INT_LIT8, RSUB_INT,
|
||||||
|
SHL_INT_2ADDR, SHL_INT_LIT8, SHL_INT, SHL_LONG_2ADDR, SHL_LONG,
|
||||||
|
SHR_INT_2ADDR, SHR_INT_LIT8, SHR_INT, SHR_LONG_2ADDR, SHR_LONG,
|
||||||
|
SUB_DOUBLE_2ADDR, SUB_DOUBLE, SUB_FLOAT_2ADDR, SUB_FLOAT, SUB_INT_2ADDR,
|
||||||
|
SUB_INT, SUB_LONG_2ADDR, SUB_LONG,
|
||||||
|
USHR_INT_2ADDR, USHR_INT_LIT8, USHR_INT, USHR_LONG_2ADDR, USHR_LONG,
|
||||||
|
XOR_INT_2ADDR, XOR_INT_LIT16, XOR_INT_LIT8, XOR_INT, XOR_LONG_2ADDR, XOR_LONG,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user