forked from ReVanced/revanced-patches
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(YouTube Music): Add
Spoof client patch
to fix playback (ReVance…
- Loading branch information
Showing
12 changed files
with
274 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
// Do not remove. Necessary for the extension plugin to be applied to the project. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<manifest/> |
27 changes: 27 additions & 0 deletions
27
extensions/music/src/main/java/app/revanced/extension/music/spoof/SpoofClientPatch.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package app.revanced.extension.music.spoof; | ||
|
||
/** | ||
* @noinspection unused | ||
*/ | ||
public class SpoofClientPatch { | ||
private static final int CLIENT_TYPE_ID = 26; | ||
private static final String CLIENT_VERSION = "6.21"; | ||
private static final String DEVICE_MODEL = "iPhone16,2"; | ||
private static final String OS_VERSION = "17.7.2.21H221"; | ||
|
||
public static int getClientId() { | ||
return CLIENT_TYPE_ID; | ||
} | ||
|
||
public static String getClientVersion() { | ||
return CLIENT_VERSION; | ||
} | ||
|
||
public static String getClientModel() { | ||
return DEVICE_MODEL; | ||
} | ||
|
||
public static String getOsVersion() { | ||
return OS_VERSION; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/Fingerprints.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package app.revanced.patches.music.misc.spoof | ||
|
||
import app.revanced.patcher.fingerprint | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
import com.android.tools.smali.dexlib2.Opcode | ||
|
||
internal val playerRequestConstructorFingerprint = fingerprint { | ||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) | ||
strings("player") | ||
} | ||
|
||
/** | ||
* Matches using the class found in [playerRequestConstructorFingerprint]. | ||
*/ | ||
internal val createPlayerRequestBodyFingerprint = fingerprint { | ||
parameters("L") | ||
returns("V") | ||
opcodes( | ||
Opcode.CHECK_CAST, | ||
Opcode.IGET, | ||
Opcode.AND_INT_LIT16, | ||
) | ||
strings("ms") | ||
} | ||
|
||
/** | ||
* Used to get a reference to other clientInfo fields. | ||
*/ | ||
internal val setClientInfoFieldsFingerprint = fingerprint { | ||
returns("L") | ||
strings("Google Inc.") | ||
} | ||
|
||
/** | ||
* Used to get a reference to the clientInfo and clientInfo.clientVersion field. | ||
*/ | ||
internal val setClientInfoClientVersionFingerprint = fingerprint { | ||
strings("10.29") | ||
} |
105 changes: 105 additions & 0 deletions
105
patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofClientPatch.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package app.revanced.patches.music.misc.spoof | ||
|
||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction | ||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions | ||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction | ||
import app.revanced.patcher.extensions.InstructionExtensions.instructions | ||
import app.revanced.patcher.patch.bytecodePatch | ||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable | ||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch | ||
import app.revanced.util.getReference | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
import com.android.tools.smali.dexlib2.Opcode | ||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation | ||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction | ||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference | ||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference | ||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod | ||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter | ||
|
||
internal const val EXTENSION_CLASS_DESCRIPTOR = | ||
"Lapp/revanced/extension/music/spoof/SpoofClientPatch;" | ||
|
||
// TODO: Replace this patch with spoofVideoStreamsPatch once possible. | ||
val spoofClientPatch = bytecodePatch( | ||
name = "Spoof client", | ||
description = "Spoofs the client to fix playback.", | ||
) { | ||
compatibleWith("com.google.android.apps.youtube.music") | ||
|
||
dependsOn( | ||
sharedExtensionPatch, | ||
// TODO: Add settingsPatch | ||
userAgentClientSpoofPatch, | ||
) | ||
|
||
execute { | ||
val playerRequestClass = playerRequestConstructorFingerprint.classDef | ||
|
||
val createPlayerRequestBodyMatch = createPlayerRequestBodyFingerprint.match(playerRequestClass) | ||
|
||
val clientInfoContainerClass = createPlayerRequestBodyMatch.method | ||
.getInstruction(createPlayerRequestBodyMatch.patternMatch!!.startIndex) | ||
.getReference<TypeReference>()!!.type | ||
|
||
val clientInfoField = setClientInfoClientVersionFingerprint.method.instructions.first { | ||
it.opcode == Opcode.IPUT_OBJECT && it.getReference<FieldReference>()!!.definingClass == clientInfoContainerClass | ||
}.getReference<FieldReference>()!! | ||
|
||
val setClientInfoFieldInstructions = setClientInfoFieldsFingerprint.method.instructions.filter { | ||
(it.opcode == Opcode.IPUT_OBJECT || it.opcode == Opcode.IPUT) && | ||
it.getReference<FieldReference>()!!.definingClass == clientInfoField.type | ||
}.map { it.getReference<FieldReference>()!! } | ||
|
||
// Offsets are known for the fields in the clientInfo object. | ||
val clientIdField = setClientInfoFieldInstructions[0] | ||
val clientModelField = setClientInfoFieldInstructions[5] | ||
val osVersionField = setClientInfoFieldInstructions[7] | ||
val clientVersionField = setClientInfoClientVersionFingerprint.method | ||
.getInstruction(setClientInfoClientVersionFingerprint.stringMatches!!.first().index + 1) | ||
.getReference<FieldReference>() | ||
|
||
// Helper method to spoof the client info. | ||
val spoofClientInfoMethod = ImmutableMethod( | ||
playerRequestClass.type, | ||
"spoofClientInfo", | ||
listOf(ImmutableMethodParameter(clientInfoContainerClass, null, null)), | ||
"V", | ||
AccessFlags.PRIVATE.value or AccessFlags.STATIC.value, | ||
null, | ||
null, | ||
MutableMethodImplementation(3), | ||
).toMutable().also(playerRequestClass.methods::add).apply { | ||
addInstructions( | ||
""" | ||
iget-object v0, p0, $clientInfoField | ||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientId()I | ||
move-result v1 | ||
iput v1, v0, $clientIdField | ||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientModel()Ljava/lang/String; | ||
move-result-object v1 | ||
iput-object v1, v0, $clientModelField | ||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientVersion()Ljava/lang/String; | ||
move-result-object v1 | ||
iput-object v1, v0, $clientVersionField | ||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getOsVersion()Ljava/lang/String; | ||
move-result-object v1 | ||
iput-object v1, v0, $osVersionField | ||
return-void | ||
""", | ||
) | ||
} | ||
|
||
createPlayerRequestBodyMatch.method.apply { | ||
val checkCastIndex = createPlayerRequestBodyMatch.patternMatch!!.startIndex | ||
val clientInfoContainerRegister = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA | ||
|
||
addInstruction(checkCastIndex + 1, "invoke-static {v$clientInfoContainerRegister}, $spoofClientInfoMethod") | ||
} | ||
} | ||
} |
7 changes: 0 additions & 7 deletions
7
patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt
This file was deleted.
Oops, something went wrong.
5 changes: 5 additions & 0 deletions
5
patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/UserAgentClientSpoofPatch.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package app.revanced.patches.music.misc.spoof | ||
|
||
import app.revanced.patches.shared.misc.spoof.userAgentClientSpoofPatch | ||
|
||
val userAgentClientSpoofPatch = userAgentClientSpoofPatch("com.google.android.apps.youtube.music") |
81 changes: 81 additions & 0 deletions
81
patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/UserAgentClientSpoofPatch.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package app.revanced.patches.shared.misc.spoof | ||
|
||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction | ||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction | ||
import app.revanced.patches.all.misc.transformation.IMethodCall | ||
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c | ||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch | ||
import app.revanced.util.getReference | ||
import app.revanced.util.indexOfFirstInstruction | ||
import com.android.tools.smali.dexlib2.Opcode | ||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction | ||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference | ||
import com.android.tools.smali.dexlib2.iface.reference.StringReference | ||
|
||
private const val USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE = | ||
"Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;" | ||
|
||
fun userAgentClientSpoofPatch(originalPackageName: String) = transformInstructionsPatch( | ||
filterMap = { classDef, _, instruction, instructionIndex -> | ||
filterMapInstruction35c<MethodCall>( | ||
"Lapp/revanced/extension", | ||
classDef, | ||
instruction, | ||
instructionIndex, | ||
) | ||
}, | ||
transform = transform@{ mutableMethod, entry -> | ||
val (_, _, instructionIndex) = entry | ||
|
||
// Replace the result of context.getPackageName(), if it is used in a user agent string. | ||
mutableMethod.apply { | ||
// After context.getPackageName() the result is moved to a register. | ||
val targetRegister = ( | ||
getInstruction(instructionIndex + 1) | ||
as? OneRegisterInstruction ?: return@transform | ||
).registerA | ||
|
||
// IndexOutOfBoundsException is technically possible here, | ||
// but no such occurrences are present in the app. | ||
val referee = getInstruction(instructionIndex + 2).getReference<MethodReference>()?.toString() | ||
|
||
// Only replace string builder usage. | ||
if (referee != USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE) { | ||
return@transform | ||
} | ||
|
||
// Do not change the package name in methods that use resources, or for methods that use GmsCore. | ||
// Changing these package names will result in playback limitations, | ||
// particularly Android VR background audio only playback. | ||
val resourceOrGmsStringInstructionIndex = indexOfFirstInstruction { | ||
val reference = getReference<StringReference>() | ||
opcode == Opcode.CONST_STRING && | ||
(reference?.string == "android.resource://" || reference?.string == "gcore_") | ||
} | ||
if (resourceOrGmsStringInstructionIndex >= 0) { | ||
return@transform | ||
} | ||
|
||
// Overwrite the result of context.getPackageName() with the original package name. | ||
replaceInstruction( | ||
instructionIndex + 1, | ||
"const-string v$targetRegister, \"$originalPackageName\"", | ||
) | ||
} | ||
}, | ||
) | ||
|
||
@Suppress("unused") | ||
private enum class MethodCall( | ||
override val definedClassName: String, | ||
override val methodName: String, | ||
override val methodParams: Array<String>, | ||
override val returnType: String, | ||
) : IMethodCall { | ||
GetPackageName( | ||
"Landroid/content/Context;", | ||
"getPackageName", | ||
emptyArray(), | ||
"Ljava/lang/String;", | ||
), | ||
} |
Oops, something went wrong.