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.general.ChangeStartPagePatch.StartPage;
|
||||||
import app.revanced.extension.music.patches.misc.AlbumMusicVideoPatch.RedirectType;
|
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.patches.utils.PatchStatus;
|
||||||
import app.revanced.extension.music.sponsorblock.SponsorBlockSettings;
|
import app.revanced.extension.music.sponsorblock.SponsorBlockSettings;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
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 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 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 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
|
// PreferenceScreen: Return YouTube Dislike
|
||||||
public static final BooleanSetting RYD_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE);
|
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,
|
SETTINGS_IMPORT_EXPORT.key,
|
||||||
SPOOF_APP_VERSION_TARGET.key,
|
SPOOF_APP_VERSION_TARGET.key,
|
||||||
SPOOF_CLIENT_TYPE.key,
|
SPOOF_CLIENT_TYPE.key,
|
||||||
SPOOF_STREAMING_DATA_TYPE.key,
|
|
||||||
RETURN_YOUTUBE_USERNAME_ABOUT.key,
|
RETURN_YOUTUBE_USERNAME_ABOUT.key,
|
||||||
RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT.key,
|
RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT.key,
|
||||||
RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY.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.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_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.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.settings.Setting.getSettingFromPath;
|
||||||
import static app.revanced.extension.shared.utils.ResourceUtils.getStringArray;
|
import static app.revanced.extension.shared.utils.ResourceUtils.getStringArray;
|
||||||
import static app.revanced.extension.shared.utils.StringRef.str;
|
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(DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE)
|
||||||
|| settings.equals(RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT)
|
|| settings.equals(RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT)
|
||||||
|| settings.equals(SPOOF_CLIENT_TYPE)
|
|| settings.equals(SPOOF_CLIENT_TYPE)
|
||||||
|| settings.equals(SPOOF_STREAMING_DATA_TYPE)
|
|
||||||
) {
|
) {
|
||||||
ResettableListPreference.showDialog(mActivity, enumSetting, 0);
|
ResettableListPreference.showDialog(mActivity, enumSetting, 0);
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,13 @@ public class PatchStatus {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean SpoofStreamingDataMusic() {
|
public static boolean SpoofClient() {
|
||||||
// Replace this with true If the Spoof streaming data patch succeeds in YouTube Music
|
// 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;
|
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
|
package app.revanced.extension.shared.patches.client
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import app.revanced.extension.shared.patches.PatchStatus
|
|
||||||
import app.revanced.extension.shared.settings.BaseSettings
|
import app.revanced.extension.shared.settings.BaseSettings
|
||||||
import org.apache.commons.lang3.ArrayUtils
|
import org.apache.commons.lang3.ArrayUtils
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -9,7 +8,7 @@ import java.util.Locale
|
|||||||
/**
|
/**
|
||||||
* Used to fetch streaming data.
|
* Used to fetch streaming data.
|
||||||
*/
|
*/
|
||||||
object AppClient {
|
object YouTubeAppClient {
|
||||||
// IOS
|
// IOS
|
||||||
/**
|
/**
|
||||||
* Video not playable: Paid / Movie / Private / Age-restricted
|
* 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 ANDROID_SDK_VERSION_ANDROID_VR = "32"
|
||||||
private const val BUILD_ID_ANDROID_VR = "SQ3A.220605.009.A1"
|
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(
|
private val USER_AGENT_ANDROID_VR = androidUserAgent(
|
||||||
packageName = PACKAGE_NAME_ANDROID_VR,
|
packageName = PACKAGE_NAME_ANDROID_VR,
|
||||||
@ -137,7 +135,6 @@ object AppClient {
|
|||||||
private const val ANDROID_SDK_VERSION_ANDROID_UNPLUGGED = "34"
|
private const val ANDROID_SDK_VERSION_ANDROID_UNPLUGGED = "34"
|
||||||
private const val BUILD_ID_ANDROID_UNPLUGGED = "UTT3.240625.001.K5"
|
private const val BUILD_ID_ANDROID_UNPLUGGED = "UTT3.240625.001.K5"
|
||||||
private const val GMS_CORE_VERSION_CODE_ANDROID_UNPLUGGED = "244336107"
|
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(
|
private val USER_AGENT_ANDROID_UNPLUGGED = androidUserAgent(
|
||||||
packageName = PACKAGE_NAME_ANDROID_UNPLUGGED,
|
packageName = PACKAGE_NAME_ANDROID_UNPLUGGED,
|
||||||
@ -166,7 +163,6 @@ object AppClient {
|
|||||||
private const val ANDROID_SDK_VERSION_ANDROID_CREATOR = "35"
|
private const val ANDROID_SDK_VERSION_ANDROID_CREATOR = "35"
|
||||||
private const val BUILD_ID_ANDROID_CREATOR = "AP3A.241005.015.A2"
|
private const val BUILD_ID_ANDROID_CREATOR = "AP3A.241005.015.A2"
|
||||||
private const val GMS_CORE_VERSION_CODE_ANDROID_CREATOR = "244738035"
|
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(
|
private val USER_AGENT_ANDROID_CREATOR = androidUserAgent(
|
||||||
packageName = PACKAGE_NAME_ANDROID_CREATOR,
|
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.
|
* 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'
|
* 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> {
|
fun availableClientTypes(preferredClient: ClientType): Array<ClientType> {
|
||||||
val availableClientTypes = if (PatchStatus.SpoofStreamingDataMusic())
|
val availableClientTypes = ClientType.CLIENT_ORDER_TO_USE_YOUTUBE
|
||||||
ClientType.CLIENT_ORDER_TO_USE_YOUTUBE_MUSIC
|
|
||||||
else
|
|
||||||
ClientType.CLIENT_ORDER_TO_USE_YOUTUBE
|
|
||||||
|
|
||||||
if (ArrayUtils.contains(availableClientTypes, preferredClient)) {
|
if (ArrayUtils.contains(availableClientTypes, preferredClient)) {
|
||||||
val clientToUse: Array<ClientType?> = arrayOfNulls(availableClientTypes.size)
|
val clientToUse: Array<ClientType?> = arrayOfNulls(availableClientTypes.size)
|
||||||
@ -400,19 +360,6 @@ object AppClient {
|
|||||||
"iOS Force AVC"
|
"iOS Force AVC"
|
||||||
else
|
else
|
||||||
"iOS"
|
"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 {
|
companion object {
|
||||||
@ -424,11 +371,6 @@ object AppClient {
|
|||||||
IOS,
|
IOS,
|
||||||
ANDROID_VR_NO_AUTH,
|
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.
|
* Used to fetch video information.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
object WebClient {
|
object YouTubeWebClient {
|
||||||
/**
|
/**
|
||||||
* This user agent does not require a PoToken in [ClientType.MWEB]
|
* 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
|
* 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;
|
import app.revanced.extension.music.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class SpoofClientPatch {
|
public class SpoofClientPatch extends BlockRequestPatch {
|
||||||
private static final ClientType CLIENT_TYPE = Settings.SPOOF_CLIENT_TYPE.get();
|
private static final ClientType CLIENT_TYPE = Settings.SPOOF_CLIENT_TYPE.get();
|
||||||
public static final boolean SPOOF_CLIENT = Settings.SPOOF_CLIENT.get();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static int getClientTypeId(int originalClientTypeId) {
|
public static int getClientId(int original) {
|
||||||
if (SPOOF_CLIENT) {
|
if (SPOOF_CLIENT) {
|
||||||
return CLIENT_TYPE.id;
|
return CLIENT_TYPE.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalClientTypeId;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String getClientVersion(String originalClientVersion) {
|
public static String getClientVersion(String original) {
|
||||||
if (SPOOF_CLIENT) {
|
if (SPOOF_CLIENT) {
|
||||||
return CLIENT_TYPE.clientVersion;
|
return CLIENT_TYPE.clientVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalClientVersion;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* 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) {
|
if (SPOOF_CLIENT) {
|
||||||
return CLIENT_TYPE.deviceModel;
|
return CLIENT_TYPE.deviceModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalClientModel;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* 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) {
|
if (SPOOF_CLIENT) {
|
||||||
return CLIENT_TYPE.osVersion;
|
return CLIENT_TYPE.osVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalOsVersion;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String getUserAgent(String originalUserAgent) {
|
public static String getUserAgent(String original) {
|
||||||
if (SPOOF_CLIENT) {
|
if (SPOOF_CLIENT) {
|
||||||
return CLIENT_TYPE.userAgent;
|
return CLIENT_TYPE.userAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalUserAgent;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -1,11 +1,8 @@
|
|||||||
package app.revanced.extension.shared.patches.spoof;
|
package app.revanced.extension.shared.patches.spoof;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingDataMusic;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@ -13,7 +10,7 @@ import java.util.Collections;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
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.patches.spoof.requests.StreamingDataRequest;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
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;
|
import app.revanced.extension.shared.utils.Utils;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class SpoofStreamingDataPatch {
|
public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
||||||
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();
|
|
||||||
private static final String PO_TOKEN =
|
private static final String PO_TOKEN =
|
||||||
BaseSettings.SPOOF_STREAMING_DATA_PO_TOKEN.get();
|
BaseSettings.SPOOF_STREAMING_DATA_PO_TOKEN.get();
|
||||||
private static final String VISITOR_DATA =
|
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.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@ -120,73 +62,20 @@ public class SpoofStreamingDataPatch {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static volatile String auth = "";
|
/**
|
||||||
private static volatile Map<String, String> requestHeader;
|
* Parameters causing playback issues.
|
||||||
|
*/
|
||||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
private static final String[] PATH_NO_VIDEO_ID = {
|
||||||
|
"ad_break", // This request fetches a list of times when ads can be displayed.
|
||||||
private static final String[] REQUEST_HEADER_KEYS = {
|
"get_drm_license", // Waiting for a paid video to start.
|
||||||
AUTHORIZATION_HEADER,
|
"heartbeat", // This request determines whether to pause playback when the user is AFK.
|
||||||
"X-GOOG-API-FORMAT-VERSION",
|
"refresh", // Waiting for a livestream to start.
|
||||||
"X-Goog-Visitor-Id"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void fetchStreams(String url, Map<String, String> requestHeaders) {
|
public static void fetchStreams(String url, Map<String, String> requestHeaders) {
|
||||||
setRequestHeaders(requestHeaders);
|
|
||||||
|
|
||||||
if (SPOOF_STREAMING_DATA) {
|
if (SPOOF_STREAMING_DATA) {
|
||||||
try {
|
try {
|
||||||
Uri uri = Uri.parse(url);
|
Uri uri = Uri.parse(url);
|
||||||
@ -195,11 +84,7 @@ public class SpoofStreamingDataPatch {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'get_drm_license' has no video id and appears to happen when waiting for a paid video to start.
|
if (Utils.containsAny(path, PATH_NO_VIDEO_ID)) {
|
||||||
// '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")) {
|
|
||||||
Logger.printDebug(() -> "Ignoring path: " + path);
|
Logger.printDebug(() -> "Ignoring path: " + path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -262,7 +147,7 @@ public class SpoofStreamingDataPatch {
|
|||||||
* Called after {@link #getStreamingData(String)}.
|
* Called after {@link #getStreamingData(String)}.
|
||||||
*/
|
*/
|
||||||
public static void setApproxDurationMs(String videoId, long approxDurationMs) {
|
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);
|
approxDurationMsMap.put(videoId, approxDurationMs);
|
||||||
Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs);
|
Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs);
|
||||||
}
|
}
|
||||||
@ -284,7 +169,7 @@ public class SpoofStreamingDataPatch {
|
|||||||
* Called after {@link #getStreamingData(String)}.
|
* Called after {@link #getStreamingData(String)}.
|
||||||
*/
|
*/
|
||||||
public static long getApproxDurationMs(String videoId) {
|
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);
|
final Long approxDurationMs = approxDurationMsMap.get(videoId);
|
||||||
if (approxDurationMs != null) {
|
if (approxDurationMs != null) {
|
||||||
Logger.printDebug(() -> "Replacing video length: " + approxDurationMs + " for videoId: " + videoId);
|
Logger.printDebug(() -> "Replacing video length: " + approxDurationMs + " for videoId: " + videoId);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package app.revanced.extension.shared.patches.spoof.requests
|
package app.revanced.extension.shared.patches.spoof.requests
|
||||||
|
|
||||||
import app.revanced.extension.shared.patches.client.AppClient
|
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.client.WebClient
|
import app.revanced.extension.shared.patches.client.YouTubeWebClient
|
||||||
import app.revanced.extension.shared.requests.Requester
|
import app.revanced.extension.shared.requests.Requester
|
||||||
import app.revanced.extension.shared.requests.Route
|
import app.revanced.extension.shared.requests.Route
|
||||||
import app.revanced.extension.shared.requests.Route.CompiledRoute
|
import app.revanced.extension.shared.requests.Route.CompiledRoute
|
||||||
@ -69,7 +69,7 @@ object PlayerRoutes {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun createApplicationRequestBody(
|
fun createApplicationRequestBody(
|
||||||
clientType: AppClient.ClientType,
|
clientType: YouTubeAppClient.ClientType,
|
||||||
videoId: String,
|
videoId: String,
|
||||||
playlistId: String? = null,
|
playlistId: String? = null,
|
||||||
botGuardPoToken: String = "",
|
botGuardPoToken: String = "",
|
||||||
@ -130,7 +130,7 @@ object PlayerRoutes {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun createWebInnertubeBody(
|
fun createWebInnertubeBody(
|
||||||
clientType: WebClient.ClientType,
|
clientType: YouTubeWebClient.ClientType,
|
||||||
videoId: String
|
videoId: String
|
||||||
): ByteArray {
|
): ByteArray {
|
||||||
val innerTubeBody = JSONObject()
|
val innerTubeBody = JSONObject()
|
||||||
@ -161,7 +161,7 @@ object PlayerRoutes {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getPlayerResponseConnectionFromRoute(
|
fun getPlayerResponseConnectionFromRoute(
|
||||||
route: CompiledRoute,
|
route: CompiledRoute,
|
||||||
clientType: AppClient.ClientType
|
clientType: YouTubeAppClient.ClientType
|
||||||
): HttpURLConnection {
|
): HttpURLConnection {
|
||||||
return getPlayerResponseConnectionFromRoute(
|
return getPlayerResponseConnectionFromRoute(
|
||||||
route,
|
route,
|
||||||
@ -174,7 +174,7 @@ object PlayerRoutes {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getPlayerResponseConnectionFromRoute(
|
fun getPlayerResponseConnectionFromRoute(
|
||||||
route: CompiledRoute,
|
route: CompiledRoute,
|
||||||
clientType: WebClient.ClientType
|
clientType: YouTubeWebClient.ClientType
|
||||||
): HttpURLConnection {
|
): HttpURLConnection {
|
||||||
return getPlayerResponseConnectionFromRoute(
|
return getPlayerResponseConnectionFromRoute(
|
||||||
route,
|
route,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package app.revanced.extension.shared.patches.spoof.requests
|
package app.revanced.extension.shared.patches.spoof.requests
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.AppClient
|
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.client.AppClient.availableClientTypes
|
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_STREAMING_DATA
|
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.createApplicationRequestBody
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.getPlayerResponseConnectionFromRoute
|
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.getPlayerResponseConnectionFromRoute
|
||||||
@ -93,16 +92,16 @@ class StreamingDataRequest private constructor(
|
|||||||
"X-GOOG-API-FORMAT-VERSION",
|
"X-GOOG-API-FORMAT-VERSION",
|
||||||
VISITOR_ID_HEADER
|
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()
|
BaseSettings.SPOOF_STREAMING_DATA_TYPE.get()
|
||||||
|
|
||||||
private val CLIENT_ORDER_TO_USE: Array<AppClient.ClientType> =
|
private val CLIENT_ORDER_TO_USE: Array<YouTubeAppClient.ClientType> =
|
||||||
availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
|
YouTubeAppClient.availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
|
||||||
|
|
||||||
private val DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH: Boolean =
|
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(
|
private fun send(
|
||||||
clientType: AppClient.ClientType,
|
clientType: YouTubeAppClient.ClientType,
|
||||||
videoId: String,
|
videoId: String,
|
||||||
playerHeaders: Map<String, String>,
|
playerHeaders: Map<String, String>,
|
||||||
visitorId: String,
|
visitorId: String,
|
||||||
@ -279,7 +278,7 @@ class StreamingDataRequest private constructor(
|
|||||||
} else {
|
} else {
|
||||||
BufferedInputStream(connection.inputStream).use { inputStream ->
|
BufferedInputStream(connection.inputStream).use { inputStream ->
|
||||||
ByteArrayOutputStream().use { stream ->
|
ByteArrayOutputStream().use { stream ->
|
||||||
val buffer = ByteArray(4096)
|
val buffer = ByteArray(2048)
|
||||||
var bytesRead: Int
|
var bytesRead: Int
|
||||||
while ((inputStream.read(buffer)
|
while ((inputStream.read(buffer)
|
||||||
.also { bytesRead = it }) >= 0
|
.also { bytesRead = it }) >= 0
|
||||||
|
@ -5,7 +5,8 @@ import static java.lang.Boolean.TRUE;
|
|||||||
import static app.revanced.extension.shared.patches.PatchStatus.HideFullscreenAdsDefaultBoolean;
|
import static app.revanced.extension.shared.patches.PatchStatus.HideFullscreenAdsDefaultBoolean;
|
||||||
|
|
||||||
import app.revanced.extension.shared.patches.ReturnYouTubeUsernamePatch.DisplayFormat;
|
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;
|
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);
|
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.
|
* 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 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 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
|
* @noinspection DeprecatedIsStillUsed
|
||||||
*/
|
*/
|
||||||
|
@ -2,7 +2,7 @@ package app.revanced.extension.youtube.patches.general.requests
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.annotation.GuardedBy
|
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.patches.spoof.requests.PlayerRoutes
|
||||||
import app.revanced.extension.shared.requests.Requester
|
import app.revanced.extension.shared.requests.Requester
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
@ -81,7 +81,7 @@ class VideoDetailsRequest private constructor(
|
|||||||
|
|
||||||
private fun sendRequest(videoId: String): JSONObject? {
|
private fun sendRequest(videoId: String): JSONObject? {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
val clientType = WebClient.ClientType.MWEB
|
val clientType = YouTubeWebClient.ClientType.MWEB
|
||||||
val clientTypeName = clientType.name
|
val clientTypeName = clientType.name
|
||||||
Logger.printDebug { "Fetching video details request for: $videoId, using client: $clientTypeName" }
|
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 android.annotation.SuppressLint
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.AppClient
|
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
||||||
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.patches.spoof.requests.PlayerRoutes
|
||||||
import app.revanced.extension.shared.requests.Requester
|
import app.revanced.extension.shared.requests.Requester
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
@ -119,7 +119,7 @@ class MusicRequest private constructor(
|
|||||||
Objects.requireNonNull(videoId)
|
Objects.requireNonNull(videoId)
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
val clientType = AppClient.ClientType.ANDROID_VR
|
val clientType = YouTubeAppClient.ClientType.ANDROID_VR
|
||||||
val clientTypeName = clientType.name
|
val clientTypeName = clientType.name
|
||||||
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
|
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ class MusicRequest private constructor(
|
|||||||
Objects.requireNonNull(videoId)
|
Objects.requireNonNull(videoId)
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
val clientType = WebClient.ClientType.MWEB
|
val clientType = YouTubeWebClient.ClientType.MWEB
|
||||||
val clientTypeName = clientType.name
|
val clientTypeName = clientType.name
|
||||||
Logger.printDebug { "Fetching microformat request for: $videoId, using client: $clientTypeName" }
|
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.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
|
||||||
import app.revanced.util.fingerprint.legacyFingerprint
|
import app.revanced.util.fingerprint.legacyFingerprint
|
||||||
import app.revanced.util.getReference
|
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
|
||||||
import app.revanced.util.or
|
import app.revanced.util.or
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.iface.Method
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|
||||||
|
|
||||||
internal val createPlayerRequestBodyFingerprint = legacyFingerprint(
|
internal val createPlayerRequestBodyFingerprint = legacyFingerprint(
|
||||||
name = "createPlayerRequestBodyFingerprint",
|
name = "createPlayerRequestBodyFingerprint",
|
||||||
@ -22,26 +18,6 @@ internal val createPlayerRequestBodyFingerprint = legacyFingerprint(
|
|||||||
strings = listOf("ms"),
|
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(
|
internal val setPlayerRequestClientTypeFingerprint = legacyFingerprint(
|
||||||
name = "setPlayerRequestClientTypeFingerprint",
|
name = "setPlayerRequestClientTypeFingerprint",
|
||||||
opcodes = listOf(
|
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.addInstruction
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
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.PatchException
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
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.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
import app.revanced.patches.music.utils.compatibility.Constants
|
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||||
import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
|
|
||||||
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_CLIENT
|
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_CLIENT
|
||||||
import app.revanced.patches.music.utils.playbackSpeedBottomSheetFingerprint
|
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.CategoryType
|
||||||
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||||
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
|
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
|
||||||
import app.revanced.patches.music.utils.settings.addSwitchPreference
|
import app.revanced.patches.music.utils.settings.addSwitchPreference
|
||||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||||
|
import app.revanced.patches.shared.spoof.blockrequest.blockRequestPatch
|
||||||
import app.revanced.patches.shared.createPlayerRequestBodyWithModelFingerprint
|
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.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.matchOrThrow
|
||||||
import app.revanced.util.fingerprint.methodOrThrow
|
import app.revanced.util.fingerprint.methodOrThrow
|
||||||
import app.revanced.util.fingerprint.mutableClassOrThrow
|
import app.revanced.util.fingerprint.mutableClassOrThrow
|
||||||
@ -32,109 +36,118 @@ import app.revanced.util.or
|
|||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
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.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
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.instruction.TwoRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.Reference
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||||
|
|
||||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
"$MISC_PATH/SpoofClientPatch;"
|
"$SPOOF_PATH/SpoofClientPatch;"
|
||||||
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
|
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
|
||||||
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
|
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val spoofClientPatch = bytecodePatch(
|
val spoofClientPatch = bytecodePatch(
|
||||||
SPOOF_CLIENT.title,
|
SPOOF_CLIENT.title,
|
||||||
SPOOF_CLIENT.summary,
|
SPOOF_CLIENT.summary
|
||||||
false,
|
|
||||||
) {
|
) {
|
||||||
compatibleWith(
|
compatibleWith(COMPATIBLE_PACKAGE)
|
||||||
Constants.YOUTUBE_MUSIC_PACKAGE_NAME(
|
|
||||||
"6.20.51",
|
|
||||||
"6.29.59",
|
|
||||||
"6.42.55",
|
|
||||||
"6.51.53",
|
|
||||||
"7.16.53",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
dependsOn(
|
dependsOn(
|
||||||
settingsPatch,
|
settingsPatch,
|
||||||
versionCheckPatch,
|
blockRequestPatch,
|
||||||
)
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
if (is_7_25_or_greater) {
|
lateinit var clientInfoReference: Reference
|
||||||
printWarn("\"${SPOOF_CLIENT.title}\" is not supported in this version. Use YouTube Music 7.24.51 or earlier.")
|
lateinit var clientIdReference: Reference
|
||||||
return@execute
|
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.
|
// region Get field references to be used below.
|
||||||
|
|
||||||
val (clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) =
|
setPlayerRequestClientTypeFingerprint.matchOrThrow().let {
|
||||||
setPlayerRequestClientTypeFingerprint.matchOrThrow().let { result ->
|
it.method.apply {
|
||||||
with(result.method) {
|
val clientInfoIndex = indexOfFirstInstructionOrThrow {
|
||||||
// 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) {
|
|
||||||
opcode == Opcode.IPUT_OBJECT &&
|
|
||||||
(this as TwoRegisterInstruction).registerA == clientInfoVersionRegister
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client info object's client version field.
|
|
||||||
val clientInfoClientVersionField =
|
|
||||||
getInstruction(clientInfoClientVersionFieldIndex)
|
|
||||||
.getReference<FieldReference>()
|
|
||||||
?: throw PatchException("Could not find clientInfoClientVersionField")
|
|
||||||
|
|
||||||
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)) {
|
|
||||||
val reference = getReference<FieldReference>()
|
|
||||||
opcode == Opcode.IPUT_OBJECT &&
|
|
||||||
reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR &&
|
|
||||||
reference.type == "Ljava/lang/String;"
|
|
||||||
}
|
|
||||||
getInstruction<ReferenceInstruction>(clientInfoClientModelIndex).reference
|
|
||||||
}
|
|
||||||
|
|
||||||
val clientInfoOsVersionField =
|
|
||||||
with(createPlayerRequestBodyWithVersionReleaseFingerprint.methodOrThrow()) {
|
|
||||||
val buildIndex = indexOfBuildInstruction(this)
|
|
||||||
val clientInfoOsVersionIndex = indexOfFirstInstructionOrThrow(buildIndex - 5) {
|
|
||||||
val reference = getReference<FieldReference>()
|
|
||||||
opcode == Opcode.IPUT_OBJECT &&
|
opcode == Opcode.IPUT_OBJECT &&
|
||||||
reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR &&
|
getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
|
||||||
reference.type == "Ljava/lang/String;"
|
|
||||||
}
|
}
|
||||||
getInstruction<ReferenceInstruction>(clientInfoOsVersionIndex).reference
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
clientInfoReference =
|
||||||
|
getFieldReference(clientInfoIndex)
|
||||||
|
clientIdReference =
|
||||||
|
getFieldReference(clientIdIndex)
|
||||||
|
clientVersionReference =
|
||||||
|
getFieldReference(clientVersionIndex)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;"
|
||||||
|
}
|
||||||
|
return if (reversed) {
|
||||||
|
indexOfFirstInstructionReversedOrThrow(startIndex, filter)
|
||||||
|
} else {
|
||||||
|
indexOfFirstInstructionOrThrow(startIndex, filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// endregion
|
||||||
|
|
||||||
@ -181,31 +194,49 @@ val spoofClientPatch = bytecodePatch(
|
|||||||
move-result v0
|
move-result v0
|
||||||
if-eqz v0, :disabled
|
if-eqz v0, :disabled
|
||||||
|
|
||||||
iget-object v0, p0, $clientInfoField
|
iget-object v0, p0, $clientInfoReference
|
||||||
|
|
||||||
# Set client type to the spoofed value.
|
# Set client id.
|
||||||
iget v1, v0, $clientInfoClientTypeField
|
iget v1, v0, $clientIdReference
|
||||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientTypeId(I)I
|
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientId(I)I
|
||||||
move-result v1
|
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.
|
# Set client version.
|
||||||
iget-object v1, v0, $clientInfoClientVersionField
|
iget-object v1, v0, $clientVersionReference
|
||||||
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
|
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
|
||||||
move-result-object v1
|
move-result-object v1
|
||||||
iput-object v1, v0, $clientInfoClientVersionField
|
iput-object v1, v0, $clientVersionReference
|
||||||
|
|
||||||
# Set client os version to the spoofed value.
|
# Set device brand.
|
||||||
iget-object v1, v0, $clientInfoOsVersionField
|
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;
|
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getOsVersion(Ljava/lang/String;)Ljava/lang/String;
|
||||||
move-result-object v1
|
move-result-object v1
|
||||||
iput-object v1, v0, $clientInfoOsVersionField
|
iput-object v1, v0, $osVersionReference
|
||||||
|
|
||||||
:disabled
|
:disabled
|
||||||
return-void
|
return-void
|
||||||
@ -273,10 +304,17 @@ val spoofClientPatch = bytecodePatch(
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
findMethodOrThrow("$PATCHES_PATH/PatchStatus;") {
|
||||||
|
name == "SpoofClient"
|
||||||
|
}.replaceInstruction(
|
||||||
|
0,
|
||||||
|
"const/4 v0, 0x1"
|
||||||
|
)
|
||||||
|
|
||||||
addSwitchPreference(
|
addSwitchPreference(
|
||||||
CategoryType.MISC,
|
CategoryType.MISC,
|
||||||
"revanced_spoof_client",
|
"revanced_spoof_client",
|
||||||
"false"
|
"true"
|
||||||
)
|
)
|
||||||
addPreferenceWithIntent(
|
addPreferenceWithIntent(
|
||||||
CategoryType.MISC,
|
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",
|
"Spoof client",
|
||||||
"Adds options to spoof the client to allow playback."
|
"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(
|
||||||
"Translations for YouTube Music",
|
"Translations for YouTube Music",
|
||||||
"Add translations or remove string resources."
|
"Add translations or remove string resources."
|
||||||
|
@ -18,11 +18,19 @@ internal val createPlayerRequestBodyWithModelFingerprint = legacyFingerprint(
|
|||||||
parameters = emptyList(),
|
parameters = emptyList(),
|
||||||
opcodes = listOf(Opcode.OR_INT_LIT16),
|
opcodes = listOf(Opcode.OR_INT_LIT16),
|
||||||
customFingerprint = { method, _ ->
|
customFingerprint = { method, _ ->
|
||||||
indexOfModelInstruction(method) >= 0 &&
|
indexOfBrandInstruction(method) >= 0 &&
|
||||||
|
indexOfManufacturerInstruction(method) >= 0 &&
|
||||||
|
indexOfModelInstruction(method) >= 0 &&
|
||||||
indexOfReleaseInstruction(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) =
|
fun indexOfModelInstruction(method: Method) =
|
||||||
method.indexOfFieldReference("Landroid/os/Build;->MODEL:Ljava/lang/String;")
|
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.fingerprint.legacyFingerprint
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
@ -15,37 +15,6 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|||||||
const val STREAMING_DATA_INTERFACE =
|
const val STREAMING_DATA_INTERFACE =
|
||||||
"Lcom/google/protos/youtube/api/innertube/StreamingDataOuterClass${'$'}StreamingData;"
|
"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(
|
internal val buildMediaDataSourceFingerprint = legacyFingerprint(
|
||||||
name = "buildMediaDataSourceFingerprint",
|
name = "buildMediaDataSourceFingerprint",
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
@ -1,29 +1,355 @@
|
|||||||
package app.revanced.patches.youtube.utils.fix.streamingdata
|
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.shared.spoof.useragent.baseSpoofUserAgentPatch
|
||||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
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.compatibility.Constants.YOUTUBE_PACKAGE_NAME
|
||||||
import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_STREAMING_DATA
|
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.ResourceUtils.addPreference
|
||||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
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;"
|
||||||
compatibleWith(COMPATIBLE_PACKAGE)
|
|
||||||
|
|
||||||
dependsOn(
|
val spoofStreamingDataPatch = bytecodePatch(
|
||||||
baseSpoofUserAgentPatch(YOUTUBE_PACKAGE_NAME),
|
SPOOF_STREAMING_DATA.title,
|
||||||
settingsPatch
|
SPOOF_STREAMING_DATA.summary
|
||||||
|
) {
|
||||||
|
compatibleWith(COMPATIBLE_PACKAGE)
|
||||||
|
|
||||||
|
dependsOn(
|
||||||
|
settingsPatch,
|
||||||
|
baseSpoofUserAgentPatch(YOUTUBE_PACKAGE_NAME),
|
||||||
|
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(
|
addPreference(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"SETTINGS: SPOOF_STREAMING_DATA"
|
"SETTINGS: SPOOF_STREAMING_DATA"
|
||||||
),
|
),
|
||||||
SPOOF_STREAMING_DATA
|
SPOOF_STREAMING_DATA
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
@ -76,14 +76,16 @@
|
|||||||
<item>6.42.55</item>
|
<item>6.42.55</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="revanced_spoof_client_type_entries">
|
<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_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>
|
||||||
<string-array name="revanced_spoof_client_type_entry_values">
|
<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_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>
|
||||||
<string-array name="revanced_spoof_streaming_data_type_entries">
|
<string-array name="revanced_spoof_streaming_data_type_entries">
|
||||||
<item>@string/revanced_spoof_streaming_data_type_entry_android_vr</item>
|
<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_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_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_client_type_entry_ios_music_6_21">iOS Music 6.21</string>
|
||||||
|
<string name="revanced_spoof_client_type_entry_ios_music_7_04">iOS Music 7.04</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_default_app_settings_title">Open default app settings</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>
|
<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