diff --git a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/cleardisplay/RememberClearDisplayPatch.java b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/cleardisplay/RememberClearDisplayPatch.java index e436b5dcd0..bcaf96af33 100644 --- a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/cleardisplay/RememberClearDisplayPatch.java +++ b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/cleardisplay/RememberClearDisplayPatch.java @@ -1,13 +1,26 @@ package app.revanced.extension.tiktok.cleardisplay; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.tiktok.settings.Settings; @SuppressWarnings("unused") public class RememberClearDisplayPatch { + private static volatile Boolean lastLoggedState; + public static boolean getClearDisplayState() { - return Settings.CLEAR_DISPLAY.get(); + boolean state = Settings.CLEAR_DISPLAY.get(); + if (BaseSettings.DEBUG.get() && (lastLoggedState == null || lastLoggedState != state)) { + lastLoggedState = state; + Logger.printInfo(() -> "[ReVanced ClearDisplay] get state=" + state); + } + return state; } public static void rememberClearDisplayState(boolean newState) { + if (BaseSettings.DEBUG.get()) { + boolean oldState = Settings.CLEAR_DISPLAY.get(); + Logger.printInfo(() -> "[ReVanced ClearDisplay] remember state " + oldState + " -> " + newState); + } Settings.CLEAR_DISPLAY.save(newState); } } diff --git a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/download/DownloadsPatch.java b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/download/DownloadsPatch.java index c55d62878c..039c1e8914 100644 --- a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/download/DownloadsPatch.java +++ b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/download/DownloadsPatch.java @@ -1,14 +1,29 @@ package app.revanced.extension.tiktok.download; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.tiktok.settings.Settings; @SuppressWarnings("unused") public class DownloadsPatch { + private static volatile String lastLoggedPath; + private static volatile Boolean lastLoggedRemoveWatermark; + public static String getDownloadPath() { - return Settings.DOWNLOAD_PATH.get(); + String path = Settings.DOWNLOAD_PATH.get(); + if (BaseSettings.DEBUG.get() && (lastLoggedPath == null || !lastLoggedPath.equals(path))) { + lastLoggedPath = path; + Logger.printInfo(() -> "[ReVanced Downloads] download_path=\"" + path + "\""); + } + return path; } public static boolean shouldRemoveWatermark() { - return Settings.DOWNLOAD_WATERMARK.get(); + boolean removeWatermark = Settings.DOWNLOAD_WATERMARK.get(); + if (BaseSettings.DEBUG.get() && (lastLoggedRemoveWatermark == null || lastLoggedRemoveWatermark != removeWatermark)) { + lastLoggedRemoveWatermark = removeWatermark; + Logger.printInfo(() -> "[ReVanced Downloads] remove_watermark=" + removeWatermark); + } + return removeWatermark; } } diff --git a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/FeedItemsFilter.java b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/FeedItemsFilter.java index 6b361bb618..f37c7e940a 100644 --- a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/FeedItemsFilter.java +++ b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/FeedItemsFilter.java @@ -1,11 +1,16 @@ package app.revanced.extension.tiktok.feedfilter; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.settings.BaseSettings; +import app.revanced.extension.tiktok.settings.Settings; import com.ss.android.ugc.aweme.feed.model.Aweme; +import com.ss.android.ugc.aweme.feed.model.AwemeStatistics; import com.ss.android.ugc.aweme.feed.model.FeedItemList; import com.ss.android.ugc.aweme.follow.presenter.FollowFeedList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; public final class FeedItemsFilter { private static final List FILTERS = List.of( @@ -18,37 +23,150 @@ public final class FeedItemsFilter { new ShopFilter() ); + private static final int MAX_NULL_ITEMS_LOGS = 3; + private static final AtomicInteger feedItemListNullItemsLogCount = new AtomicInteger(); + private static final AtomicInteger followFeedListNullItemsLogCount = new AtomicInteger(); + public static void filter(FeedItemList feedItemList) { - filterFeedList(feedItemList.items, item -> item); + boolean verbose = BaseSettings.DEBUG.get(); + if (feedItemList == null || feedItemList.items == null) { + if (verbose) { + logNullItems("FeedItemList", feedItemListNullItemsLogCount); + } + return; + } + if (verbose) { + debugLogBatch("FeedItemList", feedItemList.items); + } + filterFeedList("FeedItemList", feedItemList.items, item -> item, verbose); } public static void filter(FollowFeedList followFeedList) { - filterFeedList(followFeedList.mItems, feed -> (feed != null) ? feed.aweme : null); + boolean verbose = BaseSettings.DEBUG.get(); + if (followFeedList == null || followFeedList.mItems == null) { + if (verbose) { + logNullItems("FollowFeedList", followFeedListNullItemsLogCount); + } + return; + } + if (verbose) { + debugLogBatch("FollowFeedList", followFeedList.mItems); + } + filterFeedList("FollowFeedList", followFeedList.mItems, feed -> (feed != null) ? feed.aweme : null, verbose); } - private static void filterFeedList(List list, AwemeExtractor extractor) { + private static void filterFeedList( + String source, + List list, + AwemeExtractor extractor, + boolean verbose + ) { + if (list == null) return; + // Could be simplified with removeIf() but requires Android 7.0+ while TikTok supports 4.0+. + int initialSize = list.size(); + int removed = 0; Iterator iterator = list.iterator(); while (iterator.hasNext()) { T container = iterator.next(); Aweme item = extractor.extract(container); - if (item != null && shouldFilter(item)) { + if (item == null) { + continue; + } + + String reason = getFilterReason(item); + logItem(item, reason, verbose); + + if (reason != null) { + removed++; iterator.remove(); } } + + if (verbose) { + final int removedFinal = removed; + Logger.printInfo(() -> "[ReVanced FeedFilter] filter(" + source + "): size " + initialSize + " -> " + list.size() + + " (removed=" + removedFinal + ", verbose=" + verbose + ")"); + } } private static boolean shouldFilter(Aweme item) { + return getFilterReason(item) != null; + } + + private static String getFilterReason(Aweme item) { for (IFilter filter : FILTERS) { if (filter.getEnabled() && filter.getFiltered(item)) { - return true; + return filter.getClass().getSimpleName(); } } - return false; + return null; + } + + private static void logNullItems(String source, AtomicInteger counter) { + int count = counter.getAndIncrement(); + if (count < MAX_NULL_ITEMS_LOGS) { + Logger.printInfo(() -> "[ReVanced FeedFilter] filter(" + source + "): items=null"); + } else if (count == MAX_NULL_ITEMS_LOGS) { + Logger.printInfo(() -> "[ReVanced FeedFilter] filter(" + source + "): items=null (further logs suppressed)"); + } + } + + private static void debugLogBatch(String source, List list) { + int size = list == null ? -1 : list.size(); + Logger.printInfo(() -> + "[ReVanced FeedFilter] filter(" + source + "): size=" + size + + " remove_ads=" + Settings.REMOVE_ADS.get() + + " hide_shop=" + Settings.HIDE_SHOP.get() + + " hide_live=" + Settings.HIDE_LIVE.get() + + " hide_story=" + Settings.HIDE_STORY.get() + + " hide_image=" + Settings.HIDE_IMAGE.get() + + " min_max_views=\"" + Settings.MIN_MAX_VIEWS.get() + "\"" + + " min_max_likes=\"" + Settings.MIN_MAX_LIKES.get() + "\"" + ); + } + + private static void logItem(Aweme item, String reason, boolean verbose) { + if (!verbose) return; + + String shareUrl = item.getShareUrl(); + if (shareUrl != null && shareUrl.length() > 140) { + shareUrl = shareUrl.substring(0, 140) + "..."; + } + + String finalShareUrl = shareUrl; + Logger.printInfo(() -> { + long playCount = -1; + long likeCount = -1; + AwemeStatistics statistics = item.getStatistics(); + if (statistics != null) { + playCount = statistics.getPlayCount(); + likeCount = statistics.getDiggCount(); + } + + var imageInfos = item.getImageInfos(); + boolean isImage = imageInfos != null && !imageInfos.isEmpty(); + boolean isPhotoMode = item.getPhotoModeImageInfo() != null || item.getPhotoModeTextInfo() != null; + boolean isLive = item.getLiveId() != 0 || item.getLiveType() != null; + + return "[ReVanced FeedFilter] item" + + " aid=" + item.getAid() + + " ad=" + item.isAd() + + " promo=" + item.isWithPromotionalMusic() + + " live=" + isLive + + " liveReplay=" + item.isLiveReplay() + + " story=" + item.getIsTikTokStory() + + " image=" + isImage + + " photoMode=" + isPhotoMode + + " playCount=" + playCount + + " likeCount=" + likeCount + + " shareUrl=" + (finalShareUrl == null ? "null" : "\"" + finalShareUrl + "\"") + + " => " + (reason == null ? "KEEP" : "FILTER(" + reason + ")"); + }); } @FunctionalInterface interface AwemeExtractor { Aweme extract(T source); } -} \ No newline at end of file +} diff --git a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/ImageVideoFilter.java b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/ImageVideoFilter.java index ed3e7cdb92..4370b49996 100644 --- a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/ImageVideoFilter.java +++ b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/ImageVideoFilter.java @@ -11,6 +11,10 @@ public class ImageVideoFilter implements IFilter { @Override public boolean getFiltered(Aweme item) { - return item.isImage() || item.isPhotoMode(); + // TikTok 43.6.2: Aweme no longer exposes isImage()/isPhotoMode(). + var imageInfos = item.getImageInfos(); + boolean isImage = imageInfos != null && !imageInfos.isEmpty(); + boolean isPhotoMode = item.getPhotoModeImageInfo() != null || item.getPhotoModeTextInfo() != null; + return isImage || isPhotoMode; } } diff --git a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/LiveFilter.java b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/LiveFilter.java index db6ab0af06..ea05a8a0cd 100644 --- a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/LiveFilter.java +++ b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/LiveFilter.java @@ -11,6 +11,7 @@ public class LiveFilter implements IFilter { @Override public boolean getFiltered(Aweme item) { - return item.isLive() || item.isLiveReplay(); + // TikTok 43.6.2: Aweme no longer exposes isLive(), use liveId/liveType instead. + return item.getLiveId() != 0 || item.isLiveReplay() || item.getLiveType() != null; } } diff --git a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/ShopFilter.java b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/ShopFilter.java index f134dc1bdc..09f0019419 100644 --- a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/ShopFilter.java +++ b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/feedfilter/ShopFilter.java @@ -12,6 +12,7 @@ public class ShopFilter implements IFilter { @Override public boolean getFiltered(Aweme item) { - return item.getShareUrl().contains(SHOP_INFO); + String shareUrl = item.getShareUrl(); + return shareUrl != null && shareUrl.contains(SHOP_INFO); } } diff --git a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/settings/TikTokActivityHook.java b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/settings/TikTokActivityHook.java index b9d6bc8ecb..56ec659690 100644 --- a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/settings/TikTokActivityHook.java +++ b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/settings/TikTokActivityHook.java @@ -43,8 +43,11 @@ public class TikTokActivityHook { * @return Whether the settings menu should be initialized. */ public static boolean initialize(AdPersonalizationActivity base) { - Bundle extras = base.getIntent().getExtras(); - if (extras != null && !extras.getBoolean("revanced", false)) return false; + Intent intent = base.getIntent(); + Bundle extras = intent.getExtras(); + if ((extras == null || !extras.getBoolean("revanced", false)) && !"revanced_settings".equals(intent.getAction())) { + return false; + } SettingsStatus.load(); diff --git a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/settings/preference/categories/ExtensionPreferenceCategory.java b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/settings/preference/categories/ExtensionPreferenceCategory.java index 7383a5582c..707eda5e72 100644 --- a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/settings/preference/categories/ExtensionPreferenceCategory.java +++ b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/settings/preference/categories/ExtensionPreferenceCategory.java @@ -4,6 +4,8 @@ import android.content.Context; import android.preference.PreferenceScreen; import app.revanced.extension.shared.settings.BaseSettings; +import app.revanced.extension.shared.settings.preference.ClearLogBufferPreference; +import app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference; import app.revanced.extension.tiktok.settings.preference.ReVancedTikTokAboutPreference; import app.revanced.extension.tiktok.settings.preference.TogglePreference; @@ -34,5 +36,15 @@ public class ExtensionPreferenceCategory extends ConditionalPreferenceCategory { "Show extension debug log.", BaseSettings.DEBUG )); + + var exportLogs = new ExportLogToClipboardPreference(context); + exportLogs.setTitle("Export debug logs"); + exportLogs.setSummary("Copy ReVanced debug logs to clipboard."); + addPreference(exportLogs); + + var clearLogs = new ClearLogBufferPreference(context); + clearLogs.setTitle("Clear debug logs"); + clearLogs.setSummary("Clear stored ReVanced debug logs."); + addPreference(clearLogs); } } diff --git a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/share/ShareUrlSanitizer.java b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/share/ShareUrlSanitizer.java index 5d09c10c0b..416827cfa4 100644 --- a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/share/ShareUrlSanitizer.java +++ b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/share/ShareUrlSanitizer.java @@ -24,6 +24,17 @@ public final class ShareUrlSanitizer { return url; } - return sanitizer.sanitizeUrlString(url); + String sanitized = sanitizer.sanitizeUrlString(url); + if (BaseSettings.DEBUG.get() && sanitized != null && !sanitized.equals(url)) { + Logger.printInfo(() -> "[ReVanced SanitizeShareUrl] " + + truncate(url) + " -> " + truncate(sanitized)); + } + return sanitized; + } + + private static String truncate(String url) { + if (url == null) return "null"; + if (url.length() <= 160) return "\"" + url + "\""; + return "\"" + url.substring(0, 160) + "...\""; } } diff --git a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/speed/PlaybackSpeedPatch.java b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/speed/PlaybackSpeedPatch.java index 3b078ab896..8dbee52f7e 100644 --- a/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/speed/PlaybackSpeedPatch.java +++ b/extensions/tiktok/src/main/java/app/revanced/extension/tiktok/speed/PlaybackSpeedPatch.java @@ -1,13 +1,26 @@ package app.revanced.extension.tiktok.speed; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.tiktok.settings.Settings; public class PlaybackSpeedPatch { + private static volatile float lastLoggedSpeed = Float.NaN; + public static void rememberPlaybackSpeed(float newSpeed) { + if (BaseSettings.DEBUG.get()) { + float oldSpeed = Settings.REMEMBERED_SPEED.get(); + Logger.printInfo(() -> "[ReVanced PlaybackSpeed] remember speed " + oldSpeed + " -> " + newSpeed); + } Settings.REMEMBERED_SPEED.save(newSpeed); } public static float getPlaybackSpeed() { - return Settings.REMEMBERED_SPEED.get(); + float speed = Settings.REMEMBERED_SPEED.get(); + if (BaseSettings.DEBUG.get() && Float.compare(lastLoggedSpeed, speed) != 0) { + lastLoggedSpeed = speed; + Logger.printInfo(() -> "[ReVanced PlaybackSpeed] get speed=" + speed); + } + return speed; } } diff --git a/extensions/tiktok/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java b/extensions/tiktok/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java index a950b09057..ea381b4141 100644 --- a/extensions/tiktok/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java +++ b/extensions/tiktok/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java @@ -1,16 +1,26 @@ package com.ss.android.ugc.aweme.feed.model; +import java.util.List; + //Dummy class public class Aweme { + public String getAid() { + throw new UnsupportedOperationException("Stub"); + } + public boolean isAd() { throw new UnsupportedOperationException("Stub"); } - public boolean isLive() { + public boolean isLiveReplay() { throw new UnsupportedOperationException("Stub"); } - public boolean isLiveReplay() { + public long getLiveId() { + throw new UnsupportedOperationException("Stub"); + } + + public String getLiveType() { throw new UnsupportedOperationException("Stub"); } @@ -22,11 +32,15 @@ public class Aweme { throw new UnsupportedOperationException("Stub"); } - public boolean isImage() { + public List getImageInfos() { throw new UnsupportedOperationException("Stub"); } - public boolean isPhotoMode() { + public PhotoModeImageInfo getPhotoModeImageInfo() { + throw new UnsupportedOperationException("Stub"); + } + + public PhotoModeTextInfo getPhotoModeTextInfo() { throw new UnsupportedOperationException("Stub"); } diff --git a/extensions/tiktok/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/PhotoModeImageInfo.java b/extensions/tiktok/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/PhotoModeImageInfo.java new file mode 100644 index 0000000000..67a6e999fc --- /dev/null +++ b/extensions/tiktok/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/PhotoModeImageInfo.java @@ -0,0 +1,6 @@ +package com.ss.android.ugc.aweme.feed.model; + +// Dummy class +public class PhotoModeImageInfo { +} + diff --git a/extensions/tiktok/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/PhotoModeTextInfo.java b/extensions/tiktok/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/PhotoModeTextInfo.java new file mode 100644 index 0000000000..35a0dbc69d --- /dev/null +++ b/extensions/tiktok/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/PhotoModeTextInfo.java @@ -0,0 +1,6 @@ +package com.ss.android.ugc.aweme.feed.model; + +// Dummy class +public class PhotoModeTextInfo { +} + diff --git a/patches/api/patches.api b/patches/api/patches.api index abe4c40271..5fd0cd44d8 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1292,6 +1292,10 @@ public final class app/revanced/patches/tiktok/misc/login/fixgoogle/FixGoogleLog public static final fun getFixGoogleLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/tiktok/misc/settings/EnableOpenDebugPatchKt { + public static final fun getEnableOpenDebugPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/tiktok/misc/settings/SettingsPatchKt { public static final fun getSettingsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/FeedFilterPatch.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/FeedFilterPatch.kt index 0f1227e4d3..faba5357ce 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/FeedFilterPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/FeedFilterPatch.kt @@ -1,10 +1,9 @@ package app.revanced.patches.tiktok.feedfilter import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.instructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.tiktok.misc.extension.sharedExtensionPatch -import app.revanced.patches.tiktok.misc.settings.settingsPatch import app.revanced.patches.tiktok.misc.settings.settingsStatusLoadFingerprint import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -19,31 +18,60 @@ val feedFilterPatch = bytecodePatch( ) { dependsOn( sharedExtensionPatch, - settingsPatch, ) compatibleWith( - "com.ss.android.ugc.trill"("36.5.4"), - "com.zhiliaoapp.musically"("36.5.4"), + "com.ss.android.ugc.trill"("43.6.2"), + "com.zhiliaoapp.musically"("43.6.2"), ) execute { - arrayOf( - feedApiServiceLIZFingerprint.method to "$EXTENSION_CLASS_DESCRIPTOR->filter(Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;)V", - followFeedFingerprint.method to "$EXTENSION_CLASS_DESCRIPTOR->filter(Lcom/ss/android/ugc/aweme/follow/presenter/FollowFeedList;)V" - ).forEach { (method, filterSignature) -> - val returnInstruction = method.instructions.first { it.opcode == Opcode.RETURN_OBJECT } - val register = (returnInstruction as OneRegisterInstruction).registerA - method.addInstruction( - returnInstruction.location.index, - "invoke-static { v$register }, $filterSignature" - ) - } - settingsStatusLoadFingerprint.method.addInstruction( 0, "invoke-static {}, Lapp/revanced/extension/tiktok/settings/SettingsStatus;->enableFeedFilter()V", ) + + // Hook into the model getter, as TikTok feed data is no longer guaranteed to go through + // FeedApiService.fetchFeedList() on 43.6.2 (e.g., cache pipelines). + feedItemListGetItemsFingerprint.method.let { method -> + val returnIndices = method.implementation!!.instructions.withIndex() + .filter { it.value.opcode == Opcode.RETURN_OBJECT } + .map { it.index } + .toList() + + returnIndices.asReversed().forEach { returnIndex -> + method.addInstructions( + returnIndex, + """ + invoke-static {p0}, $EXTENSION_CLASS_DESCRIPTOR->filter(Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;)V + nop + """, + ) + } + } + + arrayOf( + followFeedFingerprint.method to "$EXTENSION_CLASS_DESCRIPTOR->filter(Lcom/ss/android/ugc/aweme/follow/presenter/FollowFeedList;)V" + ).forEach { (method, filterSignature) -> + val returnIndices = method.implementation!!.instructions.withIndex() + .filter { it.value.opcode == Opcode.RETURN_OBJECT } + .map { it.index } + .toList() + + returnIndices.asReversed().forEach { returnIndex -> + val register = (method.implementation!!.instructions[returnIndex] as OneRegisterInstruction).registerA + + method.addInstructions( + returnIndex, + """ + if-eqz v$register, :revanced_skip_filter_$returnIndex + invoke-static/range { v$register .. v$register }, $filterSignature + :revanced_skip_filter_$returnIndex + nop + """, + ) + } + } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/Fingerprints.kt index f85dd2d072..a8376eddd2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/feedfilter/Fingerprints.kt @@ -2,21 +2,31 @@ package app.revanced.patches.tiktok.feedfilter import app.revanced.patcher.fingerprint import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode -internal val feedApiServiceLIZFingerprint = fingerprint { +internal val feedApiLIZIZFingerprint = fingerprint { + // TikTok 43.6.2: Lcom/ss/android/ugc/aweme/feed/api/FeedApi;->LIZIZ(LX/0Qft;)Lcom/ss/android/ugc/aweme/feed/model/FeedItemList; + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + returns("Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;") + parameters("LX/0Qft;") custom { method, classDef -> - classDef.endsWith("/FeedApiService;") && method.name == "fetchFeedList" + classDef.endsWith("/FeedApi;") && method.name == "LIZIZ" + } +} + +internal val feedItemListGetItemsFingerprint = fingerprint { + // TikTok 43.6.2: Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;->getItems()Ljava/util/List; + accessFlags(AccessFlags.PUBLIC) + returns("Ljava/util/List;") + custom { method, classDef -> + classDef.endsWith("/FeedItemList;") && method.name == "getItems" && method.parameterTypes.isEmpty() } } internal val followFeedFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) returns("Lcom/ss/android/ugc/aweme/follow/presenter/FollowFeedList;") - strings("getFollowFeedList") - opcodes( - Opcode.INVOKE_INTERFACE_RANGE, - Opcode.MOVE_RESULT_OBJECT, - Opcode.INVOKE_INTERFACE - ) -} \ No newline at end of file + custom { method, _ -> + // TikTok 43.6.2: LX/*;->LIZ(LX/*;LX/*;)Lcom/ss/android/ugc/aweme/follow/presenter/FollowFeedList; + method.parameterTypes.size == 2 + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/cleardisplay/RememberClearDisplayPatch.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/cleardisplay/RememberClearDisplayPatch.kt index 13c829e625..8ed94f777d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/cleardisplay/RememberClearDisplayPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/cleardisplay/RememberClearDisplayPatch.kt @@ -16,8 +16,8 @@ val rememberClearDisplayPatch = bytecodePatch( description = "Remembers the clear display configurations in between videos.", ) { compatibleWith( - "com.ss.android.ugc.trill"("36.5.4"), - "com.zhiliaoapp.musically"("36.5.4"), + "com.ss.android.ugc.trill"("43.6.2"), + "com.zhiliaoapp.musically"("43.6.2"), ) execute { diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/DownloadsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/DownloadsPatch.kt index e6894b521e..9f87c03f87 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/DownloadsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/DownloadsPatch.kt @@ -7,7 +7,6 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.tiktok.misc.extension.sharedExtensionPatch -import app.revanced.patches.tiktok.misc.settings.settingsPatch import app.revanced.patches.tiktok.misc.settings.settingsStatusLoadFingerprint import app.revanced.util.findInstructionIndicesReversedOrThrow import app.revanced.util.getReference @@ -25,15 +24,19 @@ val downloadsPatch = bytecodePatch( ) { dependsOn( sharedExtensionPatch, - settingsPatch, ) compatibleWith( - "com.ss.android.ugc.trill"("36.5.4"), - "com.zhiliaoapp.musically"("36.5.4"), + "com.ss.android.ugc.trill"("43.6.2"), + "com.zhiliaoapp.musically"("43.6.2"), ) execute { + settingsStatusLoadFingerprint.method.addInstruction( + 0, + "invoke-static {}, Lapp/revanced/extension/tiktok/settings/SettingsStatus;->enableDownload()V", + ) + aclCommonShareFingerprint.method.returnEarly(0) aclCommonShare2Fingerprint.method.returnEarly(2) @@ -74,10 +77,5 @@ val downloadsPatch = bytecodePatch( ) } } - - settingsStatusLoadFingerprint.method.addInstruction( - 0, - "invoke-static {}, Lapp/revanced/extension/tiktok/settings/SettingsStatus;->enableDownload()V", - ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/Fingerprints.kt index 221036bb96..321991310d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/Fingerprints.kt @@ -9,9 +9,11 @@ internal val getSpeedFingerprint = fingerprint { } } -internal val setSpeedFingerprint = fingerprint { +internal val speedOptionEnabledFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) - returns("V") - parameters("Ljava/lang/String;", "Lcom/ss/android/ugc/aweme/feed/model/Aweme;", "F") - strings("enterFrom") + returns("Z") + parameters("Lcom/ss/android/ugc/aweme/feed/model/Aweme;") + custom { method, classDef -> + classDef.type == "LX/0MbX;" && method.name == "LIZ" + } } diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/PlaybackSpeedPatch.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/PlaybackSpeedPatch.kt index d59626ee76..a56d9918a3 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/PlaybackSpeedPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/PlaybackSpeedPatch.kt @@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.tiktok.misc.extension.sharedExtensionPatch import app.revanced.patches.tiktok.shared.getEnterFromFingerprint import app.revanced.patches.tiktok.shared.onRenderFirstFrameFingerprint import app.revanced.util.getReference @@ -17,54 +18,66 @@ val playbackSpeedPatch = bytecodePatch( description = "Enables the playback speed option for all videos and " + "retains the speed configurations in between videos.", ) { + dependsOn(sharedExtensionPatch) + compatibleWith( - "com.ss.android.ugc.trill"("36.5.4"), - "com.zhiliaoapp.musically"("36.5.4"), + "com.ss.android.ugc.trill"("43.6.2"), + "com.zhiliaoapp.musically"("43.6.2"), ) execute { - setSpeedFingerprint.let { onVideoSwiped -> - getSpeedFingerprint.method.apply { - val injectIndex = - indexOfFirstInstructionOrThrow { getReference()?.returnType == "F" } + 2 - val register = getInstruction(injectIndex - 1).registerA + getSpeedFingerprint.method.apply { + val injectIndex = + indexOfFirstInstructionOrThrow { getReference()?.returnType == "F" } + 2 + val register = getInstruction(injectIndex - 1).registerA - addInstruction( - injectIndex, - "invoke-static { v$register }," + - " Lapp/revanced/extension/tiktok/speed/PlaybackSpeedPatch;->rememberPlaybackSpeed(F)V", - ) - } - - // By default, the playback speed will reset to 1.0 at the start of each video. - // Instead, override it with the desired playback speed. - onRenderFirstFrameFingerprint.method.addInstructions( - 0, - """ - # Video playback location (e.g. home page, following page or search result page) retrieved using getEnterFrom method. - const/4 v0, 0x1 - invoke-virtual { p0, v0 }, ${getEnterFromFingerprint.originalMethod} - move-result-object v0 - - # Model of current video retrieved using getCurrentAweme method. - invoke-virtual { p0 }, Lcom/ss/android/ugc/aweme/feed/panel/BaseListFragmentPanel;->getCurrentAweme()Lcom/ss/android/ugc/aweme/feed/model/Aweme; - move-result-object v1 - - # Desired playback speed retrieved using getPlaybackSpeed method. - invoke-static { }, Lapp/revanced/extension/tiktok/speed/PlaybackSpeedPatch;->getPlaybackSpeed()F - move-result v2 - invoke-static { v0, v1, v2 }, ${onVideoSwiped.originalMethod} - """, - ) - - // Force enable the playback speed option for all videos. - onVideoSwiped.classDef.methods.find { method -> method.returnType == "Z" }?.addInstructions( - 0, - """ - const/4 v0, 0x1 - return v0 - """, + addInstruction( + injectIndex, + "invoke-static { v$register }," + + " Lapp/revanced/extension/tiktok/speed/PlaybackSpeedPatch;->rememberPlaybackSpeed(F)V", ) } + + // By default, the playback speed will reset to 1.0 at the start of each video. + // Instead, override it with the desired playback speed. + onRenderFirstFrameFingerprint.method.addInstructions( + 0, + """ + # Video playback location (e.g. home page, following page or search result page) retrieved using getEnterFrom method. + const/4 v0, 0x1 + invoke-virtual { p0, v0 }, ${getEnterFromFingerprint.originalMethod} + move-result-object v0 + + # Model of current video retrieved using getCurrentAweme method. + invoke-virtual { p0 }, Lcom/ss/android/ugc/aweme/feed/panel/BaseListFragmentPanel;->getCurrentAweme()Lcom/ss/android/ugc/aweme/feed/model/Aweme; + move-result-object v1 + if-eqz v1, :revanced_skip_set_speed + + # Desired playback speed retrieved using getPlaybackSpeed method. + invoke-static {}, Lapp/revanced/extension/tiktok/speed/PlaybackSpeedPatch;->getPlaybackSpeed()F + move-result v2 + + # Apply desired playback speed. + const/4 v3, 0x0 + invoke-static { v0, v1, v2, v3 }, LX/0MbX;->LJ(Ljava/lang/String;Lcom/ss/android/ugc/aweme/feed/model/Aweme;FLjava/lang/String;)V + + :revanced_skip_set_speed + nop + """, + ) + + // Force enable the playback speed option for all videos. + speedOptionEnabledFingerprint.method.addInstructions( + 0, + """ + if-eqz p0, :revanced_return_false + const/4 v0, 0x1 + return v0 + + :revanced_return_false + const/4 v0, 0x0 + return v0 + """, + ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/EnableOpenDebugPatch.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/EnableOpenDebugPatch.kt new file mode 100644 index 0000000000..a057d8ae1b --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/EnableOpenDebugPatch.kt @@ -0,0 +1,154 @@ +package app.revanced.patches.tiktok.misc.settings + +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.replaceInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.tiktok.misc.extension.sharedExtensionPatch +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction22c +import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +private const val SETTINGS_EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/tiktok/settings/TikTokActivityHook;" + +@Suppress("unused") +val enableOpenDebugPatch = bytecodePatch( + name = "Enable Open Debug", + description = "Re-enables the hidden \"Open debug\" entry in TikTok settings.", +) { + dependsOn(sharedExtensionPatch) + + compatibleWith( + "com.ss.android.ugc.trill"("43.6.2"), + "com.zhiliaoapp.musically"("43.6.2"), + ) + + execute { + val initializeSettingsMethodDescriptor = + "$SETTINGS_EXTENSION_CLASS_DESCRIPTOR->initialize(" + + "Lcom/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity;" + + ")Z" + + // Show the entry in the "Support" group. + supportGroupDefaultStateFingerprint.method.apply { + val aboutSgetIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.SGET_OBJECT && getReference()?.name == "ABOUT" + } + + val aboutAddInstruction = getInstruction(aboutSgetIndex + 1) + val listRegister = aboutAddInstruction.registerC + val itemRegister = aboutAddInstruction.registerD + + addInstructions( + aboutSgetIndex + 2, + """ + sget-object v$itemRegister, LX/0mDW;->OPEN_DEBUG:LX/0mDW; + invoke-virtual { v$listRegister, v$itemRegister }, LX/165P;->add(Ljava/lang/Object;)Z + """, + ) + } + + // Initialize the ReVanced settings UI when AdPersonalizationActivity is opened with our marker extra. + adPersonalizationActivityOnCreateFingerprint.method.apply { + val initializeSettingsIndex = implementation!!.instructions.indexOfFirst { + it.opcode == Opcode.INVOKE_SUPER + } + 1 + + val thisRegister = getInstruction(initializeSettingsIndex - 1).registerC + val usableRegister = implementation!!.registerCount - parameters.size - 2 + + addInstructionsWithLabels( + initializeSettingsIndex, + """ + invoke-static {v$thisRegister}, $initializeSettingsMethodDescriptor + move-result v$usableRegister + if-eqz v$usableRegister, :do_not_open + return-void + """, + ExternalLabel("do_not_open", getInstruction(initializeSettingsIndex)), + ) + } + + // Set a custom label ("ReVanced settings") for the entry. + openDebugCellStateConstructorFingerprint.method.apply { + val titleValuePutIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.IPUT_OBJECT && getReference()?.name == "LLILLL" + } + + val valueRegister = getInstruction(titleValuePutIndex).registerA + addInstruction(titleValuePutIndex, "const-string v$valueRegister, \"ReVanced settings\"") + } + + // Prefer the "titleValue" field over resolving the "titleId" resource. + openDebugCellComposeFingerprint.method.apply { + val getStringInvokeIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.toString() == + "Landroid/content/Context;->getString(I)Ljava/lang/String;" + } + + val moveResultIndex = getStringInvokeIndex + 1 + val afterTitleIndex = getStringInvokeIndex + 2 + + val titleStringRegister = getInstruction(moveResultIndex).registerA + + val titleIdFieldGetIndex = indexOfFirstInstructionReversedOrThrow(getStringInvokeIndex) { + opcode == Opcode.IGET_OBJECT && getReference()?.name == "LLILL" + } + val stateRegister = getInstruction(titleIdFieldGetIndex).registerB + + addInstructionsWithLabels( + getStringInvokeIndex, + """ + iget-object v$titleStringRegister, v$stateRegister, LX/05iN;->LLILLL:Ljava/lang/String; + if-nez v$titleStringRegister, :revanced_title_done + """, + ExternalLabel("revanced_title_done", getInstruction(afterTitleIndex)), + ) + } + + // Swap the icon to a built-in gear icon. + openDebugCellVmDefaultStateFingerprint.method.apply { + val iconIdLiteralIndex = indexOfFirstInstructionOrThrow { + this is NarrowLiteralInstruction && narrowLiteral == 0x7f0107e3 + } + + val iconRegister = getInstruction(iconIdLiteralIndex).registerA + + // raw/icon_2pt_settings_stroke + replaceInstruction(iconIdLiteralIndex, "const v$iconRegister, 0x7f010088") + } + + // Wire up the click action to open ReVanced settings. + openDebugCellClickWrapperFingerprint.method.apply { + addInstructions( + 0, + """ + iget-object v0, p0, Lkotlin/jvm/internal/AwS350S0200000_2;->l1:Ljava/lang/Object; + check-cast v0, Landroid/content/Context; + new-instance v1, Landroid/content/Intent; + const-class v2, Lcom/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity; + invoke-direct { v1, v0, v2 }, Landroid/content/Intent;->(Landroid/content/Context;Ljava/lang/Class;)V + const-string v2, "revanced_settings" + invoke-virtual { v1, v2 }, Landroid/content/Intent;->setAction(Ljava/lang/String;)Landroid/content/Intent; + const/high16 v2, 0x10000000 + invoke-virtual { v1, v2 }, Landroid/content/Intent;->addFlags(I)Landroid/content/Intent; + invoke-virtual { v0, v1 }, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V + sget-object v0, Lkotlin/Unit;->LIZ:Lkotlin/Unit; + return-object v0 + """, + ) + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/Fingerprints.kt index d1c4d6de68..d336315427 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/Fingerprints.kt @@ -33,3 +33,51 @@ internal val settingsStatusLoadFingerprint = fingerprint { method.name == "load" } } + +internal val supportGroupDefaultStateFingerprint = fingerprint { + custom { method, classDef -> + classDef.endsWith("/SupportGroupVM;") && method.name == "defaultState" + } +} + +internal val openDebugCellVmDefaultStateFingerprint = fingerprint { + custom { method, classDef -> + classDef.endsWith("/OpenDebugCellVM;") && method.name == "defaultState" + } +} + +internal val openDebugCellStateConstructorFingerprint = fingerprint { + custom { method, classDef -> + classDef.endsWith("LX/05iN;") && + method.name == "" && + method.parameterTypes == listOf( + "LX/05hd;", + "Ljava/lang/Integer;", + "Ljava/lang/Integer;", + "Ljava/lang/Integer;", + "Lkotlin/jvm/internal/AwS526S0100000_2;", + ) + } +} + +internal val openDebugCellComposeFingerprint = fingerprint { + custom { method, _ -> + method.name == "LIZ" && + method.returnType == "V" && + method.parameterTypes == listOf( + "LX/05iN;", + "Z", + "Z", + "LX/06c6;", + "I", + ) + } +} + +internal val openDebugCellClickWrapperFingerprint = fingerprint { + custom { method, classDef -> + classDef.endsWith("Lkotlin/jvm/internal/AwS350S0200000_2;") && + method.name == "invoke\$85" && + method.parameterTypes == listOf("Lkotlin/jvm/internal/AwS350S0200000_2;") + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/SettingsPatch.kt index bc6cc2bffe..c71d7f1a00 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/settings/SettingsPatch.kt @@ -18,12 +18,13 @@ private const val EXTENSION_CLASS_DESCRIPTOR = val settingsPatch = bytecodePatch( name = "Settings", description = "Adds ReVanced settings to TikTok.", + use = false, ) { dependsOn(sharedExtensionPatch, addBrandLicensePatch) compatibleWith( - "com.ss.android.ugc.trill"("36.5.4"), - "com.zhiliaoapp.musically"("36.5.4"), + "com.ss.android.ugc.trill"("43.6.2"), + "com.zhiliaoapp.musically"("43.6.2"), ) execute { diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/share/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/share/Fingerprints.kt index 836be89006..6eeb7136f8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/share/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/share/Fingerprints.kt @@ -1,25 +1,17 @@ package app.revanced.patches.tiktok.misc.share import app.revanced.patcher.fingerprint -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode -internal val urlShorteningFingerprint = fingerprint { - accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL) - returns("LX/") +internal val shareUrlShorteningFingerprint = fingerprint { + returns("LX/0rZz;") parameters( "I", "Ljava/lang/String;", "Ljava/lang/String;", - "Ljava/lang/String;" + "Ljava/lang/String;", ) - opcodes(Opcode.RETURN_OBJECT) - - // Same Kotlin intrinsics literal on both variants. - strings("getShortShareUrlObservab\u2026ongUrl, subBizSceneValue)") - - custom { method, _ -> - // LIZLLL is obfuscated by ProGuard/R8, but stable across both TikTok and Musically. - method.name == "LIZLLL" + strings("item is null") + custom { method, classDef -> + classDef.type == "LX/0fTY;" && method.name == "LJIJI" } } diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/share/SanitizeShareUrlsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/share/SanitizeShareUrlsPatch.kt index fd616141c7..f1221f5d1d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/share/SanitizeShareUrlsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/share/SanitizeShareUrlsPatch.kt @@ -1,19 +1,10 @@ package app.revanced.patches.tiktok.misc.share 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.patch.bytecodePatch import app.revanced.patches.shared.PATCH_DESCRIPTION_SANITIZE_SHARING_LINKS import app.revanced.patches.shared.PATCH_NAME_SANITIZE_SHARING_LINKS import app.revanced.patches.tiktok.misc.extension.sharedExtensionPatch -import app.revanced.util.findFreeRegister -import app.revanced.util.getReference -import app.revanced.util.indexOfFirstInstructionOrThrow -import com.android.tools.smali.dexlib2.Opcode -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.reference.MethodReference private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/tiktok/share/ShareUrlSanitizer;" @@ -26,60 +17,33 @@ val sanitizeShareUrlsPatch = bytecodePatch( dependsOn(sharedExtensionPatch) compatibleWith( - "com.ss.android.ugc.trill"("36.5.4"), - "com.zhiliaoapp.musically"("36.5.4"), + "com.ss.android.ugc.trill"("43.6.2"), + "com.zhiliaoapp.musically"("43.6.2"), ) execute { - urlShorteningFingerprint.method.apply { - val invokeIndex = indexOfFirstInstructionOrThrow { - val ref = getReference() - ref?.name == "LIZ" && ref.definingClass.startsWith("LX/") - } + shareUrlShorteningFingerprint.method.addInstructions( + 0, + """ + if-eqz p4, :revanced_skip_sanitization + invoke-virtual {p4}, Ljava/lang/String;->length()I + move-result v0 + if-eqz v0, :revanced_skip_sanitization - val moveResultIndex = indexOfFirstInstructionOrThrow(invokeIndex, Opcode.MOVE_RESULT_OBJECT) - val urlRegister = getInstruction(moveResultIndex).registerA + invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->shouldSanitize()Z + move-result v0 + if-eqz v0, :revanced_skip_sanitization - // Resolve Observable wrapper classes at runtime - val observableWrapperIndex = indexOfFirstInstructionOrThrow(Opcode.NEW_INSTANCE) - val observableWrapperClass = getInstruction(observableWrapperIndex) - .reference.toString() + invoke-static {p4}, $EXTENSION_CLASS_DESCRIPTOR->sanitizeShareUrl(Ljava/lang/String;)Ljava/lang/String; + move-result-object v0 - val observableFactoryIndex = indexOfFirstInstructionOrThrow { - val ref = getReference() - ref?.name == "LJ" && ref.definingClass.startsWith("LX/") - } - val observableFactoryRef = getInstruction(observableFactoryIndex) - .reference as MethodReference + new-instance v1, LX/0rXE; + invoke-direct {v1, v0}, LX/0rXE;->(Ljava/lang/Object;)V + return-object v1 - val observableFactoryClass = observableFactoryRef.definingClass - val observableInterfaceType = observableFactoryRef.parameterTypes.first() - val observableReturnType = observableFactoryRef.returnType - - val wrapperRegister = findFreeRegister(moveResultIndex + 1, urlRegister) - - // Check setting and conditionally sanitize share URL. - addInstructionsWithLabels( - moveResultIndex + 1, - """ - invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->shouldSanitize()Z - move-result v$wrapperRegister - if-eqz v$wrapperRegister, :skip_sanitization - - invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->sanitizeShareUrl(Ljava/lang/String;)Ljava/lang/String; - move-result-object v$urlRegister - - # Wrap sanitized URL and return early to bypass ShareExtService - new-instance v$wrapperRegister, $observableWrapperClass - invoke-direct { v$wrapperRegister, v$urlRegister }, $observableWrapperClass->(Ljava/lang/String;)V - invoke-static { v$wrapperRegister }, $observableFactoryClass->LJ($observableInterfaceType)$observableReturnType - move-result-object v$urlRegister - return-object v$urlRegister - - :skip_sanitization - nop - """ - ) - } + :revanced_skip_sanitization + nop + """, + ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/SpoofSimPatch.kt b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/SpoofSimPatch.kt index 40a45650de..add59969aa 100644 --- a/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/SpoofSimPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/tiktok/misc/spoof/sim/SpoofSimPatch.kt @@ -5,7 +5,6 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.tiktok.misc.extension.sharedExtensionPatch -import app.revanced.patches.tiktok.misc.settings.settingsPatch import app.revanced.patches.tiktok.misc.settings.settingsStatusLoadFingerprint import app.revanced.util.findMutableMethodOf import com.android.tools.smali.dexlib2.Opcode @@ -21,7 +20,6 @@ val spoofSimPatch = bytecodePatch( ) { dependsOn( sharedExtensionPatch, - settingsPatch, ) compatibleWith( @@ -30,6 +28,11 @@ val spoofSimPatch = bytecodePatch( ) execute { + settingsStatusLoadFingerprint.method.addInstruction( + 0, + "invoke-static {}, Lapp/revanced/extension/tiktok/settings/SettingsStatus;->enableSimSpoof()V", + ) + val replacements = hashMapOf( "getSimCountryIso" to "getCountryIso", "getNetworkCountryIso" to "getCountryIso", @@ -90,11 +93,5 @@ val spoofSimPatch = bytecodePatch( } } } - - // Enable patch in settings. - settingsStatusLoadFingerprint.method.addInstruction( - 0, - "invoke-static {}, Lapp/revanced/extension/tiktok/settings/SettingsStatus;->enableSimSpoof()V", - ) } }