Skip to content

Commit

Permalink
YT 17.40.40: disable in-video ads & enable bg playback
Browse files Browse the repository at this point in the history
  • Loading branch information
wanam committed Oct 14, 2022
1 parent 97fc624 commit 4ede7df
Show file tree
Hide file tree
Showing 16 changed files with 314 additions and 315 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
# YouTubeAdAway
Xposed module to block YouTube ads on the official YouTube apps (+TV, Music, Games, Kids, Go)
Xposed module to block YouTube in-video ads and enable background playback on the official YouTube app
Tested against YouTube 17.40.40, but it should support newer updates.
Feel free to raise an issue if a newer YouTube update is not supported, and please make sure you provide your xposed logs.

YouTube videos ad cards are not hidden, and there is not ETA on when it will be supported.
You are more than welcome to contribute with pull requests!
19 changes: 0 additions & 19 deletions YoutubeAdAway.iml

This file was deleted.

37 changes: 21 additions & 16 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
apply plugin: 'com.android.application'
plugins {
id 'com.android.application'
}

android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
compileSdk 31

defaultConfig {
applicationId "ma.wanam.youtubeadaway"
minSdkVersion 9
targetSdkVersion 26
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
minSdk 27
targetSdk 31
versionCode 500
versionName "5.0.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

repositories {
jcenter();
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
provided 'de.robv.android.xposed:api:82'
}
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'

// used for debugging only
// implementation 'org.apache.commons:commons-lang3:3.12.0'
}
27 changes: 11 additions & 16 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ma.wanam.youtubeadaway"
android:versionCode="433"
android:versionName="4.3.3">

<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="27" />
package="ma.wanam.youtubeadaway">

<application
android:allowBackup="true"
android:directBootAware="true"
android:icon="@drawable/ic_launcher"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.Dialog">
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposedminversion"
android:value="40+" />
android:value="82+" />
<meta-data
android:name="xposeddescription"
android:value="YouTube AdAway by Wanam" />

<activity
android:name=".MainActivity"
android:name="ma.wanam.youtubeadaway.MainActivity"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<!--<category android:name="android.intent.category.LAUNCHER"/>-->
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>

</manifest>
</manifest>
193 changes: 118 additions & 75 deletions app/src/main/java/ma/wanam/youtubeadaway/BFAsync.java
Original file line number Diff line number Diff line change
@@ -1,119 +1,162 @@
package ma.wanam.youtubeadaway;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.regex.Pattern;

