Skip to content

Commit a4b0e76

Browse files
zainarbaniLisoUseInAIKyriosoSumAtrIX
authored
fix(YouTube - Client Spoof): Restore missing high qualities by spoofing the iOS client user agent (#3468)
Co-authored-by: LisoUseInAIKyrios <[email protected]> Co-authored-by: oSumAtrIX <[email protected]>
1 parent 0a7b2c5 commit a4b0e76

File tree

4 files changed

+99
-2
lines changed

4 files changed

+99
-2
lines changed

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
55
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
66
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
77
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
8+
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
89
import app.revanced.patcher.extensions.or
910
import app.revanced.patcher.patch.BytecodePatch
1011
import app.revanced.patcher.patch.PatchException
@@ -77,13 +78,17 @@ object SpoofClientPatch : BytecodePatch(
7778
SetPlayerRequestClientTypeFingerprint,
7879
CreatePlayerRequestBodyFingerprint,
7980
CreatePlayerRequestBodyWithModelFingerprint,
81+
CreatePlayerRequestBodyWithVersionReleaseFingerprint,
8082

8183
// Player gesture config.
8284
PlayerGestureConfigSyntheticFingerprint,
8385

8486
// Player speed menu item.
8587
CreatePlaybackSpeedMenuItemFingerprint,
8688

89+
// Video qualities missing.
90+
BuildRequestFingerprint,
91+
8792
// Watch history.
8893
GetTrackingUriFingerprint,
8994
),
@@ -92,6 +97,10 @@ object SpoofClientPatch : BytecodePatch(
9297
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofClientPatch;"
9398
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
9499
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
100+
private const val REQUEST_CLASS_DESCRIPTOR =
101+
"Lorg/chromium/net/ExperimentalUrlRequest;"
102+
private const val REQUEST_BUILDER_CLASS_DESCRIPTOR =
103+
"Lorg/chromium/net/ExperimentalUrlRequest\$Builder;"
95104

96105
override fun execute(context: BytecodeContext) {
97106
AddResourcesPatch(this::class)
@@ -186,6 +195,19 @@ object SpoofClientPatch : BytecodePatch(
186195
?: throw PatchException("Could not find clientInfoClientModelField")
187196
}
188197

198+
val clientInfoOsVersionField = CreatePlayerRequestBodyWithVersionReleaseFingerprint.resultOrThrow().let {
199+
val getOsVersionIndex =
200+
CreatePlayerRequestBodyWithVersionReleaseFingerprint.indexOfBuildVersionReleaseInstruction(it.method)
201+
202+
// The next IPUT_OBJECT instruction after getting the client os version is setting the client os version field.
203+
val index = it.mutableMethod.indexOfFirstInstructionOrThrow(getOsVersionIndex) {
204+
opcode == Opcode.IPUT_OBJECT
205+
}
206+
207+
it.mutableMethod.getInstruction(index).getReference<FieldReference>()
208+
?: throw PatchException("Could not find clientInfoOsVersionField")
209+
}
210+
189211
// endregion
190212

191213
// region Spoof client type for /player requests.
@@ -245,6 +267,12 @@ object SpoofClientPatch : BytecodePatch(
245267
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
246268
move-result-object v1
247269
iput-object v1, v0, $clientInfoClientVersionField
270+
271+
# Set client os version to the spoofed value.
272+
iget-object v1, v0, $clientInfoOsVersionField
273+
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getOsVersion(Ljava/lang/String;)Ljava/lang/String;
274+
move-result-object v1
275+
iput-object v1, v0, $clientInfoOsVersionField
248276
249277
:disabled
250278
return-void
@@ -330,5 +358,28 @@ object SpoofClientPatch : BytecodePatch(
330358
}
331359

332360
// endregion
361+
362+
// region Fix video qualities missing, if spoofing to iOS by overriding the user agent.
363+
364+
BuildRequestFingerprint.resultOrThrow().let { result ->
365+
result.mutableMethod.apply {
366+
val buildRequestIndex = getInstructions().lastIndex - 2
367+
val requestBuilderRegister = getInstruction<FiveRegisterInstruction>(buildRequestIndex).registerC
368+
369+
val newRequestBuilderIndex = result.scanResult.patternScanResult!!.endIndex
370+
val urlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
371+
372+
// Replace "requestBuilder.build(): Request" with "overrideUserAgent(requestBuilder, url): Request".
373+
replaceInstruction(
374+
buildRequestIndex,
375+
"invoke-static { v$requestBuilderRegister, v$urlRegister }, " +
376+
"$INTEGRATIONS_CLASS_DESCRIPTOR->" +
377+
"overrideUserAgent(${REQUEST_BUILDER_CLASS_DESCRIPTOR}Ljava/lang/String;)" +
378+
REQUEST_CLASS_DESCRIPTOR
379+
)
380+
}
381+
}
382+
383+
// endregion
333384
}
334385
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
2+
3+
import app.revanced.patcher.extensions.or
4+
import app.revanced.patcher.fingerprint.MethodFingerprint
5+
import com.android.tools.smali.dexlib2.AccessFlags
6+
import com.android.tools.smali.dexlib2.Opcode
7+
8+
internal object BuildRequestFingerprint : MethodFingerprint(
9+
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
10+
returnType = "Lorg/chromium/net/UrlRequest;",
11+
opcodes = listOf(
12+
Opcode.INVOKE_DIRECT,
13+
Opcode.INVOKE_VIRTUAL
14+
)
15+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
2+
3+
import app.revanced.patcher.extensions.or
4+
import app.revanced.patcher.fingerprint.MethodFingerprint
5+
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithVersionReleaseFingerprint.indexOfBuildVersionReleaseInstruction
6+
import app.revanced.util.containsWideLiteralInstructionValue
7+
import app.revanced.util.getReference
8+
import app.revanced.util.indexOfFirstInstruction
9+
import com.android.tools.smali.dexlib2.AccessFlags
10+
import com.android.tools.smali.dexlib2.iface.Method
11+
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
12+
13+
internal object CreatePlayerRequestBodyWithVersionReleaseFingerprint : MethodFingerprint(
14+
returnType = "L",
15+
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
16+
parameters = listOf(),
17+
customFingerprint = { methodDef, _ ->
18+
methodDef.containsWideLiteralInstructionValue(1073741824) &&
19+
indexOfBuildVersionReleaseInstruction(methodDef) >= 0
20+
},
21+
) {
22+
fun indexOfBuildVersionReleaseInstruction(methodDef: Method) =
23+
methodDef.indexOfFirstInstruction {
24+
val reference = getReference<FieldReference>()
25+
reference?.definingClass == "Landroid/os/Build\$VERSION;" &&
26+
reference.name == "RELEASE" &&
27+
reference.type == "Ljava/lang/String;"
28+
}
29+
}
30+
31+

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,8 +1132,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
11321132
<string name="revanced_spoof_client_summary_off">Client is not spoofed\n\nVideo playback may not work</string>
11331133
<string name="revanced_spoof_client_user_dialog_message">Turning off this setting may cause video playback issues.</string>
11341134
<string name="revanced_spoof_client_use_ios_title">Spoof client to iOS</string>
1135-
<string name="revanced_spoof_client_use_ios_summary_on">Client is currently spoofed to iOS\n\nSide effects include:\n• No HDR video\n• Higher video qualities may be missing\n• Live streams cannot play as audio only\n• Live streams not available on Android 8.0</string>
1136-
<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• Kids videos do not playback\n• Paused videos can randomly resume\n• Low quality Shorts seekbar thumbnails\n• Download action button is always hidden\n• End screen cards are always hidden</string>
1135+
<string name="revanced_spoof_client_use_ios_summary_on">Client is currently spoofed to iOS\n\nSide effects include:\n• No HDR video\n• Higher video qualities may be missing\n• Live streams cannot play as audio only</string>
1136+
<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• Kids videos do not playback\n• Paused videos can randomly resume\n• Low quality Shorts seekbar thumbnails\n• Download action button is hidden\n• End screen cards are hidden</string>
11371137
<string name="revanced_spoof_client_storyboard_timeout">Spoof client thumbnails not available (API timed out)</string>
11381138
<string name="revanced_spoof_client_storyboard_io_exception">Spoof client thumbnails temporarily not available: %s</string>
11391139
</patch>

0 commit comments

Comments
 (0)