mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-04 16:44:29 +02:00
feat(YouTube Music - Spoof client): Add support for latest versions, Remove 'Spoof streaming data' patch
- 'Spoof client' patch now supports YouTube Music 7.25.53+ - 'Spoof client' patch now spoofs as many innerTube contexts as possible, just like the 'Spoof streaming data' patch - 'Spoof streaming data' patch is no longer a shared patch - Nevertheless, 'BlockRequestPatch' is still a shared patch, so each setting is located in a shared path (BaseSettings) - In YouTube Music 7.16.53 or earlier, 'iOS YouTube Music 6.21' is the only client that does not require 'BlockRequestPatch' - 'iOS YouTube Music 7.04' supports opus codec when 'Enable OPUS codec' is turned on
This commit is contained in:
parent
1151a9a5be
commit
0d2d45a597
@ -1,118 +0,0 @@
|
||||
package app.revanced.extension.music.patches.misc.client;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
public class AppClient {
|
||||
|
||||
// Audio codec is MP4A.
|
||||
private static final String CLIENT_VERSION_ANDROID_MUSIC_4_27 = "4.27.53";
|
||||
|
||||
// Audio codec is OPUS.
|
||||
private static final String CLIENT_VERSION_ANDROID_MUSIC_5_29 = "5.29.53";
|
||||
|
||||
private static final String PACKAGE_NAME_ANDROID_MUSIC = "com.google.android.apps.youtube.music";
|
||||
private static final String DEVICE_MODEL_ANDROID_MUSIC = Build.MODEL;
|
||||
private static final String OS_VERSION_ANDROID_MUSIC = Build.VERSION.RELEASE;
|
||||
|
||||
// Audio codec is MP4A.
|
||||
private static final String CLIENT_VERSION_IOS_MUSIC_6_21 = "6.21";
|
||||
|
||||
// Audio codec is OPUS.
|
||||
private static final String CLIENT_VERSION_IOS_MUSIC_7_04 = "7.04";
|
||||
|
||||
private static final String PACKAGE_NAME_IOS_MUSIC = "com.google.ios.youtubemusic";
|
||||
private static final String DEVICE_MODEL_IOS_MUSIC = "iPhone14,3";
|
||||
private static final String OS_VERSION_IOS_MUSIC = "15.7.1.19H117";
|
||||
private static final String USER_AGENT_VERSION_IOS_MUSIC = "15_7_1";
|
||||
|
||||
private AppClient() {
|
||||
}
|
||||
|
||||
private static String androidUserAgent(String clientVersion) {
|
||||
return PACKAGE_NAME_ANDROID_MUSIC +
|
||||
"/" +
|
||||
clientVersion +
|
||||
" (Linux; U; Android " +
|
||||
OS_VERSION_ANDROID_MUSIC +
|
||||
"; GB) gzip";
|
||||
}
|
||||
|
||||
private static String iOSUserAgent(String clientVersion) {
|
||||
return PACKAGE_NAME_IOS_MUSIC +
|
||||
"/" +
|
||||
clientVersion +
|
||||
"(" +
|
||||
DEVICE_MODEL_IOS_MUSIC +
|
||||
"; U; CPU iOS " +
|
||||
USER_AGENT_VERSION_IOS_MUSIC +
|
||||
" like Mac OS X)";
|
||||
}
|
||||
|
||||
public enum ClientType {
|
||||
ANDROID_MUSIC_4_27(21,
|
||||
DEVICE_MODEL_ANDROID_MUSIC,
|
||||
OS_VERSION_ANDROID_MUSIC,
|
||||
androidUserAgent(CLIENT_VERSION_ANDROID_MUSIC_4_27),
|
||||
CLIENT_VERSION_ANDROID_MUSIC_4_27
|
||||
),
|
||||
ANDROID_MUSIC_5_29(21,
|
||||
DEVICE_MODEL_ANDROID_MUSIC,
|
||||
OS_VERSION_ANDROID_MUSIC,
|
||||
androidUserAgent(CLIENT_VERSION_ANDROID_MUSIC_5_29),
|
||||
CLIENT_VERSION_ANDROID_MUSIC_5_29
|
||||
),
|
||||
IOS_MUSIC_6_21(
|
||||
26,
|
||||
DEVICE_MODEL_IOS_MUSIC,
|
||||
OS_VERSION_IOS_MUSIC,
|
||||
iOSUserAgent(CLIENT_VERSION_IOS_MUSIC_6_21),
|
||||
CLIENT_VERSION_IOS_MUSIC_6_21
|
||||
),
|
||||
IOS_MUSIC_7_04(
|
||||
26,
|
||||
DEVICE_MODEL_IOS_MUSIC,
|
||||
OS_VERSION_IOS_MUSIC,
|
||||
iOSUserAgent(CLIENT_VERSION_IOS_MUSIC_7_04),
|
||||
CLIENT_VERSION_IOS_MUSIC_7_04
|
||||
);
|
||||
|
||||
/**
|
||||
* YouTube
|
||||
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
|
||||
*/
|
||||
public final int id;
|
||||
|
||||
/**
|
||||
* Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model)
|
||||
*/
|
||||
public final String deviceModel;
|
||||
|
||||
/**
|
||||
* Device OS version.
|
||||
*/
|
||||
public final String osVersion;
|
||||
|
||||
/**
|
||||
* Player user-agent.
|
||||
*/
|
||||
public final String userAgent;
|
||||
|
||||
/**
|
||||
* App version.
|
||||
*/
|
||||
public final String clientVersion;
|
||||
|
||||
ClientType(int id,
|
||||
String deviceModel,
|
||||
String osVersion,
|
||||
String userAgent,
|
||||
String clientVersion
|
||||
) {
|
||||
this.id = id;
|
||||
this.deviceModel = deviceModel;
|
||||
this.clientVersion = clientVersion;
|
||||
this.osVersion = osVersion;
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import app.revanced.extension.music.patches.general.ChangeStartPagePatch.StartPage;
|
||||
import app.revanced.extension.music.patches.misc.AlbumMusicVideoPatch.RedirectType;
|
||||
import app.revanced.extension.music.patches.misc.client.AppClient.ClientType;
|
||||
import app.revanced.extension.music.patches.utils.PatchStatus;
|
||||
import app.revanced.extension.music.sponsorblock.SponsorBlockSettings;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
@ -186,9 +185,6 @@ public class Settings extends BaseSettings {
|
||||
public static final EnumSetting<RedirectType> DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE = new EnumSetting<>("revanced_disable_music_video_in_album_redirect_type", RedirectType.REDIRECT, true);
|
||||
public static final BooleanSetting ENABLE_OPUS_CODEC = new BooleanSetting("revanced_enable_opus_codec", FALSE, true);
|
||||
public static final BooleanSetting SETTINGS_IMPORT_EXPORT = new BooleanSetting("revanced_extended_settings_import_export", FALSE, false);
|
||||
public static final BooleanSetting SPOOF_CLIENT = new BooleanSetting("revanced_spoof_client", FALSE, true);
|
||||
public static final EnumSetting<ClientType> SPOOF_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_client_type", ClientType.IOS_MUSIC_6_21, true);
|
||||
|
||||
|
||||
// PreferenceScreen: Return YouTube Dislike
|
||||
public static final BooleanSetting RYD_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE);
|
||||
@ -271,7 +267,6 @@ public class Settings extends BaseSettings {
|
||||
SETTINGS_IMPORT_EXPORT.key,
|
||||
SPOOF_APP_VERSION_TARGET.key,
|
||||
SPOOF_CLIENT_TYPE.key,
|
||||
SPOOF_STREAMING_DATA_TYPE.key,
|
||||
RETURN_YOUTUBE_USERNAME_ABOUT.key,
|
||||
RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT.key,
|
||||
RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY.key,
|
||||
|
@ -20,7 +20,6 @@ import static app.revanced.extension.music.utils.ExtendedUtils.getLayoutParams;
|
||||
import static app.revanced.extension.music.utils.RestartUtils.showRestartDialog;
|
||||
import static app.revanced.extension.shared.settings.BaseSettings.RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT;
|
||||
import static app.revanced.extension.shared.settings.BaseSettings.RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY;
|
||||
import static app.revanced.extension.shared.settings.BaseSettings.SPOOF_STREAMING_DATA_TYPE;
|
||||
import static app.revanced.extension.shared.settings.Setting.getSettingFromPath;
|
||||
import static app.revanced.extension.shared.utils.ResourceUtils.getStringArray;
|
||||
import static app.revanced.extension.shared.utils.StringRef.str;
|
||||
@ -165,7 +164,6 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
|
||||
|| settings.equals(DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE)
|
||||
|| settings.equals(RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT)
|
||||
|| settings.equals(SPOOF_CLIENT_TYPE)
|
||||
|| settings.equals(SPOOF_STREAMING_DATA_TYPE)
|
||||
) {
|
||||
ResettableListPreference.showDialog(mActivity, enumSetting, 0);
|
||||
}
|
||||
|
@ -6,8 +6,13 @@ public class PatchStatus {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean SpoofStreamingDataMusic() {
|
||||
// Replace this with true If the Spoof streaming data patch succeeds in YouTube Music
|
||||
public static boolean SpoofClient() {
|
||||
// Replace this with true If the Spoof streaming data patch succeeds in YouTube Music.
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean SpoofStreamingData() {
|
||||
// Replace this with true If the Spoof streaming data patch succeeds in YouTube.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,189 @@
|
||||
package app.revanced.extension.shared.patches.client;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class MusicAppClient {
|
||||
|
||||
// Audio codec is MP4A.
|
||||
private static final String CLIENT_VERSION_ANDROID_MUSIC_4_27 = "4.27.53";
|
||||
|
||||
// Audio codec is OPUS.
|
||||
private static final String CLIENT_VERSION_ANDROID_MUSIC_5_29 = "5.29.53";
|
||||
|
||||
private static final String PACKAGE_NAME_ANDROID_MUSIC = "com.google.android.apps.youtube.music";
|
||||
private static final String DEVICE_BRAND_ANDROID_MUSIC = Build.BRAND;
|
||||
private static final String DEVICE_MAKE_ANDROID_MUSIC = Build.MANUFACTURER;
|
||||
private static final String DEVICE_MODEL_ANDROID_MUSIC = Build.MODEL;
|
||||
private static final String BUILD_ID_ANDROID_MUSIC = Build.ID;
|
||||
private static final String OS_NAME_ANDROID_MUSIC = "Android Automotive";
|
||||
private static final String OS_VERSION_ANDROID_MUSIC = Build.VERSION.RELEASE;
|
||||
|
||||
// Audio codec is MP4A.
|
||||
private static final String CLIENT_VERSION_IOS_MUSIC_6_21 = "6.21";
|
||||
private static final String DEVICE_MODEL_IOS_MUSIC_6_21 = "iPhone14,3";
|
||||
private static final String OS_VERSION_IOS_MUSIC_6_21 = "15.7.1.19H117";
|
||||
private static final String USER_AGENT_VERSION_IOS_MUSIC_6_21 = "15_7_1";
|
||||
|
||||
// Audio codec is OPUS.
|
||||
private static final String CLIENT_VERSION_IOS_MUSIC_7_04 = "7.04";
|
||||
private static final String DEVICE_MODEL_IOS_MUSIC_7_04 = "iPhone17,2";
|
||||
private static final String OS_VERSION_IOS_MUSIC_7_04 = "18.2.1.22C161";
|
||||
private static final String USER_AGENT_VERSION_IOS_MUSIC_7_04 = "18_2_1";
|
||||
|
||||
private static final String PACKAGE_NAME_IOS_MUSIC = "com.google.ios.youtubemusic";
|
||||
private static final String DEVICE_BRAND_IOS_MUSIC = "Apple";
|
||||
private static final String DEVICE_MAKE_IOS_MUSIC = "Apple";
|
||||
private static final String OS_NAME_IOS_MUSIC = "iOS";
|
||||
|
||||
private MusicAppClient() {
|
||||
}
|
||||
|
||||
private static String androidUserAgent(String clientVersion) {
|
||||
return PACKAGE_NAME_ANDROID_MUSIC +
|
||||
"/" +
|
||||
clientVersion +
|
||||
"(Linux; U; Android " +
|
||||
OS_VERSION_ANDROID_MUSIC +
|
||||
"; " +
|
||||
Locale.getDefault() +
|
||||
"; " +
|
||||
DEVICE_MODEL_ANDROID_MUSIC +
|
||||
" Build/" +
|
||||
BUILD_ID_ANDROID_MUSIC +
|
||||
") gzip";
|
||||
}
|
||||
|
||||
private static String iOSUserAgent(String clientVersion, String deviceModel, String osVersion) {
|
||||
return PACKAGE_NAME_IOS_MUSIC +
|
||||
"/" +
|
||||
clientVersion +
|
||||
" (" +
|
||||
deviceModel +
|
||||
"; U; CPU iOS " +
|
||||
osVersion +
|
||||
" like Mac OS X; " +
|
||||
Locale.getDefault() +
|
||||
")";
|
||||
}
|
||||
|
||||
public enum ActionButtonType {
|
||||
NONE, // No action button (~ 6.14)
|
||||
YOUTUBE_BUTTON, // Type of action button is YouTubeButton (6.15 ~ 7.16)
|
||||
LITHO // Type of action button is ComponentHost (7.17 ~)
|
||||
}
|
||||
|
||||
public enum ClientType {
|
||||
ANDROID_MUSIC_4_27(21,
|
||||
DEVICE_BRAND_ANDROID_MUSIC,
|
||||
DEVICE_MAKE_ANDROID_MUSIC,
|
||||
DEVICE_MODEL_ANDROID_MUSIC,
|
||||
OS_NAME_ANDROID_MUSIC,
|
||||
OS_VERSION_ANDROID_MUSIC,
|
||||
androidUserAgent(CLIENT_VERSION_ANDROID_MUSIC_4_27),
|
||||
CLIENT_VERSION_ANDROID_MUSIC_4_27,
|
||||
ActionButtonType.NONE
|
||||
),
|
||||
ANDROID_MUSIC_5_29(21,
|
||||
DEVICE_BRAND_ANDROID_MUSIC,
|
||||
DEVICE_MAKE_ANDROID_MUSIC,
|
||||
DEVICE_MODEL_ANDROID_MUSIC,
|
||||
OS_NAME_ANDROID_MUSIC,
|
||||
OS_VERSION_ANDROID_MUSIC,
|
||||
androidUserAgent(CLIENT_VERSION_ANDROID_MUSIC_5_29),
|
||||
CLIENT_VERSION_ANDROID_MUSIC_5_29,
|
||||
ActionButtonType.NONE
|
||||
),
|
||||
IOS_MUSIC_6_21(
|
||||
26,
|
||||
DEVICE_BRAND_IOS_MUSIC,
|
||||
DEVICE_MAKE_IOS_MUSIC,
|
||||
DEVICE_MODEL_IOS_MUSIC_6_21,
|
||||
OS_NAME_IOS_MUSIC,
|
||||
OS_VERSION_IOS_MUSIC_6_21,
|
||||
iOSUserAgent(CLIENT_VERSION_IOS_MUSIC_6_21, DEVICE_MODEL_IOS_MUSIC_6_21, USER_AGENT_VERSION_IOS_MUSIC_6_21),
|
||||
CLIENT_VERSION_IOS_MUSIC_6_21,
|
||||
ActionButtonType.YOUTUBE_BUTTON
|
||||
),
|
||||
IOS_MUSIC_7_04(
|
||||
26,
|
||||
DEVICE_BRAND_IOS_MUSIC,
|
||||
DEVICE_MAKE_IOS_MUSIC,
|
||||
DEVICE_MODEL_IOS_MUSIC_7_04,
|
||||
OS_NAME_IOS_MUSIC,
|
||||
OS_VERSION_IOS_MUSIC_7_04,
|
||||
iOSUserAgent(CLIENT_VERSION_IOS_MUSIC_7_04, DEVICE_MODEL_IOS_MUSIC_7_04, USER_AGENT_VERSION_IOS_MUSIC_7_04),
|
||||
CLIENT_VERSION_IOS_MUSIC_7_04,
|
||||
ActionButtonType.LITHO
|
||||
);
|
||||
|
||||
/**
|
||||
* YouTube
|
||||
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
|
||||
*/
|
||||
public final int id;
|
||||
|
||||
/**
|
||||
* Device brand, equivalent to {@link Build#BRAND} (System property: ro.product.vendor.brand)
|
||||
*/
|
||||
public final String deviceBrand;
|
||||
|
||||
/**
|
||||
* Device make, equivalent to {@link Build#MANUFACTURER} (System property: ro.product.vendor.manufacturer)
|
||||
*/
|
||||
public final String deviceMake;
|
||||
|
||||
/**
|
||||
* Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model)
|
||||
*/
|
||||
public final String deviceModel;
|
||||
|
||||
/**
|
||||
* Device OS name.
|
||||
*/
|
||||
public final String osName;
|
||||
|
||||
/**
|
||||
* Device OS version.
|
||||
*/
|
||||
public final String osVersion;
|
||||
|
||||
/**
|
||||
* Player user-agent.
|
||||
*/
|
||||
public final String userAgent;
|
||||
|
||||
/**
|
||||
* App version.
|
||||
*/
|
||||
public final String clientVersion;
|
||||
|
||||
private final ActionButtonType actionButtonType;
|
||||
|
||||
ClientType(int id,
|
||||
String deviceBrand,
|
||||
String deviceMake,
|
||||
String deviceModel,
|
||||
String osName,
|
||||
String osVersion,
|
||||
String userAgent,
|
||||
String clientVersion,
|
||||
ActionButtonType actionButtonType
|
||||
) {
|
||||
this.id = id;
|
||||
this.deviceBrand = deviceBrand;
|
||||
this.deviceMake = deviceMake;
|
||||
this.deviceModel = deviceModel;
|
||||
this.clientVersion = clientVersion;
|
||||
this.osName = osName;
|
||||
this.osVersion = osVersion;
|
||||
this.userAgent = userAgent;
|
||||
this.actionButtonType = actionButtonType;
|
||||
}
|
||||
|
||||
public boolean isYouTubeButton() {
|
||||
return actionButtonType == ActionButtonType.YOUTUBE_BUTTON;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package app.revanced.extension.shared.patches.client
|
||||
|
||||
import android.os.Build
|
||||
import app.revanced.extension.shared.patches.PatchStatus
|
||||
import app.revanced.extension.shared.settings.BaseSettings
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import java.util.Locale
|
||||
@ -9,7 +8,7 @@ import java.util.Locale
|
||||
/**
|
||||
* Used to fetch streaming data.
|
||||
*/
|
||||
object AppClient {
|
||||
object YouTubeAppClient {
|
||||
// IOS
|
||||
/**
|
||||
* Video not playable: Paid / Movie / Private / Age-restricted
|
||||
@ -108,7 +107,6 @@ object AppClient {
|
||||
*/
|
||||
private const val ANDROID_SDK_VERSION_ANDROID_VR = "32"
|
||||
private const val BUILD_ID_ANDROID_VR = "SQ3A.220605.009.A1"
|
||||
private const val CHIPSET_ANDROID_VR = "Qualcomm;SXR2230P"
|
||||
|
||||
private val USER_AGENT_ANDROID_VR = androidUserAgent(
|
||||
packageName = PACKAGE_NAME_ANDROID_VR,
|
||||
@ -137,7 +135,6 @@ object AppClient {
|
||||
private const val ANDROID_SDK_VERSION_ANDROID_UNPLUGGED = "34"
|
||||
private const val BUILD_ID_ANDROID_UNPLUGGED = "UTT3.240625.001.K5"
|
||||
private const val GMS_CORE_VERSION_CODE_ANDROID_UNPLUGGED = "244336107"
|
||||
private const val CHIPSET_ANDROID_UNPLUGGED = "Mediatek;MT8696"
|
||||
|
||||
private val USER_AGENT_ANDROID_UNPLUGGED = androidUserAgent(
|
||||
packageName = PACKAGE_NAME_ANDROID_UNPLUGGED,
|
||||
@ -166,7 +163,6 @@ object AppClient {
|
||||
private const val ANDROID_SDK_VERSION_ANDROID_CREATOR = "35"
|
||||
private const val BUILD_ID_ANDROID_CREATOR = "AP3A.241005.015.A2"
|
||||
private const val GMS_CORE_VERSION_CODE_ANDROID_CREATOR = "244738035"
|
||||
private const val CHIPSET_ANDROID_CREATOR = "Google;Tensor G4"
|
||||
|
||||
private val USER_AGENT_ANDROID_CREATOR = androidUserAgent(
|
||||
packageName = PACKAGE_NAME_ANDROID_CREATOR,
|
||||
@ -177,39 +173,6 @@ object AppClient {
|
||||
)
|
||||
|
||||
|
||||
// ANDROID MUSIC
|
||||
/**
|
||||
* Video not playable: All videos that can't be played on YouTube Music
|
||||
*/
|
||||
private const val PACKAGE_NAME_ANDROID_MUSIC = "com.google.android.apps.youtube.music"
|
||||
|
||||
/**
|
||||
* Older client versions don't seem to require poToken.
|
||||
* It is not the default client yet, as it requires sufficient testing.
|
||||
*/
|
||||
private const val CLIENT_VERSION_ANDROID_MUSIC = "4.27.53"
|
||||
|
||||
/**
|
||||
* The device machine id for the Google Pixel 4.
|
||||
* See [this GitLab](https://dumps.tadiphone.dev/dumps/google/flame) for more information.
|
||||
*/
|
||||
private const val DEVICE_MODEL_ANDROID_MUSIC = "Pixel 4"
|
||||
private const val DEVICE_MAKE_ANDROID_MUSIC = "Google"
|
||||
private const val OS_VERSION_ANDROID_MUSIC = "11"
|
||||
private const val ANDROID_SDK_VERSION_ANDROID_MUSIC = "30"
|
||||
private const val BUILD_ID_ANDROID_MUSIC = "SPP2.210219.008"
|
||||
private const val GMS_CORE_VERSION_CODE_ANDROID_MUSIC = "244738022"
|
||||
private const val CHIPSET_ANDROID_MUSIC = "Qualcomm;SM8150"
|
||||
|
||||
private val USER_AGENT_ANDROID_MUSIC = androidUserAgent(
|
||||
packageName = PACKAGE_NAME_ANDROID_MUSIC,
|
||||
clientVersion = CLIENT_VERSION_ANDROID_MUSIC,
|
||||
osVersion = OS_VERSION_ANDROID_MUSIC,
|
||||
deviceModel = DEVICE_MODEL_ANDROID_MUSIC,
|
||||
buildId = BUILD_ID_ANDROID_MUSIC
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Same format as Android YouTube User-Agent.
|
||||
* Example: 'com.google.android.youtube/19.46.40(Linux; U; Android 13; in_ID; 21061110AG Build/TP1A.220624.014) gzip'
|
||||
@ -240,10 +203,7 @@ object AppClient {
|
||||
}
|
||||
|
||||
fun availableClientTypes(preferredClient: ClientType): Array<ClientType> {
|
||||
val availableClientTypes = if (PatchStatus.SpoofStreamingDataMusic())
|
||||
ClientType.CLIENT_ORDER_TO_USE_YOUTUBE_MUSIC
|
||||
else
|
||||
ClientType.CLIENT_ORDER_TO_USE_YOUTUBE
|
||||
val availableClientTypes = ClientType.CLIENT_ORDER_TO_USE_YOUTUBE
|
||||
|
||||
if (ArrayUtils.contains(availableClientTypes, preferredClient)) {
|
||||
val clientToUse: Array<ClientType?> = arrayOfNulls(availableClientTypes.size)
|
||||
@ -400,19 +360,6 @@ object AppClient {
|
||||
"iOS Force AVC"
|
||||
else
|
||||
"iOS"
|
||||
),
|
||||
ANDROID_MUSIC(
|
||||
id = 21,
|
||||
deviceMake = DEVICE_MAKE_ANDROID_MUSIC,
|
||||
deviceModel = DEVICE_MODEL_ANDROID_MUSIC,
|
||||
osVersion = OS_VERSION_ANDROID_MUSIC,
|
||||
userAgent = USER_AGENT_ANDROID_MUSIC,
|
||||
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_MUSIC,
|
||||
clientVersion = CLIENT_VERSION_ANDROID_MUSIC,
|
||||
gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_MUSIC,
|
||||
requireAuth = true,
|
||||
clientName = "ANDROID_MUSIC",
|
||||
friendlyName = "Android Music"
|
||||
);
|
||||
|
||||
companion object {
|
||||
@ -424,11 +371,6 @@ object AppClient {
|
||||
IOS,
|
||||
ANDROID_VR_NO_AUTH,
|
||||
)
|
||||
|
||||
internal val CLIENT_ORDER_TO_USE_YOUTUBE_MUSIC: Array<ClientType> = arrayOf(
|
||||
ANDROID_VR,
|
||||
ANDROID_MUSIC,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ package app.revanced.extension.shared.patches.client
|
||||
* Used to fetch video information.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
object WebClient {
|
||||
object YouTubeWebClient {
|
||||
/**
|
||||
* This user agent does not require a PoToken in [ClientType.MWEB]
|
||||
* https://github.com/yt-dlp/yt-dlp/blob/0b6b7742c2e7f2a1fcb0b54ef3dd484bab404b3f/yt_dlp/extractor/youtube.py#L259
|
@ -0,0 +1,110 @@
|
||||
package app.revanced.extension.shared.patches.spoof;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import app.revanced.extension.shared.patches.PatchStatus;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.PackageUtils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class BlockRequestPatch {
|
||||
/**
|
||||
* Used in YouTube.
|
||||
*/
|
||||
public static final boolean SPOOF_STREAMING_DATA =
|
||||
BaseSettings.SPOOF_STREAMING_DATA.get() && PatchStatus.SpoofStreamingData();
|
||||
|
||||
/**
|
||||
* Used in YouTube Music.
|
||||
*/
|
||||
public static final boolean SPOOF_CLIENT =
|
||||
BaseSettings.SPOOF_CLIENT.get() && PatchStatus.SpoofClient();
|
||||
|
||||
/**
|
||||
* In order to load the action bar normally,
|
||||
* Some versions must block the initplayback request.
|
||||
*/
|
||||
private static final boolean IS_7_17_OR_GREATER =
|
||||
PackageUtils.getAppVersionName().compareTo("7.17.00") >= 0;
|
||||
|
||||
private static final boolean IS_YOUTUBE_BUTTON =
|
||||
BaseSettings.SPOOF_CLIENT_TYPE.get().isYouTubeButton();
|
||||
|
||||
private static final boolean BLOCK_REQUEST;
|
||||
|
||||
static {
|
||||
if (SPOOF_STREAMING_DATA) {
|
||||
BLOCK_REQUEST = true;
|
||||
} else {
|
||||
if (!SPOOF_CLIENT) {
|
||||
BLOCK_REQUEST = false;
|
||||
} else {
|
||||
if (!IS_7_17_OR_GREATER && !IS_YOUTUBE_BUTTON) {
|
||||
// If the current version is lower than 7.16 and the client action button type is not YouTubeButton,
|
||||
// the initplayback request must be blocked.
|
||||
BLOCK_REQUEST = true;
|
||||
} else {
|
||||
// If the current version is higher than 7.17,
|
||||
// the initplayback request must always be blocked.
|
||||
BLOCK_REQUEST = IS_7_17_OR_GREATER;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Any unreachable ip address. Used to intentionally fail requests.
|
||||
*/
|
||||
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
|
||||
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Blocks /get_watch requests by returning an unreachable URI.
|
||||
*
|
||||
* @param playerRequestUri The URI of the player request.
|
||||
* @return An unreachable URI if the request is a /get_watch request, otherwise the original URI.
|
||||
*/
|
||||
public static Uri blockGetWatchRequest(Uri playerRequestUri) {
|
||||
if (BLOCK_REQUEST) {
|
||||
try {
|
||||
String path = playerRequestUri.getPath();
|
||||
|
||||
if (path != null && path.contains("get_watch")) {
|
||||
Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri");
|
||||
|
||||
return UNREACHABLE_HOST_URI;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "blockGetWatchRequest failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return playerRequestUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* <p>
|
||||
* Blocks /initplayback requests.
|
||||
*/
|
||||
public static String blockInitPlaybackRequest(String originalUrlString) {
|
||||
if (BLOCK_REQUEST) {
|
||||
try {
|
||||
var originalUri = Uri.parse(originalUrlString);
|
||||
String path = originalUri.getPath();
|
||||
|
||||
if (path != null && path.contains("initplayback")) {
|
||||
Logger.printDebug(() -> "Blocking 'initplayback' by clearing query");
|
||||
|
||||
return originalUri.buildUpon().clearQuery().build().toString();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return originalUrlString;
|
||||
}
|
||||
}
|
@ -1,66 +1,98 @@
|
||||
package app.revanced.extension.music.patches.misc;
|
||||
package app.revanced.extension.shared.patches.spoof;
|
||||
|
||||
import app.revanced.extension.music.patches.misc.client.AppClient.ClientType;
|
||||
import app.revanced.extension.shared.patches.client.MusicAppClient.ClientType;
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofClientPatch {
|
||||
public class SpoofClientPatch extends BlockRequestPatch {
|
||||
private static final ClientType CLIENT_TYPE = Settings.SPOOF_CLIENT_TYPE.get();
|
||||
public static final boolean SPOOF_CLIENT = Settings.SPOOF_CLIENT.get();
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int getClientTypeId(int originalClientTypeId) {
|
||||
public static int getClientId(int original) {
|
||||
if (SPOOF_CLIENT) {
|
||||
return CLIENT_TYPE.id;
|
||||
}
|
||||
|
||||
return originalClientTypeId;
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String getClientVersion(String originalClientVersion) {
|
||||
public static String getClientVersion(String original) {
|
||||
if (SPOOF_CLIENT) {
|
||||
return CLIENT_TYPE.clientVersion;
|
||||
}
|
||||
|
||||
return originalClientVersion;
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String getClientModel(String originalClientModel) {
|
||||
public static String getDeviceBrand(String original) {
|
||||
if (SPOOF_CLIENT) {
|
||||
return CLIENT_TYPE.deviceBrand;
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String getDeviceMake(String original) {
|
||||
if (SPOOF_CLIENT) {
|
||||
return CLIENT_TYPE.deviceMake;
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String getDeviceModel(String original) {
|
||||
if (SPOOF_CLIENT) {
|
||||
return CLIENT_TYPE.deviceModel;
|
||||
}
|
||||
|
||||
return originalClientModel;
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String getOsVersion(String originalOsVersion) {
|
||||
public static String getOsName(String original) {
|
||||
if (SPOOF_CLIENT) {
|
||||
return CLIENT_TYPE.osName;
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String getOsVersion(String original) {
|
||||
if (SPOOF_CLIENT) {
|
||||
return CLIENT_TYPE.osVersion;
|
||||
}
|
||||
|
||||
return originalOsVersion;
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String getUserAgent(String originalUserAgent) {
|
||||
public static String getUserAgent(String original) {
|
||||
if (SPOOF_CLIENT) {
|
||||
return CLIENT_TYPE.userAgent;
|
||||
}
|
||||
|
||||
return originalUserAgent;
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
@ -1,11 +1,8 @@
|
||||
package app.revanced.extension.shared.patches.spoof;
|
||||
|
||||
import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingDataMusic;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@ -13,7 +10,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import app.revanced.extension.shared.patches.client.AppClient.ClientType;
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient.ClientType;
|
||||
import app.revanced.extension.shared.patches.spoof.requests.StreamingDataRequest;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
@ -21,10 +18,7 @@ import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofStreamingDataPatch {
|
||||
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_STREAMING_DATA.get();
|
||||
private static final boolean SPOOF_STREAMING_DATA_YOUTUBE = SPOOF_STREAMING_DATA && !SpoofStreamingDataMusic();
|
||||
private static final boolean SPOOF_STREAMING_DATA_MUSIC = SPOOF_STREAMING_DATA && SpoofStreamingDataMusic();
|
||||
public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
||||
private static final String PO_TOKEN =
|
||||
BaseSettings.SPOOF_STREAMING_DATA_PO_TOKEN.get();
|
||||
private static final String VISITOR_DATA =
|
||||
@ -50,58 +44,6 @@ public class SpoofStreamingDataPatch {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Blocks /get_watch requests by returning an unreachable URI.
|
||||
*
|
||||
* @param playerRequestUri The URI of the player request.
|
||||
* @return An unreachable URI if the request is a /get_watch request, otherwise the original URI.
|
||||
*/
|
||||
public static Uri blockGetWatchRequest(Uri playerRequestUri) {
|
||||
if (SPOOF_STREAMING_DATA) {
|
||||
// An exception may be thrown when the /get_watch request is blocked when connected to Wi-Fi in YouTube Music.
|
||||
if (SPOOF_STREAMING_DATA_YOUTUBE || Utils.getNetworkType() == Utils.NetworkType.MOBILE) {
|
||||
try {
|
||||
String path = playerRequestUri.getPath();
|
||||
|
||||
if (path != null && path.contains("get_watch")) {
|
||||
Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri");
|
||||
|
||||
return UNREACHABLE_HOST_URI;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "blockGetWatchRequest failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return playerRequestUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* <p>
|
||||
* Blocks /initplayback requests.
|
||||
*/
|
||||
public static String blockInitPlaybackRequest(String originalUrlString) {
|
||||
if (SPOOF_STREAMING_DATA) {
|
||||
try {
|
||||
var originalUri = Uri.parse(originalUrlString);
|
||||
String path = originalUri.getPath();
|
||||
|
||||
if (path != null && path.contains("initplayback")) {
|
||||
Logger.printDebug(() -> "Blocking 'initplayback' by clearing query");
|
||||
|
||||
return originalUri.buildUpon().clearQuery().build().toString();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return originalUrlString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@ -120,73 +62,20 @@ public class SpoofStreamingDataPatch {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static volatile String auth = "";
|
||||
private static volatile Map<String, String> requestHeader;
|
||||
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
private static final String[] REQUEST_HEADER_KEYS = {
|
||||
AUTHORIZATION_HEADER,
|
||||
"X-GOOG-API-FORMAT-VERSION",
|
||||
"X-Goog-Visitor-Id"
|
||||
/**
|
||||
* Parameters causing playback issues.
|
||||
*/
|
||||
private static final String[] PATH_NO_VIDEO_ID = {
|
||||
"ad_break", // This request fetches a list of times when ads can be displayed.
|
||||
"get_drm_license", // Waiting for a paid video to start.
|
||||
"heartbeat", // This request determines whether to pause playback when the user is AFK.
|
||||
"refresh", // Waiting for a livestream to start.
|
||||
};
|
||||
|
||||
/**
|
||||
* If the /get_watch request is not blocked,
|
||||
* fetchRequest will not be invoked at the point where the video starts.
|
||||
* <p>
|
||||
* An additional method is used to invoke fetchRequest in YouTube Music:
|
||||
* 1. Save the requestHeader in a field.
|
||||
* 2. Invoke fetchRequest with the videoId used in PlaybackStartDescriptor.
|
||||
* <p>
|
||||
*
|
||||
* @param requestHeaders Save the request Headers used for login to a field.
|
||||
* Only used in YouTube Music where login is required.
|
||||
*/
|
||||
private static void setRequestHeaders(Map<String, String> requestHeaders) {
|
||||
if (SPOOF_STREAMING_DATA_MUSIC) {
|
||||
try {
|
||||
// Save requestHeaders whenever an account is switched.
|
||||
String authorization = requestHeaders.get(AUTHORIZATION_HEADER);
|
||||
if (authorization == null || auth.equals(authorization)) {
|
||||
return;
|
||||
}
|
||||
for (String key : REQUEST_HEADER_KEYS) {
|
||||
if (requestHeaders.get(key) == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
auth = authorization;
|
||||
requestHeader = requestHeaders;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setRequestHeaders failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void fetchStreams(@NonNull String videoId) {
|
||||
if (SPOOF_STREAMING_DATA_MUSIC) {
|
||||
try {
|
||||
if (requestHeader != null) {
|
||||
StreamingDataRequest.fetchRequest(videoId, requestHeader, VISITOR_DATA, PO_TOKEN);
|
||||
} else {
|
||||
Logger.printDebug(() -> "Ignoring request with no header.");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "fetchStreams failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void fetchStreams(String url, Map<String, String> requestHeaders) {
|
||||
setRequestHeaders(requestHeaders);
|
||||
|
||||
if (SPOOF_STREAMING_DATA) {
|
||||
try {
|
||||
Uri uri = Uri.parse(url);
|
||||
@ -195,11 +84,7 @@ public class SpoofStreamingDataPatch {
|
||||
return;
|
||||
}
|
||||
|
||||
// 'get_drm_license' has no video id and appears to happen when waiting for a paid video to start.
|
||||
// 'heartbeat' has no video id and appears to be only after playback has started.
|
||||
// 'refresh' has no video id and appears to happen when waiting for a livestream to start.
|
||||
// 'ad_break' has no video id.
|
||||
if (path.contains("get_drm_license") || path.contains("heartbeat") || path.contains("refresh") || path.contains("ad_break")) {
|
||||
if (Utils.containsAny(path, PATH_NO_VIDEO_ID)) {
|
||||
Logger.printDebug(() -> "Ignoring path: " + path);
|
||||
return;
|
||||
}
|
||||
@ -262,7 +147,7 @@ public class SpoofStreamingDataPatch {
|
||||
* Called after {@link #getStreamingData(String)}.
|
||||
*/
|
||||
public static void setApproxDurationMs(String videoId, long approxDurationMs) {
|
||||
if (SPOOF_STREAMING_DATA_YOUTUBE && approxDurationMs != Long.MAX_VALUE) {
|
||||
if (SPOOF_STREAMING_DATA && approxDurationMs != Long.MAX_VALUE) {
|
||||
approxDurationMsMap.put(videoId, approxDurationMs);
|
||||
Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs);
|
||||
}
|
||||
@ -284,7 +169,7 @@ public class SpoofStreamingDataPatch {
|
||||
* Called after {@link #getStreamingData(String)}.
|
||||
*/
|
||||
public static long getApproxDurationMs(String videoId) {
|
||||
if (SPOOF_STREAMING_DATA_YOUTUBE && videoId != null) {
|
||||
if (SPOOF_STREAMING_DATA && videoId != null) {
|
||||
final Long approxDurationMs = approxDurationMsMap.get(videoId);
|
||||
if (approxDurationMs != null) {
|
||||
Logger.printDebug(() -> "Replacing video length: " + approxDurationMs + " for videoId: " + videoId);
|
||||
|
@ -1,7 +1,7 @@
|
||||
package app.revanced.extension.shared.patches.spoof.requests
|
||||
|
||||
import app.revanced.extension.shared.patches.client.AppClient
|
||||
import app.revanced.extension.shared.patches.client.WebClient
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.patches.client.YouTubeWebClient
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.requests.Route
|
||||
import app.revanced.extension.shared.requests.Route.CompiledRoute
|
||||
@ -69,7 +69,7 @@ object PlayerRoutes {
|
||||
|
||||
@JvmStatic
|
||||
fun createApplicationRequestBody(
|
||||
clientType: AppClient.ClientType,
|
||||
clientType: YouTubeAppClient.ClientType,
|
||||
videoId: String,
|
||||
playlistId: String? = null,
|
||||
botGuardPoToken: String = "",
|
||||
@ -130,7 +130,7 @@ object PlayerRoutes {
|
||||
|
||||
@JvmStatic
|
||||
fun createWebInnertubeBody(
|
||||
clientType: WebClient.ClientType,
|
||||
clientType: YouTubeWebClient.ClientType,
|
||||
videoId: String
|
||||
): ByteArray {
|
||||
val innerTubeBody = JSONObject()
|
||||
@ -161,7 +161,7 @@ object PlayerRoutes {
|
||||
@JvmStatic
|
||||
fun getPlayerResponseConnectionFromRoute(
|
||||
route: CompiledRoute,
|
||||
clientType: AppClient.ClientType
|
||||
clientType: YouTubeAppClient.ClientType
|
||||
): HttpURLConnection {
|
||||
return getPlayerResponseConnectionFromRoute(
|
||||
route,
|
||||
@ -174,7 +174,7 @@ object PlayerRoutes {
|
||||
@JvmStatic
|
||||
fun getPlayerResponseConnectionFromRoute(
|
||||
route: CompiledRoute,
|
||||
clientType: WebClient.ClientType
|
||||
clientType: YouTubeWebClient.ClientType
|
||||
): HttpURLConnection {
|
||||
return getPlayerResponseConnectionFromRoute(
|
||||
route,
|
||||
|
@ -1,8 +1,7 @@
|
||||
package app.revanced.extension.shared.patches.spoof.requests
|
||||
|
||||
import androidx.annotation.GuardedBy
|
||||
import app.revanced.extension.shared.patches.client.AppClient
|
||||
import app.revanced.extension.shared.patches.client.AppClient.availableClientTypes
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_STREAMING_DATA
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.createApplicationRequestBody
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.getPlayerResponseConnectionFromRoute
|
||||
@ -93,16 +92,16 @@ class StreamingDataRequest private constructor(
|
||||
"X-GOOG-API-FORMAT-VERSION",
|
||||
VISITOR_ID_HEADER
|
||||
)
|
||||
private val SPOOF_STREAMING_DATA_TYPE: AppClient.ClientType =
|
||||
private val SPOOF_STREAMING_DATA_TYPE: YouTubeAppClient.ClientType =
|
||||
BaseSettings.SPOOF_STREAMING_DATA_TYPE.get()
|
||||
|
||||
private val CLIENT_ORDER_TO_USE: Array<AppClient.ClientType> =
|
||||
availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
|
||||
private val CLIENT_ORDER_TO_USE: Array<YouTubeAppClient.ClientType> =
|
||||
YouTubeAppClient.availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
|
||||
|
||||
private val DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH: Boolean =
|
||||
SPOOF_STREAMING_DATA_TYPE == AppClient.ClientType.ANDROID_VR_NO_AUTH
|
||||
SPOOF_STREAMING_DATA_TYPE == YouTubeAppClient.ClientType.ANDROID_VR_NO_AUTH
|
||||
|
||||
private var lastSpoofedClientType: AppClient.ClientType? = null
|
||||
private var lastSpoofedClientType: YouTubeAppClient.ClientType? = null
|
||||
|
||||
|
||||
/**
|
||||
@ -163,7 +162,7 @@ class StreamingDataRequest private constructor(
|
||||
}
|
||||
|
||||
private fun send(
|
||||
clientType: AppClient.ClientType,
|
||||
clientType: YouTubeAppClient.ClientType,
|
||||
videoId: String,
|
||||
playerHeaders: Map<String, String>,
|
||||
visitorId: String,
|
||||
@ -279,7 +278,7 @@ class StreamingDataRequest private constructor(
|
||||
} else {
|
||||
BufferedInputStream(connection.inputStream).use { inputStream ->
|
||||
ByteArrayOutputStream().use { stream ->
|
||||
val buffer = ByteArray(4096)
|
||||
val buffer = ByteArray(2048)
|
||||
var bytesRead: Int
|
||||
while ((inputStream.read(buffer)
|
||||
.also { bytesRead = it }) >= 0
|
||||
|
@ -5,7 +5,8 @@ import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.patches.PatchStatus.HideFullscreenAdsDefaultBoolean;
|
||||
|
||||
import app.revanced.extension.shared.patches.ReturnYouTubeUsernamePatch.DisplayFormat;
|
||||
import app.revanced.extension.shared.patches.client.AppClient.ClientType;
|
||||
import app.revanced.extension.shared.patches.client.MusicAppClient;
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient;
|
||||
import app.revanced.extension.shared.patches.spoof.SpoofStreamingDataPatch.AudioStreamLanguageOverrideAvailability;
|
||||
|
||||
/**
|
||||
@ -25,6 +26,28 @@ public class BaseSettings {
|
||||
|
||||
public static final EnumSetting<AppLanguage> REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true);
|
||||
|
||||
/**
|
||||
* These settings are used by YouTube Music.
|
||||
* Some patches are in a shared path, so they are declared here.
|
||||
*/
|
||||
public static final BooleanSetting SPOOF_CLIENT = new BooleanSetting("revanced_spoof_client", TRUE, true);
|
||||
public static final EnumSetting<MusicAppClient.ClientType> SPOOF_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_client_type", MusicAppClient.ClientType.IOS_MUSIC_6_21, true);
|
||||
|
||||
/**
|
||||
* These settings are used by YouTube.
|
||||
* Some patches are in a shared path, so they are declared here.
|
||||
*/
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA = new BooleanSetting("revanced_spoof_streaming_data", TRUE, true, "revanced_spoof_streaming_data_user_dialog_message");
|
||||
public static final EnumSetting<AppLanguage> SPOOF_STREAMING_DATA_LANGUAGE = new EnumSetting<>("revanced_spoof_streaming_data_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_streaming_data_ios_force_avc", FALSE, true,
|
||||
"revanced_spoof_streaming_data_ios_force_avc_user_dialog_message");
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE);
|
||||
// Client type must be last spoof setting due to cyclic references.
|
||||
public static final EnumSetting<YouTubeAppClient.ClientType> SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", YouTubeAppClient.ClientType.ANDROID_VR, true);
|
||||
|
||||
public static final StringSetting SPOOF_STREAMING_DATA_PO_TOKEN = new StringSetting("revanced_spoof_streaming_data_po_token", "", true);
|
||||
public static final StringSetting SPOOF_STREAMING_DATA_VISITOR_DATA = new StringSetting("revanced_spoof_streaming_data_visitor_data", "", true);
|
||||
|
||||
/**
|
||||
* These settings are used by YouTube and YouTube Music.
|
||||
*/
|
||||
@ -39,17 +62,6 @@ public class BaseSettings {
|
||||
public static final EnumSetting<DisplayFormat> RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT = new EnumSetting<>("revanced_return_youtube_username_display_format", DisplayFormat.USERNAME_ONLY, true);
|
||||
public static final StringSetting RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY = new StringSetting("revanced_return_youtube_username_youtube_data_api_v3_developer_key", "", true, false);
|
||||
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA = new BooleanSetting("revanced_spoof_streaming_data", TRUE, true, "revanced_spoof_streaming_data_user_dialog_message");
|
||||
public static final EnumSetting<AppLanguage> SPOOF_STREAMING_DATA_LANGUAGE = new EnumSetting<>("revanced_spoof_streaming_data_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_streaming_data_ios_force_avc", FALSE, true,
|
||||
"revanced_spoof_streaming_data_ios_force_avc_user_dialog_message");
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE);
|
||||
// Client type must be last spoof setting due to cyclic references.
|
||||
public static final EnumSetting<ClientType> SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", ClientType.ANDROID_VR, true);
|
||||
|
||||
public static final StringSetting SPOOF_STREAMING_DATA_PO_TOKEN = new StringSetting("revanced_spoof_streaming_data_po_token", "", true);
|
||||
public static final StringSetting SPOOF_STREAMING_DATA_VISITOR_DATA = new StringSetting("revanced_spoof_streaming_data_visitor_data", "", true);
|
||||
|
||||
/**
|
||||
* @noinspection DeprecatedIsStillUsed
|
||||
*/
|
||||
|
@ -2,7 +2,7 @@ package app.revanced.extension.youtube.patches.general.requests
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.annotation.GuardedBy
|
||||
import app.revanced.extension.shared.patches.client.WebClient
|
||||
import app.revanced.extension.shared.patches.client.YouTubeWebClient
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
@ -81,7 +81,7 @@ class VideoDetailsRequest private constructor(
|
||||
|
||||
private fun sendRequest(videoId: String): JSONObject? {
|
||||
val startTime = System.currentTimeMillis()
|
||||
val clientType = WebClient.ClientType.MWEB
|
||||
val clientType = YouTubeWebClient.ClientType.MWEB
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching video details request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
|
@ -2,8 +2,8 @@ package app.revanced.extension.youtube.patches.video.requests
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.annotation.GuardedBy
|
||||
import app.revanced.extension.shared.patches.client.AppClient
|
||||
import app.revanced.extension.shared.patches.client.WebClient
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.patches.client.YouTubeWebClient
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
@ -119,7 +119,7 @@ class MusicRequest private constructor(
|
||||
Objects.requireNonNull(videoId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
val clientType = AppClient.ClientType.ANDROID_VR
|
||||
val clientType = YouTubeAppClient.ClientType.ANDROID_VR
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
@ -163,7 +163,7 @@ class MusicRequest private constructor(
|
||||
Objects.requireNonNull(videoId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
val clientType = WebClient.ClientType.MWEB
|
||||
val clientType = YouTubeWebClient.ClientType.MWEB
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching microformat request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
|
@ -2,13 +2,9 @@ package app.revanced.patches.music.utils.fix.client
|
||||
|
||||
import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
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.reference.MethodReference
|
||||
|
||||
internal val createPlayerRequestBodyFingerprint = legacyFingerprint(
|
||||
name = "createPlayerRequestBodyFingerprint",
|
||||
@ -22,26 +18,6 @@ internal val createPlayerRequestBodyFingerprint = legacyFingerprint(
|
||||
strings = listOf("ms"),
|
||||
)
|
||||
|
||||
internal val createPlayerRequestBodyWithVersionReleaseFingerprint = legacyFingerprint(
|
||||
name = "createPlayerRequestBodyWithVersionReleaseFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("L"),
|
||||
strings = listOf("Google Inc."),
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfBuildInstruction(method) >= 0
|
||||
},
|
||||
)
|
||||
|
||||
fun indexOfBuildInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference?.name == "build" &&
|
||||
reference.parameterTypes.isEmpty() &&
|
||||
reference.returnType.startsWith("L")
|
||||
}
|
||||
|
||||
internal val setPlayerRequestClientTypeFingerprint = legacyFingerprint(
|
||||
name = "setPlayerRequestClientTypeFingerprint",
|
||||
opcodes = listOf(
|
||||
|
@ -3,24 +3,28 @@ package app.revanced.patches.music.utils.fix.client
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.music.utils.compatibility.Constants
|
||||
import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
|
||||
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_CLIENT
|
||||
import app.revanced.patches.music.utils.playbackSpeedBottomSheetFingerprint
|
||||
import app.revanced.patches.music.utils.playservice.is_7_25_or_greater
|
||||
import app.revanced.patches.music.utils.playservice.versionCheckPatch
|
||||
import app.revanced.patches.music.utils.settings.CategoryType
|
||||
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
|
||||
import app.revanced.patches.music.utils.settings.addSwitchPreference
|
||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||
import app.revanced.patches.shared.spoof.blockrequest.blockRequestPatch
|
||||
import app.revanced.patches.shared.createPlayerRequestBodyWithModelFingerprint
|
||||
import app.revanced.patches.shared.extension.Constants.PATCHES_PATH
|
||||
import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
|
||||
import app.revanced.patches.shared.indexOfBrandInstruction
|
||||
import app.revanced.patches.shared.indexOfManufacturerInstruction
|
||||
import app.revanced.patches.shared.indexOfModelInstruction
|
||||
import app.revanced.util.Utils.printWarn
|
||||
import app.revanced.patches.shared.indexOfReleaseInstruction
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.fingerprint.mutableClassOrThrow
|
||||
@ -32,108 +36,117 @@ 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.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
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.Reference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$MISC_PATH/SpoofClientPatch;"
|
||||
"$SPOOF_PATH/SpoofClientPatch;"
|
||||
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
|
||||
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
|
||||
|
||||
@Suppress("unused")
|
||||
val spoofClientPatch = bytecodePatch(
|
||||
SPOOF_CLIENT.title,
|
||||
SPOOF_CLIENT.summary,
|
||||
false,
|
||||
SPOOF_CLIENT.summary
|
||||
) {
|
||||
compatibleWith(
|
||||
Constants.YOUTUBE_MUSIC_PACKAGE_NAME(
|
||||
"6.20.51",
|
||||
"6.29.59",
|
||||
"6.42.55",
|
||||
"6.51.53",
|
||||
"7.16.53",
|
||||
),
|
||||
)
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
versionCheckPatch,
|
||||
blockRequestPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
if (is_7_25_or_greater) {
|
||||
printWarn("\"${SPOOF_CLIENT.title}\" is not supported in this version. Use YouTube Music 7.24.51 or earlier.")
|
||||
return@execute
|
||||
}
|
||||
lateinit var clientInfoReference: Reference
|
||||
lateinit var clientIdReference: Reference
|
||||
lateinit var clientVersionReference: Reference
|
||||
lateinit var deviceBrandReference: Reference
|
||||
lateinit var deviceMakeReference: Reference
|
||||
lateinit var deviceModelReference: Reference
|
||||
lateinit var osNameReference: Reference
|
||||
lateinit var osVersionReference: Reference
|
||||
|
||||
fun MutableMethod.getFieldReference(index: Int) =
|
||||
getInstruction<ReferenceInstruction>(index).reference
|
||||
|
||||
// region Get field references to be used below.
|
||||
|
||||
val (clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) =
|
||||
setPlayerRequestClientTypeFingerprint.matchOrThrow().let { result ->
|
||||
with(result.method) {
|
||||
// Field in the player request object that holds the client info object.
|
||||
val clientInfoField = instructions.find { instruction ->
|
||||
// requestMessage.clientInfo = clientInfoBuilder.build();
|
||||
instruction.opcode == Opcode.IPUT_OBJECT &&
|
||||
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
|
||||
}?.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoField")
|
||||
|
||||
// Client info object's client type field.
|
||||
val clientInfoClientTypeField =
|
||||
getInstruction(result.patternMatch!!.endIndex)
|
||||
.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoClientTypeField")
|
||||
|
||||
val clientInfoVersionIndex = result.stringMatches!!.first().index
|
||||
val clientInfoVersionRegister =
|
||||
getInstruction<OneRegisterInstruction>(clientInfoVersionIndex).registerA
|
||||
val clientInfoClientVersionFieldIndex =
|
||||
indexOfFirstInstructionOrThrow(clientInfoVersionIndex) {
|
||||
setPlayerRequestClientTypeFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val clientInfoIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
(this as TwoRegisterInstruction).registerA == clientInfoVersionRegister
|
||||
getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
|
||||
}
|
||||
val clientIdIndex = it.patternMatch!!.endIndex
|
||||
val dummyClientVersionIndex = it.stringMatches!!.first().index
|
||||
val dummyClientVersionRegister =
|
||||
getInstruction<OneRegisterInstruction>(dummyClientVersionIndex).registerA
|
||||
val clientVersionIndex =
|
||||
indexOfFirstInstructionOrThrow(dummyClientVersionIndex) {
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
(this as TwoRegisterInstruction).registerA == dummyClientVersionRegister
|
||||
}
|
||||
|
||||
// Client info object's client version field.
|
||||
val clientInfoClientVersionField =
|
||||
getInstruction(clientInfoClientVersionFieldIndex)
|
||||
.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoClientVersionField")
|
||||
|
||||
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
|
||||
clientInfoReference =
|
||||
getFieldReference(clientInfoIndex)
|
||||
clientIdReference =
|
||||
getFieldReference(clientIdIndex)
|
||||
clientVersionReference =
|
||||
getFieldReference(clientVersionIndex)
|
||||
}
|
||||
}
|
||||
|
||||
val clientInfoClientModelField =
|
||||
with(createPlayerRequestBodyWithModelFingerprint.methodOrThrow()) {
|
||||
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
|
||||
val clientInfoClientModelIndex =
|
||||
indexOfFirstInstructionOrThrow(indexOfModelInstruction(this)) {
|
||||
fun MutableMethod.getClientInfoIndex(
|
||||
startIndex: Int,
|
||||
reversed: Boolean = false
|
||||
): Int {
|
||||
val filter: Instruction.() -> Boolean = {
|
||||
val reference = getReference<FieldReference>()
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR &&
|
||||
reference.type == "Ljava/lang/String;"
|
||||
}
|
||||
getInstruction<ReferenceInstruction>(clientInfoClientModelIndex).reference
|
||||
return if (reversed) {
|
||||
indexOfFirstInstructionReversedOrThrow(startIndex, filter)
|
||||
} else {
|
||||
indexOfFirstInstructionOrThrow(startIndex, filter)
|
||||
}
|
||||
}
|
||||
|
||||
val clientInfoOsVersionField =
|
||||
with(createPlayerRequestBodyWithVersionReleaseFingerprint.methodOrThrow()) {
|
||||
val buildIndex = indexOfBuildInstruction(this)
|
||||
val clientInfoOsVersionIndex = indexOfFirstInstructionOrThrow(buildIndex - 5) {
|
||||
val reference = getReference<FieldReference>()
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR &&
|
||||
reference.type == "Ljava/lang/String;"
|
||||
}
|
||||
getInstruction<ReferenceInstruction>(clientInfoOsVersionIndex).reference
|
||||
createPlayerRequestBodyWithModelFingerprint.methodOrThrow().apply {
|
||||
val buildManufacturerIndex =
|
||||
indexOfManufacturerInstruction(this)
|
||||
val deviceBrandIndex =
|
||||
getClientInfoIndex(indexOfBrandInstruction(this))
|
||||
val deviceMakeIndex =
|
||||
getClientInfoIndex(buildManufacturerIndex)
|
||||
val deviceModelIndex =
|
||||
getClientInfoIndex(indexOfModelInstruction(this))
|
||||
val chipSetIndex =
|
||||
getClientInfoIndex(buildManufacturerIndex, true)
|
||||
val osNameIndex =
|
||||
getClientInfoIndex(chipSetIndex - 1, true)
|
||||
val osVersionIndex =
|
||||
getClientInfoIndex(indexOfReleaseInstruction(this))
|
||||
|
||||
deviceBrandReference =
|
||||
getFieldReference(deviceBrandIndex)
|
||||
deviceMakeReference =
|
||||
getFieldReference(deviceMakeIndex)
|
||||
deviceModelReference =
|
||||
getFieldReference(deviceModelIndex)
|
||||
osNameReference =
|
||||
getFieldReference(osNameIndex)
|
||||
osVersionReference =
|
||||
getFieldReference(osVersionIndex)
|
||||
}
|
||||
|
||||
// endregion
|
||||
@ -181,31 +194,49 @@ val spoofClientPatch = bytecodePatch(
|
||||
move-result v0
|
||||
if-eqz v0, :disabled
|
||||
|
||||
iget-object v0, p0, $clientInfoField
|
||||
iget-object v0, p0, $clientInfoReference
|
||||
|
||||
# Set client type to the spoofed value.
|
||||
iget v1, v0, $clientInfoClientTypeField
|
||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientTypeId(I)I
|
||||
# Set client id.
|
||||
iget v1, v0, $clientIdReference
|
||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientId(I)I
|
||||
move-result v1
|
||||
iput v1, v0, $clientInfoClientTypeField
|
||||
iput v1, v0, $clientIdReference
|
||||
|
||||
# Set client model to the spoofed value.
|
||||
iget-object v1, v0, $clientInfoClientModelField
|
||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientModel(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $clientInfoClientModelField
|
||||
|
||||
# Set client version to the spoofed value.
|
||||
iget-object v1, v0, $clientInfoClientVersionField
|
||||
# Set client version.
|
||||
iget-object v1, v0, $clientVersionReference
|
||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $clientInfoClientVersionField
|
||||
iput-object v1, v0, $clientVersionReference
|
||||
|
||||
# Set client os version to the spoofed value.
|
||||
iget-object v1, v0, $clientInfoOsVersionField
|
||||
# Set device brand.
|
||||
iget-object v1, v0, $deviceBrandReference
|
||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getDeviceBrand(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $deviceBrandReference
|
||||
|
||||
# Set device make.
|
||||
iget-object v1, v0, $deviceMakeReference
|
||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getDeviceMake(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $deviceMakeReference
|
||||
|
||||
# Set device model.
|
||||
iget-object v1, v0, $deviceModelReference
|
||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getDeviceModel(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $deviceModelReference
|
||||
|
||||
# Set os name.
|
||||
iget-object v1, v0, $osNameReference
|
||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getOsName(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $osNameReference
|
||||
|
||||
# Set os version.
|
||||
iget-object v1, v0, $osVersionReference
|
||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getOsVersion(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $clientInfoOsVersionField
|
||||
iput-object v1, v0, $osVersionReference
|
||||
|
||||
:disabled
|
||||
return-void
|
||||
@ -273,10 +304,17 @@ val spoofClientPatch = bytecodePatch(
|
||||
|
||||
// endregion
|
||||
|
||||
findMethodOrThrow("$PATCHES_PATH/PatchStatus;") {
|
||||
name == "SpoofClient"
|
||||
}.replaceInstruction(
|
||||
0,
|
||||
"const/4 v0, 0x1"
|
||||
)
|
||||
|
||||
addSwitchPreference(
|
||||
CategoryType.MISC,
|
||||
"revanced_spoof_client",
|
||||
"false"
|
||||
"true"
|
||||
)
|
||||
addPreferenceWithIntent(
|
||||
CategoryType.MISC,
|
||||
|
@ -1,67 +0,0 @@
|
||||
package app.revanced.patches.music.utils.fix.streamingdata
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.music.utils.compatibility.Constants.YOUTUBE_MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_STREAMING_DATA
|
||||
import app.revanced.patches.music.utils.settings.CategoryType
|
||||
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
|
||||
import app.revanced.patches.music.utils.settings.addSwitchPreference
|
||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||
import app.revanced.patches.music.video.playerresponse.hookPlayerResponse
|
||||
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
|
||||
import app.revanced.patches.shared.extension.Constants.PATCHES_PATH
|
||||
import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
|
||||
import app.revanced.patches.shared.spoof.streamingdata.baseSpoofStreamingDataPatch
|
||||
import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
|
||||
const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$SPOOF_PATH/SpoofStreamingDataPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val spoofStreamingDataPatch = baseSpoofStreamingDataPatch(
|
||||
{
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
baseSpoofUserAgentPatch(YOUTUBE_MUSIC_PACKAGE_NAME),
|
||||
settingsPatch,
|
||||
playerResponseMethodHookPatch,
|
||||
)
|
||||
},
|
||||
{
|
||||
findMethodOrThrow("$PATCHES_PATH/PatchStatus;") {
|
||||
name == "SpoofStreamingDataMusic"
|
||||
}.replaceInstruction(
|
||||
0,
|
||||
"const/4 v0, 0x1"
|
||||
)
|
||||
|
||||
hookPlayerResponse(
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;)V",
|
||||
true
|
||||
)
|
||||
|
||||
addSwitchPreference(
|
||||
CategoryType.MISC,
|
||||
"revanced_spoof_streaming_data",
|
||||
"true"
|
||||
)
|
||||
addPreferenceWithIntent(
|
||||
CategoryType.MISC,
|
||||
"revanced_spoof_streaming_data_type",
|
||||
"revanced_spoof_streaming_data"
|
||||
)
|
||||
addSwitchPreference(
|
||||
CategoryType.MISC,
|
||||
"revanced_spoof_streaming_data_stats_for_nerds",
|
||||
"true",
|
||||
"revanced_spoof_streaming_data"
|
||||
)
|
||||
|
||||
updatePatchStatus(SPOOF_STREAMING_DATA)
|
||||
|
||||
}
|
||||
)
|
@ -153,10 +153,6 @@ internal enum class PatchList(
|
||||
"Spoof client",
|
||||
"Adds options to spoof the client to allow playback."
|
||||
),
|
||||
SPOOF_STREAMING_DATA(
|
||||
"Spoof streaming data",
|
||||
"Adds options to spoof the streaming data to allow playback."
|
||||
),
|
||||
TRANSLATIONS_FOR_YOUTUBE_MUSIC(
|
||||
"Translations for YouTube Music",
|
||||
"Add translations or remove string resources."
|
||||
|
@ -18,11 +18,19 @@ internal val createPlayerRequestBodyWithModelFingerprint = legacyFingerprint(
|
||||
parameters = emptyList(),
|
||||
opcodes = listOf(Opcode.OR_INT_LIT16),
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfBrandInstruction(method) >= 0 &&
|
||||
indexOfManufacturerInstruction(method) >= 0 &&
|
||||
indexOfModelInstruction(method) >= 0 &&
|
||||
indexOfReleaseInstruction(method) >= 0
|
||||
}
|
||||
)
|
||||
|
||||
fun indexOfBrandInstruction(method: Method) =
|
||||
method.indexOfFieldReference("Landroid/os/Build;->BRAND:Ljava/lang/String;")
|
||||
|
||||
fun indexOfManufacturerInstruction(method: Method) =
|
||||
method.indexOfFieldReference("Landroid/os/Build;->MANUFACTURER:Ljava/lang/String;")
|
||||
|
||||
fun indexOfModelInstruction(method: Method) =
|
||||
method.indexOfFieldReference("Landroid/os/Build;->MODEL:Ljava/lang/String;")
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
package app.revanced.patches.shared.spoof.blockrequest
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
|
||||
import app.revanced.util.fingerprint.matchOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$SPOOF_PATH/BlockRequestPatch;"
|
||||
|
||||
val blockRequestPatch = bytecodePatch(
|
||||
description = "blockRequestPatch"
|
||||
) {
|
||||
execute {
|
||||
// region Block /initplayback requests to fall back to /get_watch requests.
|
||||
|
||||
buildInitPlaybackRequestFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val moveUriStringIndex = it.patternMatch!!.startIndex
|
||||
val targetRegister =
|
||||
getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
moveUriStringIndex + 1,
|
||||
"""
|
||||
invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$targetRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Block /get_watch requests to fall back to /player requests.
|
||||
|
||||
buildPlayerRequestURIFingerprint.methodOrThrow().apply {
|
||||
val invokeToStringIndex = indexOfToStringInstruction(this)
|
||||
val uriRegister =
|
||||
getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
||||
|
||||
addInstructions(
|
||||
invokeToStringIndex,
|
||||
"""
|
||||
invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||
move-result-object v$uriRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package app.revanced.patches.shared.spoof.blockrequest
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal val buildInitPlaybackRequestFingerprint = legacyFingerprint(
|
||||
name = "buildInitPlaybackRequestFingerprint",
|
||||
returnType = "Lorg/chromium/net/UrlRequest\$Builder;",
|
||||
opcodes = listOf(
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IGET_OBJECT, // Moves the request URI string to a register to build the request with.
|
||||
),
|
||||
strings = listOf(
|
||||
"Content-Type",
|
||||
"Range",
|
||||
),
|
||||
)
|
||||
|
||||
internal val buildPlayerRequestURIFingerprint = legacyFingerprint(
|
||||
name = "buildPlayerRequestURIFingerprint",
|
||||
returnType = "Ljava/lang/String;",
|
||||
strings = listOf(
|
||||
"key",
|
||||
"asig",
|
||||
),
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfToStringInstruction(method) >= 0
|
||||
},
|
||||
)
|
||||
|
||||
internal fun indexOfToStringInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>().toString() == "Landroid/net/Uri;->toString()Ljava/lang/String;"
|
||||
}
|
||||
|
@ -1,370 +0,0 @@
|
||||
package app.revanced.patches.shared.spoof.streamingdata
|
||||
|
||||
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.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
|
||||
import app.revanced.patches.shared.formatStreamModelConstructorFingerprint
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
import app.revanced.util.fingerprint.definingClassOrThrow
|
||||
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.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
|
||||
const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$SPOOF_PATH/SpoofStreamingDataPatch;"
|
||||
|
||||
fun baseSpoofStreamingDataPatch(
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||
) = bytecodePatch(
|
||||
name = "Spoof streaming data",
|
||||
description = "Adds options to spoof the streaming data to allow playback."
|
||||
) {
|
||||
block()
|
||||
|
||||
execute {
|
||||
// region Block /initplayback requests to fall back to /get_watch requests.
|
||||
|
||||
buildInitPlaybackRequestFingerprint.matchOrThrow().let {
|
||||
it.method.apply {
|
||||
val moveUriStringIndex = it.patternMatch!!.startIndex
|
||||
val targetRegister =
|
||||
getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
moveUriStringIndex + 1,
|
||||
"""
|
||||
invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$targetRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Block /get_watch requests to fall back to /player requests.
|
||||
|
||||
buildPlayerRequestURIFingerprint.methodOrThrow().apply {
|
||||
val invokeToStringIndex = indexOfToStringInstruction(this)
|
||||
val uriRegister =
|
||||
getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
||||
|
||||
addInstructions(
|
||||
invokeToStringIndex,
|
||||
"""
|
||||
invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||
move-result-object v$uriRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Get replacement streams at player requests.
|
||||
|
||||
buildRequestFingerprint.methodOrThrow().apply {
|
||||
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
|
||||
val urlRegister =
|
||||
getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||
|
||||
val entrySetIndex = indexOfEntrySetInstruction(this)
|
||||
val mapRegister = if (entrySetIndex < 0)
|
||||
urlRegister + 1
|
||||
else
|
||||
getInstruction<FiveRegisterInstruction>(entrySetIndex).registerC
|
||||
|
||||
var smaliInstructions =
|
||||
"invoke-static { v$urlRegister, v$mapRegister }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"fetchStreams(Ljava/lang/String;Ljava/util/Map;)V"
|
||||
|
||||
if (entrySetIndex < 0) smaliInstructions = """
|
||||
move-object/from16 v$mapRegister, p1
|
||||
|
||||
""" + smaliInstructions
|
||||
|
||||
// Copy request headers for streaming data fetch.
|
||||
addInstructions(newRequestBuilderIndex + 2, smaliInstructions)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Replace the streaming data.
|
||||
|
||||
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 calcApproxDurationMsMethodName = "patch_calcApproxDurationMs"
|
||||
val resultClassDef = result.classDef
|
||||
val resultMethodType = resultClassDef.type
|
||||
val setStreamingDataIndex = result.patternMatch!!.startIndex
|
||||
val setStreamingDataField =
|
||||
getInstruction(setStreamingDataIndex).getReference<FieldReference>()
|
||||
.toString()
|
||||
|
||||
val playerProtoClass =
|
||||
getInstruction(setStreamingDataIndex + 1).getReference<FieldReference>()!!.definingClass
|
||||
val protobufClass =
|
||||
protobufClassParseByteBufferFingerprint.definingClassOrThrow()
|
||||
|
||||
val getStreamingDataField = instructions.find { instruction ->
|
||||
instruction.opcode == Opcode.IGET_OBJECT &&
|
||||
instruction.getReference<FieldReference>()?.definingClass == playerProtoClass
|
||||
}?.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find getStreamingDataField")
|
||||
|
||||
val videoDetailsIndex = result.patternMatch!!.endIndex
|
||||
val videoDetailsRegister =
|
||||
getInstruction<TwoRegisterInstruction>(videoDetailsIndex).registerA
|
||||
val videoDetailsClass =
|
||||
getInstruction(videoDetailsIndex).getReference<FieldReference>()!!.type
|
||||
|
||||
addInstruction(
|
||||
videoDetailsIndex + 1,
|
||||
"invoke-direct { p0, v$videoDetailsRegister }, " +
|
||||
"$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
|
||||
)
|
||||
|
||||
result.classDef.methods.add(
|
||||
ImmutableMethod(
|
||||
resultMethodType,
|
||||
setStreamDataMethodName,
|
||||
listOf(
|
||||
ImmutableMethodParameter(
|
||||
videoDetailsClass,
|
||||
annotations,
|
||||
"videoDetails"
|
||||
)
|
||||
),
|
||||
"V",
|
||||
AccessFlags.PRIVATE.value or AccessFlags.FINAL.value,
|
||||
annotations,
|
||||
null,
|
||||
MutableMethodImplementation(9),
|
||||
).toMutable().apply {
|
||||
addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled()Z
|
||||
move-result v0
|
||||
if-eqz v0, :disabled
|
||||
|
||||
# Get video id.
|
||||
iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String;
|
||||
if-eqz v2, :disabled
|
||||
|
||||
# Get streaming data.
|
||||
invoke-static { v2 }, $EXTENSION_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer;
|
||||
move-result-object v3
|
||||
|
||||
if-eqz v3, :disabled
|
||||
|
||||
# Parse streaming data.
|
||||
sget-object v4, $playerProtoClass->a:$playerProtoClass
|
||||
invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass
|
||||
move-result-object v5
|
||||
check-cast v5, $playerProtoClass
|
||||
|
||||
iget-object v6, v5, $getStreamingDataField
|
||||
if-eqz v6, :disabled
|
||||
|
||||
# Caculate approxDurationMs.
|
||||
invoke-direct { p0, v2 }, $resultMethodType->$calcApproxDurationMsMethodName(Ljava/lang/String;)V
|
||||
|
||||
# Set spoofed streaming data.
|
||||
iput-object v6, p0, $setStreamingDataField
|
||||
|
||||
: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
|
||||
""",
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Remove /videoplayback request body to fix playback.
|
||||
// This is needed when using iOS client as streaming data source.
|
||||
|
||||
buildMediaDataSourceFingerprint.methodOrThrow().apply {
|
||||
val targetIndex = instructions.lastIndex
|
||||
|
||||
addInstructions(
|
||||
targetIndex,
|
||||
"""
|
||||
# Field a: Stream uri.
|
||||
# Field c: Http method.
|
||||
# Field d: Post data.
|
||||
move-object/from16 v0, p0
|
||||
iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
|
||||
iget v2, v0, $definingClass->c:I
|
||||
iget-object v3, v0, $definingClass->d:[B
|
||||
invoke-static { v1, v2, v3 }, $EXTENSION_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $definingClass->d:[B
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Append spoof info.
|
||||
|
||||
nerdsStatsVideoFormatBuilderFingerprint.methodOrThrow().apply {
|
||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstructions(
|
||||
index, """
|
||||
invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Fix iOS livestream current time.
|
||||
|
||||
hlsCurrentTimeFingerprint.injectLiteralInstructionBooleanCall(
|
||||
HLS_CURRENT_TIME_FEATURE_FLAG,
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->fixHLSCurrentTime(Z)Z"
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
executeBlock()
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package app.revanced.patches.shared.spoof.streamingdata
|
||||
package app.revanced.patches.youtube.utils.fix.streamingdata
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
@ -15,37 +15,6 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
const val STREAMING_DATA_INTERFACE =
|
||||
"Lcom/google/protos/youtube/api/innertube/StreamingDataOuterClass${'$'}StreamingData;"
|
||||
|
||||
internal val buildInitPlaybackRequestFingerprint = legacyFingerprint(
|
||||
name = "buildInitPlaybackRequestFingerprint",
|
||||
returnType = "Lorg/chromium/net/UrlRequest\$Builder;",
|
||||
opcodes = listOf(
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IGET_OBJECT, // Moves the request URI string to a register to build the request with.
|
||||
),
|
||||
strings = listOf(
|
||||
"Content-Type",
|
||||
"Range",
|
||||
),
|
||||
)
|
||||
|
||||
internal val buildPlayerRequestURIFingerprint = legacyFingerprint(
|
||||
name = "buildPlayerRequestURIFingerprint",
|
||||
returnType = "Ljava/lang/String;",
|
||||
strings = listOf(
|
||||
"key",
|
||||
"asig",
|
||||
),
|
||||
customFingerprint = { method, _ ->
|
||||
indexOfToStringInstruction(method) >= 0
|
||||
},
|
||||
)
|
||||
|
||||
internal fun indexOfToStringInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>().toString() == "Landroid/net/Uri;->toString()Ljava/lang/String;"
|
||||
}
|
||||
|
||||
internal val buildMediaDataSourceFingerprint = legacyFingerprint(
|
||||
name = "buildMediaDataSourceFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
@ -1,29 +1,355 @@
|
||||
package app.revanced.patches.youtube.utils.fix.streamingdata
|
||||
|
||||
import app.revanced.patches.shared.spoof.streamingdata.baseSpoofStreamingDataPatch
|
||||
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.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.shared.extension.Constants.PATCHES_PATH
|
||||
import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
|
||||
import app.revanced.patches.shared.formatStreamModelConstructorFingerprint
|
||||
import app.revanced.patches.shared.spoof.blockrequest.blockRequestPatch
|
||||
import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.YOUTUBE_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_STREAMING_DATA
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.definingClassOrThrow
|
||||
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.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
|
||||
val spoofStreamingDataPatch = baseSpoofStreamingDataPatch(
|
||||
{
|
||||
const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$SPOOF_PATH/SpoofStreamingDataPatch;"
|
||||
|
||||
val spoofStreamingDataPatch = bytecodePatch(
|
||||
SPOOF_STREAMING_DATA.title,
|
||||
SPOOF_STREAMING_DATA.summary
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
baseSpoofUserAgentPatch(YOUTUBE_PACKAGE_NAME),
|
||||
settingsPatch
|
||||
blockRequestPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
// region Get replacement streams at player requests.
|
||||
|
||||
buildRequestFingerprint.methodOrThrow().apply {
|
||||
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
|
||||
val urlRegister =
|
||||
getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||
|
||||
val entrySetIndex = indexOfEntrySetInstruction(this)
|
||||
val mapRegister = if (entrySetIndex < 0)
|
||||
urlRegister + 1
|
||||
else
|
||||
getInstruction<FiveRegisterInstruction>(entrySetIndex).registerC
|
||||
|
||||
var smaliInstructions =
|
||||
"invoke-static { v$urlRegister, v$mapRegister }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"fetchStreams(Ljava/lang/String;Ljava/util/Map;)V"
|
||||
|
||||
if (entrySetIndex < 0) smaliInstructions = """
|
||||
move-object/from16 v$mapRegister, p1
|
||||
|
||||
""" + smaliInstructions
|
||||
|
||||
// Copy request headers for streaming data fetch.
|
||||
addInstructions(newRequestBuilderIndex + 2, smaliInstructions)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Replace the streaming data.
|
||||
|
||||
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 calcApproxDurationMsMethodName = "patch_calcApproxDurationMs"
|
||||
val resultClassDef = result.classDef
|
||||
val resultMethodType = resultClassDef.type
|
||||
val setStreamingDataIndex = result.patternMatch!!.startIndex
|
||||
val setStreamingDataField =
|
||||
getInstruction(setStreamingDataIndex).getReference<FieldReference>()
|
||||
.toString()
|
||||
|
||||
val playerProtoClass =
|
||||
getInstruction(setStreamingDataIndex + 1).getReference<FieldReference>()!!.definingClass
|
||||
val protobufClass =
|
||||
protobufClassParseByteBufferFingerprint.definingClassOrThrow()
|
||||
|
||||
val getStreamingDataField = instructions.find { instruction ->
|
||||
instruction.opcode == Opcode.IGET_OBJECT &&
|
||||
instruction.getReference<FieldReference>()?.definingClass == playerProtoClass
|
||||
}?.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find getStreamingDataField")
|
||||
|
||||
val videoDetailsIndex = result.patternMatch!!.endIndex
|
||||
val videoDetailsRegister =
|
||||
getInstruction<TwoRegisterInstruction>(videoDetailsIndex).registerA
|
||||
val videoDetailsClass =
|
||||
getInstruction(videoDetailsIndex).getReference<FieldReference>()!!.type
|
||||
|
||||
addInstruction(
|
||||
videoDetailsIndex + 1,
|
||||
"invoke-direct { p0, v$videoDetailsRegister }, " +
|
||||
"$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
|
||||
)
|
||||
|
||||
result.classDef.methods.add(
|
||||
ImmutableMethod(
|
||||
resultMethodType,
|
||||
setStreamDataMethodName,
|
||||
listOf(
|
||||
ImmutableMethodParameter(
|
||||
videoDetailsClass,
|
||||
annotations,
|
||||
"videoDetails"
|
||||
)
|
||||
),
|
||||
"V",
|
||||
AccessFlags.PRIVATE.value or AccessFlags.FINAL.value,
|
||||
annotations,
|
||||
null,
|
||||
MutableMethodImplementation(9),
|
||||
).toMutable().apply {
|
||||
addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled()Z
|
||||
move-result v0
|
||||
if-eqz v0, :disabled
|
||||
|
||||
# Get video id.
|
||||
iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String;
|
||||
if-eqz v2, :disabled
|
||||
|
||||
# Get streaming data.
|
||||
invoke-static { v2 }, $EXTENSION_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer;
|
||||
move-result-object v3
|
||||
|
||||
if-eqz v3, :disabled
|
||||
|
||||
# Parse streaming data.
|
||||
sget-object v4, $playerProtoClass->a:$playerProtoClass
|
||||
invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass
|
||||
move-result-object v5
|
||||
check-cast v5, $playerProtoClass
|
||||
|
||||
iget-object v6, v5, $getStreamingDataField
|
||||
if-eqz v6, :disabled
|
||||
|
||||
# Caculate approxDurationMs.
|
||||
invoke-direct { p0, v2 }, $resultMethodType->$calcApproxDurationMsMethodName(Ljava/lang/String;)V
|
||||
|
||||
# Set spoofed streaming data.
|
||||
iput-object v6, p0, $setStreamingDataField
|
||||
|
||||
: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
|
||||
""",
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Remove /videoplayback request body to fix playback.
|
||||
// This is needed when using iOS client as streaming data source.
|
||||
|
||||
buildMediaDataSourceFingerprint.methodOrThrow().apply {
|
||||
val targetIndex = instructions.lastIndex
|
||||
|
||||
addInstructions(
|
||||
targetIndex,
|
||||
"""
|
||||
# Field a: Stream uri.
|
||||
# Field c: Http method.
|
||||
# Field d: Post data.
|
||||
move-object/from16 v0, p0
|
||||
iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
|
||||
iget v2, v0, $definingClass->c:I
|
||||
iget-object v3, v0, $definingClass->d:[B
|
||||
invoke-static { v1, v2, v3 }, $EXTENSION_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $definingClass->d:[B
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Append spoof info.
|
||||
|
||||
nerdsStatsVideoFormatBuilderFingerprint.methodOrThrow().apply {
|
||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstructions(
|
||||
index, """
|
||||
invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Fix iOS livestream current time.
|
||||
|
||||
hlsCurrentTimeFingerprint.injectLiteralInstructionBooleanCall(
|
||||
HLS_CURRENT_TIME_FEATURE_FLAG,
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->fixHLSCurrentTime(Z)Z"
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
findMethodOrThrow("$PATCHES_PATH/PatchStatus;") {
|
||||
name == "SpoofStreamingData"
|
||||
}.replaceInstruction(
|
||||
0,
|
||||
"const/4 v0, 0x1"
|
||||
)
|
||||
|
||||
addPreference(
|
||||
arrayOf(
|
||||
"SETTINGS: SPOOF_STREAMING_DATA"
|
||||
),
|
||||
SPOOF_STREAMING_DATA
|
||||
)
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -76,14 +76,16 @@
|
||||
<item>6.42.55</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_spoof_client_type_entries">
|
||||
<item>@string/revanced_spoof_client_type_entry_ios_music_6_21</item>
|
||||
<item>@string/revanced_spoof_client_type_entry_android_music_5_29</item>
|
||||
<item>@string/revanced_spoof_client_type_entry_android_music_4_27</item>
|
||||
<item>@string/revanced_spoof_client_type_entry_android_music_5_29</item>
|
||||
<item>@string/revanced_spoof_client_type_entry_ios_music_6_21</item>
|
||||
<item>@string/revanced_spoof_client_type_entry_ios_music_7_04</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_spoof_client_type_entry_values">
|
||||
<item>IOS_MUSIC_6_21</item>
|
||||
<item>ANDROID_MUSIC_5_29</item>
|
||||
<item>ANDROID_MUSIC_4_27</item>
|
||||
<item>ANDROID_MUSIC_5_29</item>
|
||||
<item>IOS_MUSIC_6_21</item>
|
||||
<item>IOS_MUSIC_7_04</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_spoof_streaming_data_type_entries">
|
||||
<item>@string/revanced_spoof_streaming_data_type_entry_android_vr</item>
|
||||
|
@ -479,17 +479,7 @@ Info:
|
||||
<string name="revanced_spoof_client_type_entry_android_music_4_27">Android Music 4.27.53</string>
|
||||
<string name="revanced_spoof_client_type_entry_android_music_5_29">Android Music 5.29.53</string>
|
||||
<string name="revanced_spoof_client_type_entry_ios_music_6_21">iOS Music 6.21</string>
|
||||
|
||||
<string name="revanced_spoof_streaming_data_title">Spoof streaming data</string>
|
||||
<string name="revanced_spoof_streaming_data_summary">"Spoof the streaming data to prevent playback issues.
|
||||
|
||||
• When used with 'Spoof client', playback issues may occur."</string>
|
||||
<string name="revanced_spoof_streaming_data_type_title">Default client</string>
|
||||
<string name="revanced_spoof_streaming_data_type_summary">Defines a default client that fetches streaming data.</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">Shows the client used to fetch streaming data in Stats for nerds.</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_android_vr">Android VR</string>
|
||||
<string name="revanced_spoof_streaming_data_type_entry_android_music">Android Music</string>
|
||||
<string name="revanced_spoof_client_type_entry_ios_music_7_04">iOS Music 7.04</string>
|
||||
|
||||
<string name="revanced_default_app_settings_title">Open default app settings</string>
|
||||
<string name="revanced_default_app_settings_summary">To open YouTube Music links in RVX Music, enable Open supported links and enable all the Supported web addresses.</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user