mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-12 05:07:41 +02:00
refactor(Spoof streaming data): No longer using Java reflection to improve performance (#113)
* refactor(Spoof Streaming Data): Move the parser to bytecode * Add comment * Increase cache limit * Fix typo * Revert changes * chore: Simplify * chore: Simplify --------- Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com>
This commit is contained in:
@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||
@ -22,7 +23,7 @@ import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
@ -37,10 +38,6 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$SPOOF_PATH/SpoofStreamingDataPatch;"
|
||||
|
||||
// In YouTube 17.34.36, this class is obfuscated.
|
||||
const val STREAMING_DATA_INTERFACE =
|
||||
"Lcom/google/protos/youtube/api/innertube/StreamingDataOuterClass${'$'}StreamingData;"
|
||||
|
||||
fun baseSpoofStreamingDataPatch(
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||
@ -84,18 +81,51 @@ fun baseSpoofStreamingDataPatch(
|
||||
|
||||
// region Replace the streaming data.
|
||||
|
||||
val approxDurationMsFieldName = formatStreamModelConstructorFingerprint.matchOrThrow().let {
|
||||
with(it.method) {
|
||||
val approxDurationMsFieldIndex = it.patternMatch!!.startIndex
|
||||
(getInstruction<ReferenceInstruction>(approxDurationMsFieldIndex).reference as FieldReference).name
|
||||
val approxDurationMsReference = formatStreamModelConstructorFingerprint.matchOrThrow().let {
|
||||
with (it.method) {
|
||||
getInstruction<ReferenceInstruction>(it.patternMatch!!.startIndex).reference
|
||||
}
|
||||
}
|
||||
|
||||
val streamingDataFormatsReference = with(videoStreamingDataConstructorFingerprint.methodOrThrow(videoStreamingDataToStringFingerprint)) {
|
||||
val getFormatsFieldIndex = indexOfGetFormatsFieldInstruction(this)
|
||||
val longMaxValueIndex = indexOfLongMaxValueInstruction(this, getFormatsFieldIndex)
|
||||
val longMaxValueRegister = getInstruction<OneRegisterInstruction>(longMaxValueIndex).registerA
|
||||
val videoIdIndex =
|
||||
indexOfFirstInstructionOrThrow(longMaxValueIndex) {
|
||||
val reference = getReference<FieldReference>()
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
reference?.type == "Ljava/lang/String;" &&
|
||||
reference.definingClass == definingClass
|
||||
}
|
||||
|
||||
val definingClassRegister =
|
||||
getInstruction<TwoRegisterInstruction>(videoIdIndex).registerB
|
||||
val videoIdReference =
|
||||
getInstruction<ReferenceInstruction>(videoIdIndex).reference
|
||||
|
||||
addInstructions(
|
||||
longMaxValueIndex + 1, """
|
||||
# Get video id.
|
||||
iget-object v$longMaxValueRegister, v$definingClassRegister, $videoIdReference
|
||||
|
||||
# Override approxDurationMs.
|
||||
invoke-static { v$longMaxValueRegister }, $EXTENSION_CLASS_DESCRIPTOR->getApproxDurationMs(Ljava/lang/String;)J
|
||||
move-result-wide v$longMaxValueRegister
|
||||
"""
|
||||
)
|
||||
removeInstruction(longMaxValueIndex)
|
||||
|
||||
getInstruction<ReferenceInstruction>(getFormatsFieldIndex).reference
|
||||
}
|
||||
|
||||
createStreamingDataFingerprint.matchOrThrow(createStreamingDataParentFingerprint)
|
||||
.let { result ->
|
||||
result.method.apply {
|
||||
val setStreamDataMethodName = "patch_setStreamingData"
|
||||
val resultMethodType = result.classDef.type
|
||||
val calcApproxDurationMsMethodName = "patch_calcApproxDurationMs"
|
||||
val resultClassDef = result.classDef
|
||||
val resultMethodType = resultClassDef.type
|
||||
val setStreamingDataIndex = result.patternMatch!!.startIndex
|
||||
val setStreamingDataField =
|
||||
getInstruction(setStreamingDataIndex).getReference<FieldReference>()
|
||||
@ -124,7 +154,7 @@ fun baseSpoofStreamingDataPatch(
|
||||
"$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
|
||||
)
|
||||
|
||||
result.classDef.methods.add(
|
||||
resultClassDef.methods.add(
|
||||
ImmutableMethod(
|
||||
resultMethodType,
|
||||
setStreamDataMethodName,
|
||||
@ -167,58 +197,83 @@ fun baseSpoofStreamingDataPatch(
|
||||
iget-object v6, v5, $getStreamingDataField
|
||||
if-eqz v6, :disabled
|
||||
|
||||
# Get original streaming data.
|
||||
iget-object v0, p0, $setStreamingDataField
|
||||
# Caculate approxDurationMs.
|
||||
invoke-direct { p0, v2 }, $resultMethodType->$calcApproxDurationMsMethodName(Ljava/lang/String;)V
|
||||
|
||||
# Set spoofed streaming data.
|
||||
iput-object v6, p0, $setStreamingDataField
|
||||
|
||||
# Get video length from original streaming data and save to extension.
|
||||
const-string v5, "$approxDurationMsFieldName"
|
||||
invoke-static { v2, v5, v0, v6 }, $EXTENSION_CLASS_DESCRIPTOR->setApproxDurationMs(Ljava/lang/String;Ljava/lang/String;$STREAMING_DATA_INTERFACE$STREAMING_DATA_INTERFACE)V
|
||||
|
||||
:disabled
|
||||
return-void
|
||||
""",
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
resultClassDef.methods.add(
|
||||
ImmutableMethod(
|
||||
resultMethodType,
|
||||
calcApproxDurationMsMethodName,
|
||||
listOf(
|
||||
ImmutableMethodParameter(
|
||||
"Ljava/lang/String;",
|
||||
annotations,
|
||||
"videoId"
|
||||
)
|
||||
),
|
||||
"V",
|
||||
AccessFlags.PRIVATE.value or AccessFlags.FINAL.value,
|
||||
annotations,
|
||||
null,
|
||||
MutableMethodImplementation(12),
|
||||
).toMutable().apply {
|
||||
addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
# Get video format list.
|
||||
iget-object v0, p0, $setStreamingDataField
|
||||
iget-object v0, v0, $streamingDataFormatsReference
|
||||
invoke-interface {v0}, Ljava/util/List;->iterator()Ljava/util/Iterator;
|
||||
move-result-object v0
|
||||
|
||||
# Initialize approxDurationMs field.
|
||||
const-wide v1, 0x7fffffffffffffffL
|
||||
|
||||
:loop
|
||||
# Loop over all video formats to get the approxDurationMs
|
||||
invoke-interface {v0}, Ljava/util/Iterator;->hasNext()Z
|
||||
move-result v3
|
||||
const-wide/16 v4, 0x0
|
||||
|
||||
if-eqz v3, :exit
|
||||
invoke-interface {v0}, Ljava/util/Iterator;->next()Ljava/lang/Object;
|
||||
move-result-object v3
|
||||
check-cast v3, ${(approxDurationMsReference as FieldReference).definingClass}
|
||||
|
||||
# Get approxDurationMs from format
|
||||
iget-wide v6, v3, $approxDurationMsReference
|
||||
|
||||
# Compare with zero to make sure approxDurationMs is not negative
|
||||
cmp-long v8, v6, v4
|
||||
if-lez v8, :loop
|
||||
|
||||
# Only use the min value of approxDurationMs
|
||||
invoke-static {v1, v2, v6, v7}, Ljava/lang/Math;->min(JJ)J
|
||||
move-result-wide v1
|
||||
goto :loop
|
||||
|
||||
:exit
|
||||
# Save approxDurationMs to integrations
|
||||
invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->setApproxDurationMs(Ljava/lang/String;J)V
|
||||
|
||||
return-void
|
||||
""",
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
videoStreamingDataConstructorFingerprint.methodOrThrow(videoStreamingDataToStringFingerprint)
|
||||
.apply {
|
||||
val formatStreamModelInitIndex = indexOfFormatStreamModelInitInstruction(this)
|
||||
val videoIdIndex =
|
||||
indexOfFirstInstructionReversedOrThrow(formatStreamModelInitIndex) {
|
||||
val reference = getReference<FieldReference>()
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
reference?.type == "Ljava/lang/String;" &&
|
||||
reference.definingClass == definingClass
|
||||
}
|
||||
val definingClassRegister =
|
||||
getInstruction<TwoRegisterInstruction>(videoIdIndex).registerB
|
||||
val videoIdReference =
|
||||
getInstruction<ReferenceInstruction>(videoIdIndex).reference
|
||||
|
||||
val toMillisIndex = indexOfToMillisInstruction(this)
|
||||
val freeRegister =
|
||||
getInstruction<FiveRegisterInstruction>(toMillisIndex).registerC
|
||||
val lengthMillisecondsRegister =
|
||||
getInstruction<OneRegisterInstruction>(toMillisIndex + 1).registerA
|
||||
|
||||
addInstructions(
|
||||
toMillisIndex + 2, """
|
||||
# Get video id.
|
||||
iget-object v$freeRegister, v$definingClassRegister, $videoIdReference
|
||||
|
||||
# Override streaming data formats.
|
||||
invoke-static { v$freeRegister, v$lengthMillisecondsRegister, v${lengthMillisecondsRegister + 1} }, $EXTENSION_CLASS_DESCRIPTOR->getApproxDurationMsFromOriginalResponse(Ljava/lang/String;J)J
|
||||
move-result-wide v$lengthMillisecondsRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Remove /videoplayback request body to fix playback.
|
||||
|
@ -7,8 +7,14 @@ import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
// In YouTube 17.34.36, this class is obfuscated.
|
||||
const val STREAMING_DATA_INTERFACE =
|
||||
"Lcom/google/protos/youtube/api/innertube/StreamingDataOuterClass${'$'}StreamingData;"
|
||||
|
||||
internal val buildMediaDataSourceFingerprint = legacyFingerprint(
|
||||
name = "buildMediaDataSourceFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
@ -108,11 +114,28 @@ internal val videoStreamingDataConstructorFingerprint = legacyFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
returnType = "V",
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfFormatStreamModelInitInstruction(method) >= 0 &&
|
||||
indexOfToMillisInstruction(method) >= 0
|
||||
indexOfGetFormatsFieldInstruction(method) >= 0 &&
|
||||
indexOfLongMaxValueInstruction(method) >= 0 &&
|
||||
indexOfFormatStreamModelInitInstruction(method) >= 0
|
||||
},
|
||||
)
|
||||
|
||||
internal fun indexOfGetFormatsFieldInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<FieldReference>()
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
reference?.definingClass == STREAMING_DATA_INTERFACE &&
|
||||
// Field e: 'formats'.
|
||||
// Field name is always 'e', regardless of the client version.
|
||||
reference.name == "e" &&
|
||||
reference.type.startsWith("L")
|
||||
}
|
||||
|
||||
internal fun indexOfLongMaxValueInstruction(method: Method, index: Int = 0) =
|
||||
method.indexOfFirstInstruction(index) {
|
||||
(this as? WideLiteralInstruction)?.wideLiteral == Long.MAX_VALUE
|
||||
}
|
||||
|
||||
internal fun indexOfFormatStreamModelInitInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
@ -121,13 +144,6 @@ internal fun indexOfFormatStreamModelInitInstruction(method: Method) =
|
||||
reference.parameterTypes.size > 1
|
||||
}
|
||||
|
||||
internal fun indexOfToMillisInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference?.name == "toMillis"
|
||||
}
|
||||
|
||||
/**
|
||||
* On YouTube, this class is 'Lcom/google/android/libraries/youtube/innertube/model/media/VideoStreamingData;'
|
||||
* On YouTube Music, class names are obfuscated.
|
||||
|
@ -1918,16 +1918,10 @@ Tap the continue button and allow optimization changes."</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_android_vr">Android VR</string>
|
||||
<string name="revanced_spoof_streaming_data_side_effects_title">Spoofing side effects</string>
|
||||
<string name="revanced_spoof_streaming_data_side_effects_ios">• Not yet found.</string>
|
||||
<string name="revanced_spoof_streaming_data_side_effects_ios_skip_sync_video_length">• Videos may end 1 second early.</string>
|
||||
<string name="revanced_spoof_streaming_data_side_effects_android_unplugged">"• Audio track menu is missing.
|
||||
• Stable volume is not available."</string>
|
||||
<string name="revanced_spoof_streaming_data_side_effects_android_vr">"• Audio track menu is missing.
|
||||
• Stable volume is not available."</string>
|
||||
<string name="revanced_spoof_streaming_data_sync_video_length_title">Sync video length before playback</string>
|
||||
<string name="revanced_spoof_streaming_data_sync_video_length_summary_on">"Video length is synced before playback.
|
||||
Video length is exact value."</string>
|
||||
<string name="revanced_spoof_streaming_data_sync_video_length_summary_off">"Video length is not synced before playback.
|
||||
Video length may be a rounded value."</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Show in Stats for nerds</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_on">Client used to fetch streaming data is shown in Stats for nerds.</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_off">Client used to fetch streaming data is hidden in Stats for nerds.</string>
|
||||
|
@ -793,7 +793,6 @@
|
||||
<SwitchPreference android:title="@string/revanced_spoof_streaming_data_title" android:key="revanced_spoof_streaming_data" android:summaryOn="@string/revanced_spoof_streaming_data_summary_on" android:summaryOff="@string/revanced_spoof_streaming_data_summary_off" />
|
||||
<ListPreference android:entries="@array/revanced_spoof_streaming_data_type_entries" android:title="@string/revanced_spoof_streaming_data_type_title" android:key="revanced_spoof_streaming_data_type" android:entryValues="@array/revanced_spoof_streaming_data_type_entry_values" android:dependency="revanced_spoof_streaming_data" />
|
||||
<app.revanced.extension.youtube.settings.preference.SpoofStreamingDataSideEffectsPreference android:title="@string/revanced_spoof_streaming_data_side_effects_title" />
|
||||
<SwitchPreference android:title="@string/revanced_spoof_streaming_data_sync_video_length_title" android:key="revanced_spoof_streaming_data_sync_video_length" android:summaryOn="@string/revanced_spoof_streaming_data_sync_video_length_summary_on" android:summaryOff="@string/revanced_spoof_streaming_data_sync_video_length_summary_off" android:dependency="revanced_spoof_streaming_data" />
|
||||
<SwitchPreference android:title="@string/revanced_spoof_streaming_data_stats_for_nerds_title" android:key="revanced_spoof_streaming_data_stats_for_nerds" android:summaryOn="@string/revanced_spoof_streaming_data_stats_for_nerds_summary_on" android:summaryOff="@string/revanced_spoof_streaming_data_stats_for_nerds_summary_off" android:dependency="revanced_spoof_streaming_data" />
|
||||
</PreferenceScreen>SETTINGS: SPOOF_STREAMING_DATA -->
|
||||
|
||||
|
Reference in New Issue
Block a user