mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-30 22:00:19 +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:
parent
ab7f130b73
commit
92a317e312
@ -5,13 +5,9 @@ import android.text.TextUtils;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.protos.youtube.api.innertube.StreamingDataOuterClass$StreamingData;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import app.revanced.extension.shared.patches.BlockRequestPatch;
|
import app.revanced.extension.shared.patches.BlockRequestPatch;
|
||||||
@ -22,12 +18,6 @@ import app.revanced.extension.shared.utils.Utils;
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
||||||
/**
|
|
||||||
* Even if the default client is not iOS, videos that cannot be played on Android VR or Android TV will fall back to iOS.
|
|
||||||
* Do not add a dependency that checks whether the default client is iOS or not.
|
|
||||||
*/
|
|
||||||
private static final boolean SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH =
|
|
||||||
SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH.get();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key: video id
|
* Key: video id
|
||||||
@ -133,29 +123,13 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
|||||||
* <p>
|
* <p>
|
||||||
* Called after {@link #getStreamingData(String)}.
|
* Called after {@link #getStreamingData(String)}.
|
||||||
*/
|
*/
|
||||||
public static void setApproxDurationMs(String videoId, String approxDurationMsFieldName,
|
public static void setApproxDurationMs(String videoId, long approxDurationMs) {
|
||||||
StreamingDataOuterClass$StreamingData originalStreamingData, StreamingDataOuterClass$StreamingData spoofedStreamingData) {
|
if (approxDurationMs != Long.MAX_VALUE) {
|
||||||
if (SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH) {
|
approxDurationMsMap.put(videoId, approxDurationMs);
|
||||||
if (formatsIsEmpty(spoofedStreamingData)) {
|
Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs);
|
||||||
List<?> originalFormats = getFormatsFromStreamingData(originalStreamingData);
|
|
||||||
Long approxDurationMs = getApproxDurationMs(originalFormats, approxDurationMsFieldName);
|
|
||||||
if (approxDurationMs != null) {
|
|
||||||
approxDurationMsMap.put(videoId, approxDurationMs);
|
|
||||||
Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs);
|
|
||||||
} else {
|
|
||||||
Logger.printDebug(() -> "Ignoring as original approxDurationMs is not found, video id: " + videoId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger.printDebug(() -> "Ignoring as spoofed formats is not empty, video id: " + videoId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks like the initial value for the videoId field.
|
|
||||||
*/
|
|
||||||
private static final String MASKED_VIDEO_ID = "zzzzzzzzzzz";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* <p>
|
* <p>
|
||||||
@ -171,22 +145,16 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
|||||||
* <p>
|
* <p>
|
||||||
* Called after {@link #getStreamingData(String)}.
|
* Called after {@link #getStreamingData(String)}.
|
||||||
*/
|
*/
|
||||||
public static long getApproxDurationMsFromOriginalResponse(String videoId, long lengthMilliseconds) {
|
public static long getApproxDurationMs(String videoId) {
|
||||||
if (SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH) {
|
if (videoId != null) {
|
||||||
try {
|
final Long approxDurationMs = approxDurationMsMap.get(videoId);
|
||||||
if (videoId != null && !videoId.equals(MASKED_VIDEO_ID)) {
|
if (approxDurationMs != null) {
|
||||||
Long approxDurationMs = approxDurationMsMap.get(videoId);
|
Logger.printDebug(() -> "Replacing video length: " + approxDurationMs + " for videoId: " + videoId);
|
||||||
if (approxDurationMs != null) {
|
approxDurationMsMap.remove(videoId);
|
||||||
Logger.printDebug(() -> "Replacing video length from " + lengthMilliseconds + " to " + approxDurationMs + " , videoId: " + videoId);
|
return approxDurationMs;
|
||||||
approxDurationMsMap.remove(videoId);
|
|
||||||
return approxDurationMs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "getOriginalFormats failure", ex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lengthMilliseconds;
|
return Long.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -228,47 +196,4 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
|||||||
|
|
||||||
return videoFormat;
|
return videoFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utils
|
|
||||||
|
|
||||||
private static boolean formatsIsEmpty(StreamingDataOuterClass$StreamingData streamingData) {
|
|
||||||
List<?> formats = getFormatsFromStreamingData(streamingData);
|
|
||||||
return formats == null || formats.size() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<?> getFormatsFromStreamingData(StreamingDataOuterClass$StreamingData streamingData) {
|
|
||||||
try {
|
|
||||||
// Field e: 'formats'.
|
|
||||||
// Field name is always 'e', regardless of the client version.
|
|
||||||
Field field = streamingData.getClass().getDeclaredField("e");
|
|
||||||
field.setAccessible(true);
|
|
||||||
if (field.get(streamingData) instanceof List<?> list) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
|
||||||
Logger.printException(() -> "Reflection error accessing formats", ex);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Long getApproxDurationMs(List<?> list, String approxDurationMsFieldName) {
|
|
||||||
try {
|
|
||||||
if (list != null) {
|
|
||||||
var iterator = list.listIterator();
|
|
||||||
if (iterator.hasNext()) {
|
|
||||||
var formats = iterator.next();
|
|
||||||
Field field = formats.getClass().getDeclaredField(approxDurationMsFieldName);
|
|
||||||
field.setAccessible(true);
|
|
||||||
if (field.get(formats) instanceof Long approxDurationMs) {
|
|
||||||
return approxDurationMs;
|
|
||||||
} else {
|
|
||||||
Logger.printDebug(() -> "Field type is null: " + approxDurationMsFieldName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
|
||||||
Logger.printException(() -> "Reflection error accessing field: " + approxDurationMsFieldName, ex);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -64,10 +64,7 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
|||||||
|
|
||||||
private void updateUI() {
|
private void updateUI() {
|
||||||
final ClientType clientType = Settings.SPOOF_STREAMING_DATA_TYPE.get();
|
final ClientType clientType = Settings.SPOOF_STREAMING_DATA_TYPE.get();
|
||||||
final String summaryTextKey = clientType == ClientType.IOS &&
|
final String summaryTextKey = "revanced_spoof_streaming_data_side_effects_" + clientType.name().toLowerCase();
|
||||||
!Settings.SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH.get()
|
|
||||||
? "revanced_spoof_streaming_data_side_effects_ios_skip_sync_video_length"
|
|
||||||
: "revanced_spoof_streaming_data_side_effects_" + clientType.name().toLowerCase();
|
|
||||||
|
|
||||||
setSummary(str(summaryTextKey));
|
setSummary(str(summaryTextKey));
|
||||||
setEnabled(Settings.SPOOF_STREAMING_DATA.get());
|
setEnabled(Settings.SPOOF_STREAMING_DATA.get());
|
||||||
|
@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
|||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
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.instructions
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
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.matchOrThrow
|
||||||
import app.revanced.util.fingerprint.methodOrThrow
|
import app.revanced.util.fingerprint.methodOrThrow
|
||||||
import app.revanced.util.getReference
|
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.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
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 =
|
const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
"$SPOOF_PATH/SpoofStreamingDataPatch;"
|
"$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(
|
fun baseSpoofStreamingDataPatch(
|
||||||
block: BytecodePatchBuilder.() -> Unit = {},
|
block: BytecodePatchBuilder.() -> Unit = {},
|
||||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||||
@ -84,18 +81,51 @@ fun baseSpoofStreamingDataPatch(
|
|||||||
|
|
||||||
// region Replace the streaming data.
|
// region Replace the streaming data.
|
||||||
|
|
||||||
val approxDurationMsFieldName = formatStreamModelConstructorFingerprint.matchOrThrow().let {
|
val approxDurationMsReference = formatStreamModelConstructorFingerprint.matchOrThrow().let {
|
||||||
with(it.method) {
|
with (it.method) {
|
||||||
val approxDurationMsFieldIndex = it.patternMatch!!.startIndex
|
getInstruction<ReferenceInstruction>(it.patternMatch!!.startIndex).reference
|
||||||
(getInstruction<ReferenceInstruction>(approxDurationMsFieldIndex).reference as FieldReference).name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
createStreamingDataFingerprint.matchOrThrow(createStreamingDataParentFingerprint)
|
||||||
.let { result ->
|
.let { result ->
|
||||||
result.method.apply {
|
result.method.apply {
|
||||||
val setStreamDataMethodName = "patch_setStreamingData"
|
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 setStreamingDataIndex = result.patternMatch!!.startIndex
|
||||||
val setStreamingDataField =
|
val setStreamingDataField =
|
||||||
getInstruction(setStreamingDataIndex).getReference<FieldReference>()
|
getInstruction(setStreamingDataIndex).getReference<FieldReference>()
|
||||||
@ -124,7 +154,7 @@ fun baseSpoofStreamingDataPatch(
|
|||||||
"$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
|
"$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
|
||||||
)
|
)
|
||||||
|
|
||||||
result.classDef.methods.add(
|
resultClassDef.methods.add(
|
||||||
ImmutableMethod(
|
ImmutableMethod(
|
||||||
resultMethodType,
|
resultMethodType,
|
||||||
setStreamDataMethodName,
|
setStreamDataMethodName,
|
||||||
@ -167,58 +197,83 @@ fun baseSpoofStreamingDataPatch(
|
|||||||
iget-object v6, v5, $getStreamingDataField
|
iget-object v6, v5, $getStreamingDataField
|
||||||
if-eqz v6, :disabled
|
if-eqz v6, :disabled
|
||||||
|
|
||||||
# Get original streaming data.
|
# Caculate approxDurationMs.
|
||||||
iget-object v0, p0, $setStreamingDataField
|
invoke-direct { p0, v2 }, $resultMethodType->$calcApproxDurationMsMethodName(Ljava/lang/String;)V
|
||||||
|
|
||||||
# Set spoofed streaming data.
|
# Set spoofed streaming data.
|
||||||
iput-object v6, p0, $setStreamingDataField
|
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
|
:disabled
|
||||||
return-void
|
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
|
// endregion
|
||||||
|
|
||||||
// region Remove /videoplayback request body to fix playback.
|
// 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.AccessFlags
|
||||||
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
|
||||||
|
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
|
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(
|
internal val buildMediaDataSourceFingerprint = legacyFingerprint(
|
||||||
name = "buildMediaDataSourceFingerprint",
|
name = "buildMediaDataSourceFingerprint",
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||||
@ -108,11 +114,28 @@ internal val videoStreamingDataConstructorFingerprint = legacyFingerprint(
|
|||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||||
returnType = "V",
|
returnType = "V",
|
||||||
customFingerprint = { method, _ ->
|
customFingerprint = { method, _ ->
|
||||||
indexOfFormatStreamModelInitInstruction(method) >= 0 &&
|
indexOfGetFormatsFieldInstruction(method) >= 0 &&
|
||||||
indexOfToMillisInstruction(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) =
|
internal fun indexOfFormatStreamModelInitInstruction(method: Method) =
|
||||||
method.indexOfFirstInstruction {
|
method.indexOfFirstInstruction {
|
||||||
val reference = getReference<MethodReference>()
|
val reference = getReference<MethodReference>()
|
||||||
@ -121,13 +144,6 @@ internal fun indexOfFormatStreamModelInitInstruction(method: Method) =
|
|||||||
reference.parameterTypes.size > 1
|
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, this class is 'Lcom/google/android/libraries/youtube/innertube/model/media/VideoStreamingData;'
|
||||||
* On YouTube Music, class names are obfuscated.
|
* 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_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_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">• 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.
|
<string name="revanced_spoof_streaming_data_side_effects_android_unplugged">"• Audio track menu is missing.
|
||||||
• Stable volume is not available."</string>
|
• Stable volume is not available."</string>
|
||||||
<string name="revanced_spoof_streaming_data_side_effects_android_vr">"• Audio track menu is missing.
|
<string name="revanced_spoof_streaming_data_side_effects_android_vr">"• Audio track menu is missing.
|
||||||
• Stable volume is not available."</string>
|
• 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_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_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>
|
<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" />
|
<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" />
|
<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" />
|
<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" />
|
<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 -->
|
</PreferenceScreen>SETTINGS: SPOOF_STREAMING_DATA -->
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user