import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class BFAsync extends AsyncTask<ClassLoader, Void, Boolean> {
private static ClassLoader cl;
private static boolean found = false;
public class BFAsync extends AsyncTask<XC_LoadPackage.LoadPackageParam, Void, Boolean> {
private boolean DEBUG = BuildConfig.DEBUG;
private volatile boolean sigAdFound = false;
private volatile boolean sigBgFound = false;

private void debug(String msg) {
if (BuildConfig.DEBUG) {
XposedBridge.log(msg);
}
@Override
protected Boolean doInBackground(XC_LoadPackage.LoadPackageParam... params) {
ClassLoader cl = params[0].classLoader;

boolean foundBGClass = bruteForceBGP(cl);
boolean foundInVideoAds = bruteForceInVideoAds(cl);

return foundBGClass && foundInVideoAds;
}

private void debug(Throwable msg) {
if (BuildConfig.DEBUG) {
XposedBridge.log(msg);
private boolean bruteForceInVideoAds(ClassLoader cl) {
Instant start = Instant.now();
for (char a2 = 'a'; a2 <= 'z'; a2++) {
for (char a3 = 'a'; a3 <= 'z'; a3++) {
for (char a4 = 'a'; a4 <= 'z'; a4++) {
findAndHookInvideoAds('a', a2, a3, a4, cl);
if (sigAdFound) {
XposedBridge.log("In-Video ads hooks applied in " + Duration.between(start, Instant.now()).getSeconds() + " seconds!");
return true;
}
}
}
}
return false;
}

@Override
protected Boolean doInBackground(ClassLoader... params) {
cl = params[0];

private boolean bruteForceBGP(ClassLoader cl) {
Instant start = Instant.now();
for (char a1 = 'z'; a1 >= 'a'; a1--) {
for (char a2 = 'z'; a2 >= 'a'; a2--) {
for (char a3 = 'z'; a3 >= 'a'; a3--) {
findAndHookYouTubeAds(a1, a2, a3);
findAndHookVideoBGP(a1, a2, a3, cl);
if (sigBgFound) {
XposedBridge.log("Video BG playback hooks applied in " + Duration.between(start, Instant.now()).getSeconds() + " seconds!");
return true;
}
}
}
}

return found;
return false;
}


/**
* @param a1
* @param a2
* @param a3
* @return true if a hook was found
*/
private void findAndHookYouTubeAds(char a1, char a2, char a3) {
Class<?> classObj;
Class<?> paramObj;
private void findAndHookInvideoAds(char a1, char a2, char a3, char a4, ClassLoader cl) {
Class<?> aClass;
Field[] fields;
Method[] methods;
final String lCRegex = "[a-z]+";
final Pattern lCPatern = Pattern.compile(lCRegex);

try {
classObj = XposedHelpers.findClass(new StringBuffer().append(a1).append(a2).append(a3).toString(), cl);
aClass = XposedHelpers.findClass(new StringBuffer().append(a1).append(a2).append(a3).append(a4).toString(), cl);
fields = aClass.getDeclaredFields();
methods = aClass.getDeclaredMethods();
} catch (Throwable e1) {
return;
}

try {
XposedHelpers.findConstructorExact(classObj);
XposedHelpers.findConstructorExact(classObj, Parcel.class);
if (XposedHelpers.findFirstFieldByExactType(classObj, Parcelable.Creator.class).getName().equals("CREATOR")) {
try {
Method[] methods = classObj.getDeclaredMethods();
for (Method m : methods) {
if (m.getName().equals("b") && m.getReturnType().equals(boolean.class)
&& m.getParameterTypes().length == 1) {
paramObj = m.getParameterTypes()[0];

if (lCPatern.matcher(paramObj.getName()).matches()) {
Method mClass = XposedHelpers.findMethodExact(classObj, "a", paramObj);

if (mClass.getReturnType().equals(boolean.class)) {
try {
XposedBridge.hookAllConstructors(classObj, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Field[] fields = param.thisObject.getClass().getDeclaredFields();
for (Field f : fields) {
if (f.getType().equals(long.class)) {
long timestamp = Calendar.getInstance().getTimeInMillis();
debug("class: " + param.thisObject.getClass().getName());
debug("set expiry timestamp: " + f.getName() + " = " + timestamp);
XposedHelpers.setLongField(param.thisObject, f.getName(), timestamp);
break;
}
}
}
});

found = true;

debug("YouTube AdAway: Successfully hooked ads wrapper " + classObj.getName()
+ " param=" + paramObj.getName());
} catch (Throwable e) {
debug("YouTube AdAway: Failed to hook " + classObj.getName()
+ " param=" + paramObj.getName() + " error: " + e);
}
}
if (!sigAdFound) {
sigAdFound = fields.length < 10 && (int) Arrays.asList(fields).parallelStream().filter(field -> field.getType().equals(Executor.class)
|| field.getType().equals(LinkedBlockingQueue.class)
|| field.getType().equals(Runnable.class)).count() == 3;

if (sigAdFound) {
Optional<Method> fMethod = Arrays.asList(methods).parallelStream().filter(method -> method.getParameterTypes().length == 1
&& method.getParameterTypes()[0].equals(boolean.class)
&& method.getReturnType().equals(void.class)
&& java.lang.reflect.Modifier.isFinal(method.getModifiers())
).findAny();

sigAdFound = sigAdFound && fMethod.isPresent();
if (sigAdFound) {
XposedBridge.hookMethod(fMethod.get(), new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = false;
}
}
});
XposedBridge.log("Hooked ad class: " + aClass.getName() + "." + fMethod.get().getName());
}
}
}

} catch (Throwable e) {
debug("YouTube AdAway: Failed to hook " + classObj.getName() + " methods!");
debug(e);
} catch (Throwable e) {
XposedBridge.log("YouTube AdAway: Failed to hook in-video ads class: " + aClass.getName());
XposedBridge.log(e);
}
}

private void findAndHookVideoBGP(char a1, char a2, char a3, ClassLoader cl) {
Class<?> aClass;
Field[] fields;
Method[] methods;
final String lCRegex = "[a-z]+";
final Pattern lCPatern = Pattern.compile(lCRegex);

try {
aClass = XposedHelpers.findClass(new StringBuffer().append(a1).append(a2).append(a3).toString(), cl);
fields = aClass.getDeclaredFields();
methods = aClass.getDeclaredMethods();
} catch (Throwable e1) {
return;
}

try {
if (!sigBgFound) {
Class cListenableFuture = XposedHelpers.findClass("com.google.common.util.concurrent.ListenableFuture", cl);
Optional<Method> fMethod = Arrays.asList(methods).parallelStream().filter(method -> method.getParameterTypes().length == 2
&& method.getParameterTypes()[0].equals(Context.class)
&& method.getParameterTypes()[1].equals(Executor.class)
&& method.getReturnType().equals(cListenableFuture)
&& java.lang.reflect.Modifier.isStatic(method.getModifiers())
).findAny();

sigBgFound = fMethod.isPresent();

if (sigBgFound) {
fMethod = Arrays.asList(methods).parallelStream().filter(method -> method.getParameterTypes().length == 1
&& method.getReturnType().equals(boolean.class)
&& lCPatern.matcher(method.getName()).matches()
&& java.lang.reflect.Modifier.isStatic(method.getModifiers())
).findFirst();

sigBgFound = sigBgFound && fMethod.isPresent();
if (sigBgFound) {
XposedBridge.hookMethod(fMethod.get(), XC_MethodReplacement.returnConstant(true));
XposedBridge.log("Hooked bg class: " + aClass.getName() + "." + fMethod.get().getName());
}
}
}
} catch (Throwable e) {
XposedBridge.log("YouTube AdAway: Failed to hook video bg playback class: " + aClass.getName());
XposedBridge.log(e);
}
}

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/ma/wanam/youtubeadaway/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ protected void onCreate(Bundle savedInstanceState) {
+ " "
+ (XChecker.isEnabled() ? res.getString(R.string.module_active) : res
.getString(R.string.module_inactive));
TextView tvStatus = ((TextView) findViewById(R.id.moduleStatus));
TextView tvStatus = ((TextView) findViewById(id.moduleStatus));
tvStatus.setText(status);
tvStatus.setTextColor((XChecker.isEnabled() ? Color.GREEN : Color.RED));

Expand Down
Loading

0 comments on commit 4ede7df

Please sign in to comment.