mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-04-29 22:24:31 +02:00
refactor(Spoof streaming data): Instead of replacing the entire array StreamingData.formats
, replace only the approxDurationMs
field
This commit is contained in:
parent
3c8e61c850
commit
a040b78927
@ -9,9 +9,10 @@ import com.google.protos.youtube.api.innertube.StreamingDataOuterClass$Streaming
|
|||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.patches.BlockRequestPatch;
|
import app.revanced.extension.shared.patches.BlockRequestPatch;
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.StreamingDataRequest;
|
import app.revanced.extension.shared.patches.spoof.requests.StreamingDataRequest;
|
||||||
@ -29,10 +30,18 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
|||||||
SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH.get();
|
SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key: videoId.
|
* Key: video id
|
||||||
* Value: original [streamingData.formats].
|
* Value: original video length [streamingData.formats.approxDurationMs]
|
||||||
*/
|
*/
|
||||||
private static final ConcurrentHashMap<String, List<?>> formatsMap = new ConcurrentHashMap<>(20, 0.8f);
|
private static final Map<String, Long> approxDurationMsMap = Collections.synchronizedMap(
|
||||||
|
new LinkedHashMap<>(100) {
|
||||||
|
private static final int CACHE_LIMIT = 50;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Entry eldest) {
|
||||||
|
return size() > CACHE_LIMIT; // Evict the oldest entry if over the cache limit.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
@ -120,34 +129,26 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
* <p>
|
* <p>
|
||||||
* If spoofed [streamingData.formats] is empty,
|
* If spoofed [streamingData.formats] is empty,
|
||||||
* Put the original [streamingData.formats] into the HashMap.
|
* Put the original [streamingData.formats.approxDurationMs] into the HashMap.
|
||||||
* <p>
|
* <p>
|
||||||
* Called after {@link #getStreamingData(String)}.
|
* Called after {@link #getStreamingData(String)}.
|
||||||
*/
|
*/
|
||||||
public static void setFormats(String videoId, StreamingDataOuterClass$StreamingData originalStreamingData, StreamingDataOuterClass$StreamingData spoofed) {
|
public static void setApproxDurationMs(String videoId, String approxDurationMsFieldName,
|
||||||
if (SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH && formatsIsEmpty(spoofed)) {
|
StreamingDataOuterClass$StreamingData originalStreamingData, StreamingDataOuterClass$StreamingData spoofedStreamingData) {
|
||||||
formatsMap.put(videoId, getFormatsFromStreamingData(originalStreamingData));
|
if (SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH) {
|
||||||
Logger.printDebug(() -> "New formats video id: " + videoId);
|
if (formatsIsEmpty(spoofedStreamingData)) {
|
||||||
}
|
List<?> originalFormats = getFormatsFromStreamingData(originalStreamingData);
|
||||||
}
|
Long approxDurationMs = getApproxDurationMs(originalFormats, approxDurationMsFieldName);
|
||||||
|
if (approxDurationMs != null) {
|
||||||
private static boolean formatsIsEmpty(StreamingDataOuterClass$StreamingData streamingData) {
|
approxDurationMsMap.put(videoId, approxDurationMs);
|
||||||
List<?> formats = getFormatsFromStreamingData(streamingData);
|
Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs);
|
||||||
return formats == null || formats.size() == 0;
|
} else {
|
||||||
}
|
Logger.printDebug(() -> "Ignoring as original approxDurationMs is not found, video id: " + videoId);
|
||||||
|
}
|
||||||
private static List<?> getFormatsFromStreamingData(StreamingDataOuterClass$StreamingData streamingData) {
|
} else {
|
||||||
try {
|
Logger.printDebug(() -> "Ignoring as spoofed formats is not empty, video id: " + videoId);
|
||||||
// Field e: 'formats'.
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -170,21 +171,21 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
|||||||
* <p>
|
* <p>
|
||||||
* Called after {@link #getStreamingData(String)}.
|
* Called after {@link #getStreamingData(String)}.
|
||||||
*/
|
*/
|
||||||
public static List<?> getOriginalFormats(String videoId, List<?> spoofedFormats) {
|
public static long getApproxDurationMsFromOriginalResponse(String videoId, long lengthMilliseconds) {
|
||||||
if (SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH) {
|
if (SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH) {
|
||||||
try {
|
try {
|
||||||
if (videoId != null && !videoId.equals(MASKED_VIDEO_ID) && spoofedFormats.size() == 0) {
|
if (videoId != null && !videoId.equals(MASKED_VIDEO_ID)) {
|
||||||
List<?> androidFormats = formatsMap.get(videoId);
|
Long approxDurationMs = approxDurationMsMap.get(videoId);
|
||||||
if (androidFormats != null) {
|
if (approxDurationMs != null) {
|
||||||
Logger.printDebug(() -> "Overriding iOS formats to original formats: " + videoId);
|
Logger.printDebug(() -> "Replacing video length from " + lengthMilliseconds + " to " + approxDurationMs + " , videoId: " + videoId);
|
||||||
return androidFormats;
|
return approxDurationMs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "getOriginalFormats failure", ex);
|
Logger.printException(() -> "getOriginalFormats failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return spoofedFormats;
|
return lengthMilliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -226,4 +227,47 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@ package app.revanced.extension.shared.patches.spoof.requests;
|
|||||||
|
|
||||||
import static app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_STREAMING_DATA;
|
import static app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_STREAMING_DATA;
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import app.revanced.patches.music.utils.settings.CategoryType
|
|||||||
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||||
import app.revanced.patches.music.utils.settings.addSwitchPreference
|
import app.revanced.patches.music.utils.settings.addSwitchPreference
|
||||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||||
|
import app.revanced.patches.shared.formatStreamModelConstructorFingerprint
|
||||||
import app.revanced.util.fingerprint.matchOrThrow
|
import app.revanced.util.fingerprint.matchOrThrow
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||||
|
|
||||||
|
@ -5,17 +5,6 @@ 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
|
||||||
|
|
||||||
/**
|
|
||||||
* On YouTube, this class is 'Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;'
|
|
||||||
* On YouTube Music, class names are obfuscated.
|
|
||||||
*/
|
|
||||||
internal val formatStreamModelConstructorFingerprint = legacyFingerprint(
|
|
||||||
name = "formatStreamModelConstructorFingerprint",
|
|
||||||
returnType = "V",
|
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
|
||||||
literals = listOf(45374643L),
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YouTube Music 7.13.52 ~
|
* YouTube Music 7.13.52 ~
|
||||||
*/
|
*/
|
||||||
|
@ -35,6 +35,21 @@ private fun Method.indexOfFieldReference(string: String) = indexOfFirstInstructi
|
|||||||
reference.toString() == string
|
reference.toString() == string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On YouTube, this class is 'Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;'
|
||||||
|
* On YouTube Music, class names are obfuscated.
|
||||||
|
*/
|
||||||
|
internal val formatStreamModelConstructorFingerprint = legacyFingerprint(
|
||||||
|
name = "formatStreamModelConstructorFingerprint",
|
||||||
|
returnType = "V",
|
||||||
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||||
|
opcodes = listOf(
|
||||||
|
Opcode.IGET_WIDE,
|
||||||
|
Opcode.IPUT_WIDE,
|
||||||
|
),
|
||||||
|
literals = listOf(45374643L),
|
||||||
|
)
|
||||||
|
|
||||||
internal val mdxPlayerDirectorSetVideoStageFingerprint = legacyFingerprint(
|
internal val mdxPlayerDirectorSetVideoStageFingerprint = legacyFingerprint(
|
||||||
name = "mdxPlayerDirectorSetVideoStageFingerprint",
|
name = "mdxPlayerDirectorSetVideoStageFingerprint",
|
||||||
strings = listOf("MdxDirector setVideoStage ad should be null when videoStage is not an Ad state ")
|
strings = listOf("MdxDirector setVideoStage ad should be null when videoStage is not an Ad state ")
|
||||||
|
@ -12,13 +12,13 @@ import app.revanced.patcher.patch.bytecodePatch
|
|||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
import app.revanced.patches.shared.blockrequest.blockRequestPatch
|
import app.revanced.patches.shared.blockrequest.blockRequestPatch
|
||||||
import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
|
import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
|
||||||
|
import app.revanced.patches.shared.formatStreamModelConstructorFingerprint
|
||||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||||
import app.revanced.util.fingerprint.definingClassOrThrow
|
import app.revanced.util.fingerprint.definingClassOrThrow
|
||||||
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
|
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.indexOfFirstInstructionOrThrow
|
|
||||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||||
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
|
||||||
@ -81,6 +81,13 @@ fun baseSpoofStreamingDataPatch(
|
|||||||
|
|
||||||
// region Replace the streaming data.
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createStreamingDataFingerprint.matchOrThrow(createStreamingDataParentFingerprint)
|
createStreamingDataFingerprint.matchOrThrow(createStreamingDataParentFingerprint)
|
||||||
.let { result ->
|
.let { result ->
|
||||||
result.method.apply {
|
result.method.apply {
|
||||||
@ -134,40 +141,42 @@ fun baseSpoofStreamingDataPatch(
|
|||||||
addInstructionsWithLabels(
|
addInstructionsWithLabels(
|
||||||
0,
|
0,
|
||||||
"""
|
"""
|
||||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled()Z
|
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled()Z
|
||||||
move-result v0
|
move-result v0
|
||||||
if-eqz v0, :disabled
|
if-eqz v0, :disabled
|
||||||
|
|
||||||
# Get video id.
|
# Get video id.
|
||||||
iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String;
|
iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String;
|
||||||
if-eqz v2, :disabled
|
if-eqz v2, :disabled
|
||||||
|
|
||||||
# Get streaming data.
|
# Get streaming data.
|
||||||
invoke-static { v2 }, $EXTENSION_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer;
|
invoke-static { v2 }, $EXTENSION_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer;
|
||||||
move-result-object v3
|
move-result-object v3
|
||||||
if-eqz v3, :disabled
|
|
||||||
|
if-eqz v3, :disabled
|
||||||
# Parse streaming data.
|
|
||||||
sget-object v4, $playerProtoClass->a:$playerProtoClass
|
# Parse streaming data.
|
||||||
invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass
|
sget-object v4, $playerProtoClass->a:$playerProtoClass
|
||||||
move-result-object v5
|
invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass
|
||||||
check-cast v5, $playerProtoClass
|
move-result-object v5
|
||||||
|
check-cast v5, $playerProtoClass
|
||||||
iget-object v6, v5, $getStreamingDataField
|
|
||||||
if-eqz v6, :disabled
|
iget-object v6, v5, $getStreamingDataField
|
||||||
|
if-eqz v6, :disabled
|
||||||
# Get original streaming data.
|
|
||||||
iget-object v0, p0, $setStreamingDataField
|
# Get original streaming data.
|
||||||
|
iget-object v0, p0, $setStreamingDataField
|
||||||
# Set spoofed streaming data.
|
|
||||||
iput-object v6, p0, $setStreamingDataField
|
# Set spoofed streaming data.
|
||||||
|
iput-object v6, p0, $setStreamingDataField
|
||||||
# Set original streaming data formats.
|
|
||||||
invoke-static { v2, v0, v6 }, $EXTENSION_CLASS_DESCRIPTOR->setFormats(Ljava/lang/String;$STREAMING_DATA_INTERFACE$STREAMING_DATA_INTERFACE)V
|
# Get video length from original streaming data and save to extension.
|
||||||
|
const-string v5, "$approxDurationMsFieldName"
|
||||||
:disabled
|
invoke-static { v2, v5, v0, v6 }, $EXTENSION_CLASS_DESCRIPTOR->setApproxDurationMs(Ljava/lang/String;Ljava/lang/String;$STREAMING_DATA_INTERFACE$STREAMING_DATA_INTERFACE)V
|
||||||
return-void
|
|
||||||
""",
|
:disabled
|
||||||
|
return-void
|
||||||
|
""",
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -188,24 +197,21 @@ fun baseSpoofStreamingDataPatch(
|
|||||||
getInstruction<TwoRegisterInstruction>(videoIdIndex).registerB
|
getInstruction<TwoRegisterInstruction>(videoIdIndex).registerB
|
||||||
val videoIdReference =
|
val videoIdReference =
|
||||||
getInstruction<ReferenceInstruction>(videoIdIndex).reference
|
getInstruction<ReferenceInstruction>(videoIdIndex).reference
|
||||||
val formatsIndex = indexOfFirstInstructionReversedOrThrow(videoIdIndex) {
|
|
||||||
opcode == Opcode.IGET_OBJECT &&
|
|
||||||
getReference<FieldReference>()?.definingClass == STREAMING_DATA_INTERFACE
|
|
||||||
}
|
|
||||||
val freeRegister = getInstruction<OneRegisterInstruction>(
|
|
||||||
indexOfFirstInstructionOrThrow(formatsIndex, Opcode.CONST_WIDE)
|
|
||||||
).registerA
|
|
||||||
|
|
||||||
val audioCodecListRegister = getInstruction<TwoRegisterInstruction>(formatsIndex).registerA
|
val toMillisIndex = indexOfToMillisInstruction(this)
|
||||||
|
val freeRegister =
|
||||||
|
getInstruction<FiveRegisterInstruction>(toMillisIndex).registerC
|
||||||
|
val lengthMillisecondsRegister =
|
||||||
|
getInstruction<OneRegisterInstruction>(toMillisIndex + 1).registerA
|
||||||
|
|
||||||
addInstructions(
|
addInstructions(
|
||||||
formatsIndex + 1, """
|
toMillisIndex + 2, """
|
||||||
# Get video id.
|
# Get video id.
|
||||||
iget-object v$freeRegister, v$definingClassRegister, $videoIdReference
|
iget-object v$freeRegister, v$definingClassRegister, $videoIdReference
|
||||||
|
|
||||||
# Override streaming data formats.
|
# Override streaming data formats.
|
||||||
invoke-static { v$freeRegister, v$audioCodecListRegister }, $EXTENSION_CLASS_DESCRIPTOR->getOriginalFormats(Ljava/lang/String;Ljava/util/List;)Ljava/util/List;
|
invoke-static { v$freeRegister, v$lengthMillisecondsRegister, v${lengthMillisecondsRegister + 1} }, $EXTENSION_CLASS_DESCRIPTOR->getApproxDurationMsFromOriginalResponse(Ljava/lang/String;J)J
|
||||||
move-result-object v$audioCodecListRegister
|
move-result-wide v$lengthMillisecondsRegister
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,8 @@ 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
|
indexOfFormatStreamModelInitInstruction(method) >= 0 &&
|
||||||
|
indexOfToMillisInstruction(method) >= 0
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -120,6 +121,17 @@ 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 Music, class names are obfuscated.
|
||||||
|
*/
|
||||||
internal val videoStreamingDataToStringFingerprint = legacyFingerprint(
|
internal val videoStreamingDataToStringFingerprint = legacyFingerprint(
|
||||||
name = "videoStreamingDataToStringFingerprint",
|
name = "videoStreamingDataToStringFingerprint",
|
||||||
returnType = "Ljava/lang/String;",
|
returnType = "Ljava/lang/String;",
|
||||||
@ -135,7 +147,6 @@ internal const val HLS_CURRENT_TIME_FEATURE_FLAG = 45355374L
|
|||||||
|
|
||||||
internal val hlsCurrentTimeFingerprint = legacyFingerprint(
|
internal val hlsCurrentTimeFingerprint = legacyFingerprint(
|
||||||
name = "hlsCurrentTimeFingerprint",
|
name = "hlsCurrentTimeFingerprint",
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
|
||||||
parameters = listOf("Z", "L"),
|
parameters = listOf("Z", "L"),
|
||||||
literals = listOf(HLS_CURRENT_TIME_FEATURE_FLAG),
|
literals = listOf(HLS_CURRENT_TIME_FEATURE_FLAG),
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user