Skip to content

Commit c20dbff

Browse files
author
LisoUseInAIKyrios
authored
fix(YouTube - Spoof client): Improve Android spoofing (#3230)
1 parent 9c58b4b commit c20dbff

10 files changed

+45
-135
lines changed

src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch.kt

Lines changed: 15 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import app.revanced.patches.all.misc.resources.AddResourcesPatch
1515
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
1616
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
1717
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
18-
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.*
18+
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildInitPlaybackRequestFingerprint
19+
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildPlayerRequestURIFingerprint
20+
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyFingerprint
21+
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithModelFingerprint
22+
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.SetPlayerRequestClientTypeFingerprint
1923
import app.revanced.patches.youtube.misc.settings.SettingsPatch
20-
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
2124
import app.revanced.util.getReference
22-
import app.revanced.util.indexOfFirstInstruction
2325
import app.revanced.util.resultOrThrow
2426
import com.android.tools.smali.dexlib2.AccessFlags
2527
import com.android.tools.smali.dexlib2.Opcode
@@ -35,11 +37,9 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
3537
name = "Spoof client",
3638
description = "Spoofs the client to allow video playback.",
3739
dependencies = [
38-
PlayerResponseMethodHookPatch::class,
3940
SettingsPatch::class,
4041
AddResourcesPatch::class,
4142
UserAgentClientSpoofPatch::class,
42-
PlayerResponseMethodHookPatch::class,
4343
],
4444
compatiblePackages = [
4545
CompatiblePackage(
@@ -69,19 +69,11 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
6969
)
7070
object SpoofClientPatch : BytecodePatch(
7171
setOf(
72-
// Client type spoof.
7372
BuildInitPlaybackRequestFingerprint,
7473
BuildPlayerRequestURIFingerprint,
7574
SetPlayerRequestClientTypeFingerprint,
7675
CreatePlayerRequestBodyFingerprint,
7776
CreatePlayerRequestBodyWithModelFingerprint,
78-
79-
// Storyboard spoof.
80-
StoryboardRendererSpecFingerprint,
81-
PlayerResponseModelImplRecommendedLevelFingerprint,
82-
StoryboardRendererDecoderRecommendedLevelFingerprint,
83-
PlayerResponseModelImplGeneralFingerprint,
84-
StoryboardRendererDecoderSpecFingerprint,
8577
),
8678
) {
8779
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
@@ -98,7 +90,7 @@ object SpoofClientPatch : BytecodePatch(
9890
sorting = Sorting.UNSORTED,
9991
preferences = setOf(
10092
SwitchPreference("revanced_spoof_client"),
101-
SwitchPreference("revanced_spoof_client_use_testsuite"),
93+
SwitchPreference("revanced_spoof_client_use_ios"),
10294
),
10395
),
10496
)
@@ -149,11 +141,11 @@ object SpoofClientPatch : BytecodePatch(
149141
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
150142
// Field in the player request object that holds the client info object.
151143
val clientInfoField = result.mutableMethod
152-
.getInstructions().first { instruction ->
144+
.getInstructions().find { instruction ->
153145
// requestMessage.clientInfo = clientInfoBuilder.build();
154146
instruction.opcode == Opcode.IPUT_OBJECT &&
155147
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
156-
}.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField")
148+
}?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField")
157149

158150
// Client info object's client type field.
159151
val clientInfoClientTypeField = result.mutableMethod
@@ -168,20 +160,17 @@ object SpoofClientPatch : BytecodePatch(
168160
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
169161
}
170162

171-
val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().mutableMethod.let {
172-
val instructions = it.getInstructions()
173-
174-
val getClientModelIndex = it.indexOfFirstInstruction {
175-
getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;"
176-
}
163+
val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().let {
164+
val getClientModelIndex = CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction(it.method)
165+
val instructions = it.mutableMethod.getInstructions()
177166

178167
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
179168
instructions.subList(
180169
getClientModelIndex,
181-
instructions.lastIndex,
182-
).first { instruction ->
170+
instructions.size,
171+
).find { instruction ->
183172
instruction.opcode == Opcode.IPUT_OBJECT
184-
}.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField")
173+
}?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField")
185174
}
186175

187176
// endregion
@@ -243,6 +232,7 @@ object SpoofClientPatch : BytecodePatch(
243232
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
244233
move-result-object v1
245234
iput-object v1, v0, $clientInfoClientVersionField
235+
246236
:disabled
247237
return-void
248238
""",
@@ -253,104 +243,5 @@ object SpoofClientPatch : BytecodePatch(
253243

254244
// endregion
255245

256-
// region Fix storyboard if Android Testsuite is used.
257-
258-
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
259-
"$INTEGRATIONS_CLASS_DESCRIPTOR->setPlayerResponseVideoId(" +
260-
"Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
261-
)
262-
263-
// Hook recommended seekbar thumbnails quality level for regular videos.
264-
StoryboardRendererDecoderRecommendedLevelFingerprint.resultOrThrow().let {
265-
val endIndex = it.scanResult.patternScanResult!!.endIndex
266-
267-
it.mutableMethod.apply {
268-
val originalValueRegister =
269-
getInstruction<OneRegisterInstruction>(endIndex).registerA
270-
271-
addInstructions(
272-
endIndex + 1,
273-
"""
274-
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
275-
move-result v$originalValueRegister
276-
""",
277-
)
278-
}
279-
}
280-
281-
// Hook the recommended precise seeking thumbnails quality.
282-
PlayerResponseModelImplRecommendedLevelFingerprint.resultOrThrow().let {
283-
val endIndex = it.scanResult.patternScanResult!!.endIndex
284-
285-
it.mutableMethod.apply {
286-
val originalValueRegister =
287-
getInstruction<OneRegisterInstruction>(endIndex).registerA
288-
289-
addInstructions(
290-
endIndex,
291-
"""
292-
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
293-
move-result v$originalValueRegister
294-
""",
295-
)
296-
}
297-
}
298-
299-
// TODO: Hook the seekbar recommended level for Shorts to fix Shorts low quality seekbar thumbnails.
300-
301-
/**
302-
* Hook StoryBoard renderer url.
303-
*/
304-
PlayerResponseModelImplGeneralFingerprint.resultOrThrow().let {
305-
val getStoryBoardIndex = it.scanResult.patternScanResult!!.endIndex
306-
307-
it.mutableMethod.apply {
308-
val getStoryBoardRegister = getInstruction<OneRegisterInstruction>(getStoryBoardIndex).registerA
309-
310-
addInstructions(
311-
getStoryBoardIndex,
312-
"""
313-
invoke-static { v$getStoryBoardRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
314-
move-result-object v$getStoryBoardRegister
315-
""",
316-
)
317-
}
318-
}
319-
320-
// Hook the seekbar thumbnail decoder, required for Shorts.
321-
StoryboardRendererDecoderSpecFingerprint.resultOrThrow().let {
322-
val storyBoardUrlIndex = it.scanResult.patternScanResult!!.startIndex + 1
323-
324-
it.mutableMethod.apply {
325-
val getStoryBoardRegister = getInstruction<OneRegisterInstruction>(storyBoardUrlIndex).registerA
326-
327-
addInstructions(
328-
storyBoardUrlIndex + 1,
329-
"""
330-
invoke-static { v$getStoryBoardRegister }, ${INTEGRATIONS_CLASS_DESCRIPTOR}->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
331-
move-result-object v$getStoryBoardRegister
332-
""",
333-
)
334-
}
335-
}
336-
337-
StoryboardRendererSpecFingerprint.resultOrThrow().let {
338-
it.mutableMethod.apply {
339-
val storyBoardUrlParams = "p0"
340-
341-
addInstructions(
342-
0,
343-
"""
344-
if-nez $storyBoardUrlParams, :ignore
345-
invoke-static { $storyBoardUrlParams }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
346-
move-result-object $storyBoardUrlParams
347-
:ignore
348-
nop
349-
""",
350-
)
351-
}
352-
}
353-
354-
// endregion
355246
}
356247
}

src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/CreatePlayerRequestBodyWithModelFingerprint.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,30 @@ package app.revanced.patches.youtube.misc.fix.playback.fingerprints
22

33
import app.revanced.patcher.extensions.or
44
import app.revanced.patcher.fingerprint.MethodFingerprint
5+
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction
6+
import app.revanced.util.containsWideLiteralInstructionValue
57
import app.revanced.util.getReference
8+
import app.revanced.util.indexOfFirstInstruction
69
import com.android.tools.smali.dexlib2.AccessFlags
10+
import com.android.tools.smali.dexlib2.iface.Method
711
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
812

913
internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
1014
returnType = "L",
1115
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
1216
parameters = listOf(),
1317
customFingerprint = { methodDef, _ ->
14-
methodDef.implementation!!.instructions.any {
15-
it.getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;"
16-
}
18+
methodDef.containsWideLiteralInstructionValue(1073741824) &&
19+
indexOfBuildModelInstruction(methodDef) >= 0
1720
},
18-
)
21+
) {
22+
fun indexOfBuildModelInstruction(methodDef: Method) =
23+
methodDef.indexOfFirstInstruction {
24+
val reference = getReference<FieldReference>()
25+
reference?.definingClass == "Landroid/os/Build;" &&
26+
reference.name == "MODEL" &&
27+
reference.type == "Ljava/lang/String;"
28+
}
29+
}
30+
31+

src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplGeneralFingerprint.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import app.revanced.util.containsWideLiteralInstructionValue
66
import com.android.tools.smali.dexlib2.AccessFlags
77
import com.android.tools.smali.dexlib2.Opcode
88

9+
@Deprecated("Fingerprint is obsolete and will be deleted soon")
910
internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint(
1011
returnType = "Ljava/lang/String;",
1112
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplRecommendedLevelFingerprint.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import app.revanced.util.containsWideLiteralInstructionValue
66
import com.android.tools.smali.dexlib2.AccessFlags
77
import com.android.tools.smali.dexlib2.Opcode
88

9+
@Deprecated("Fingerprint is obsolete and will be deleted soon")
910
internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFingerprint(
1011
returnType = "I",
1112
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
22

3-
import app.revanced.patcher.fingerprint.MethodFingerprint
3+
import app.revanced.util.patch.LiteralValueFingerprint
44
import com.android.tools.smali.dexlib2.Opcode
55

6-
internal object SetPlayerRequestClientTypeFingerprint : MethodFingerprint(
7-
strings = listOf("10.29"),
6+
internal object SetPlayerRequestClientTypeFingerprint : LiteralValueFingerprint(
87
opcodes = listOf(
98
Opcode.IGET,
109
Opcode.IPUT, // Sets ClientInfo.clientId.
1110
),
11+
strings = listOf("10.29"),
12+
literalSupplier = { 134217728 }
1213
)

src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererDecoderRecommendedLevelFingerprint.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
88
/**
99
* Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint].
1010
*/
11+
@Deprecated("Fingerprint is obsolete and will be deleted soon")
1112
internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint(
1213
returnType = "V",
1314
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererDecoderSpecFingerprint.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
88
/**
99
* Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint].
1010
*/
11+
@Deprecated("Fingerprint is obsolete and will be deleted soon")
1112
internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
1213
returnType = "V",
1314
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererSpecFingerprint.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.or
44
import app.revanced.patcher.fingerprint.MethodFingerprint
55
import com.android.tools.smali.dexlib2.AccessFlags
66

7+
@Deprecated("Fingerprint is obsolete and will be deleted soon")
78
internal object StoryboardRendererSpecFingerprint : MethodFingerprint(
89
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
910
returnType = "L",

src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardThumbnailFingerprint.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
88
/**
99
* Resolves using the class found in [StoryboardThumbnailParentFingerprint].
1010
*/
11+
@Deprecated("Fingerprint is obsolete and will be deleted soon")
1112
internal object StoryboardThumbnailFingerprint : MethodFingerprint(
1213
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
1314
returnType = "Z",

src/main/resources/addresources/values/strings.xml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,10 +1092,9 @@
10921092
<string name="revanced_spoof_client_summary_on">Client is spoofed</string>
10931093
<string name="revanced_spoof_client_summary_off">Client is not spoofed\n\nVideo playback may not work</string>
10941094
<string name="revanced_spoof_client_user_dialog_message">Turning off this setting may cause video playback issues.</string>
1095-
<string name="revanced_spoof_client_use_testsuite_title">Spoof client to Android Testsuite</string>
1096-
<string name="revanced_spoof_client_use_testsuite_summary">Spoof the client to Android Testsuite</string>
1097-
<string name="revanced_spoof_client_use_testsuite_summary_on">Client is spoofed to an Android Testsuite client (iOS client is used for live streams)\n\nSide effects include, but are not limited to:\n• Speed flyout menu is missing\n• Captions are missing\n• Player swipe gestures may not work\n• Low quality Shorts seekbar thumbnails\n• Watch history may not work</string>
1098-
<string name="revanced_spoof_client_use_testsuite_summary_off">Client is spoofed to an iOS client\n\nSide effects include:\n• No HDR video\n• Speed flyout menu is missing</string>
1095+
<string name="revanced_spoof_client_use_ios_title">Spoof client to iOS</string>
1096+
<string name="revanced_spoof_client_use_ios_summary_on">Client is currently spoofed to iOS\n\nSide effects include:\n• No HDR video\n• Speed menu is missing\n• Watch history may not work\n• Live streams cannot play as audio only\n• Live streams not available on older devices</string>
1097+
<string name="revanced_spoof_client_use_ios_summary_off">Client is currently spoofed to Android VR\n\nSide effects include:\n• No HDR video\n• Player swipe gestures do not work\n• Kids videos do not playback\n• Paused videos can randomly resume</string>
10991098
<string name="revanced_spoof_client_storyboard_timeout">Spoof client thumbnails not available (API timed out)</string>
11001099
<string name="revanced_spoof_client_storyboard_io_exception">Spoof client thumbnails temporarily not available: %s</string>
11011100
</patch>

0 commit comments

Comments
 (0)