diff --git a/app/src/main/java/gm/tieba/tabswitch/XposedInit.java b/app/src/main/java/gm/tieba/tabswitch/XposedInit.java index 47fd7291..04766ecc 100644 --- a/app/src/main/java/gm/tieba/tabswitch/XposedInit.java +++ b/app/src/main/java/gm/tieba/tabswitch/XposedInit.java @@ -103,6 +103,7 @@ protected void afterHookedMethod(final MethodHookParam param) throws Throwable { attachBaseContext((Application) param.args[0]); Preferences.init(getContext()); AcRules.init(getContext()); + String currTbVersion = DeobfuscationHelper.getTbVersion(getContext()); final var hookers = List.of( new TSPreference(), @@ -140,6 +141,34 @@ protected void afterHookedMethod(final MethodHookParam param) throws Throwable { } } + List matchersList = matchers.stream() + .map(Obfuscated::matchers) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + + // Remove matchers that does not satisfy version requirement + matchersList.removeIf( + o -> { + if (o.getRequiredVersion() != null) { + boolean isVersionSatisfied = DeobfuscationHelper.isTbSatisfyVersionRequirement( + o.getRequiredVersion(), + currTbVersion + ); + if (!isVersionSatisfied) { + XposedBridge.log( + String.format( + "Skipping rule [%s] due to version mismatch (current version: %s)", + o.toString(), + currTbVersion + ) + ); + } + return !isVersionSatisfied; + } + return false; + } + ); + if (DeobfuscationHelper.isVersionChanged(getContext())) { if ("com.baidu.tieba".equals(lpparam.processName)) { XposedHelpers.findAndHookMethod("com.baidu.tieba.tblauncher.MainTabActivity", sClassLoader, "onCreate", Bundle.class, new XC_MethodHook() { @@ -153,17 +182,12 @@ protected void afterHookedMethod(final MethodHookParam param) throws Throwable { }); } XposedBridge.log("Deobfuscation"); - new DeobfuscationHooker( - matchers.stream() - .map(Obfuscated::matchers) - .flatMap(Collection::stream) - .collect(Collectors.toList()) - ).hook(); + + new DeobfuscationHooker(matchersList).hook(); return; } - final var lostList = matchers.stream() - .map(Obfuscated::matchers) - .flatMap(Collection::stream) + + final var lostList = matchersList.stream() .map(Matcher::toString) .filter(matcher -> !AcRules.isRuleFound(matcher)) .collect(Collectors.toList()); @@ -175,7 +199,7 @@ protected void beforeHookedMethod(final MethodHookParam param) throws Throwable final var messages = new ArrayList(); messages.add(String.format(Constants.getStrings().get("exception_rules_incomplete"), BuildConfig.TARGET_VERSION)); messages.add(String.format(Locale.CHINA, "tbversion: %s, module version: %d", - DeobfuscationHelper.getTbVersion(getContext()), BuildConfig.VERSION_CODE)); + currTbVersion, BuildConfig.VERSION_CODE)); messages.add(String.format(Locale.CHINA, "%d rule(s) lost: %s", lostList.size(), lostList)); final var message = TextUtils.join("\n", messages); XposedBridge.log(message); diff --git a/app/src/main/java/gm/tieba/tabswitch/hooker/deobfuscation/DeobfuscationHelper.java b/app/src/main/java/gm/tieba/tabswitch/hooker/deobfuscation/DeobfuscationHelper.java index f42db0c9..f87c1003 100644 --- a/app/src/main/java/gm/tieba/tabswitch/hooker/deobfuscation/DeobfuscationHelper.java +++ b/app/src/main/java/gm/tieba/tabswitch/hooker/deobfuscation/DeobfuscationHelper.java @@ -106,4 +106,24 @@ protected void beforeHookedMethod(final MethodHookParam param) throws Throwable activity.startActivity(intent); } } + + public static boolean isTbSatisfyVersionRequirement(final String requiredVersion, final String currentVersion) { + String[] currParts = currentVersion.split("\\."); + String[] reqParts = requiredVersion.split("\\."); + int length = Math.max(currParts.length, reqParts.length); + for(int i = 0; i < length; i++) { + try { + int currPart = i < currParts.length ? + Integer.parseInt(currParts[i]) : 0; + int reqPart = i < reqParts.length ? + Integer.parseInt(reqParts[i]) : 0; + if (currPart != reqPart) { + return currPart > reqPart; + } + } catch (NumberFormatException e) { + return false; + } + } + return true; + } } diff --git a/app/src/main/java/gm/tieba/tabswitch/hooker/deobfuscation/Matcher.kt b/app/src/main/java/gm/tieba/tabswitch/hooker/deobfuscation/Matcher.kt index 190c1ba7..abc51ba8 100644 --- a/app/src/main/java/gm/tieba/tabswitch/hooker/deobfuscation/Matcher.kt +++ b/app/src/main/java/gm/tieba/tabswitch/hooker/deobfuscation/Matcher.kt @@ -2,41 +2,71 @@ package gm.tieba.tabswitch.hooker.deobfuscation import gm.tieba.tabswitch.util.ClassMatcherUtils -abstract class Matcher(val classMatcher: ClassMatcherUtils? = null) { - override fun toString(): String = classMatcher?.toString().orEmpty() +class MatcherProperties { + var classMatcher: ClassMatcherUtils? = null + var requiredVersion: String? = null + companion object { + @JvmStatic + fun create() : MatcherProperties { + return MatcherProperties() + } + } + override fun toString(): String { + val versionString = requiredVersion?.let { "$it@" } ?: "" + return versionString + classMatcher?.toString().orEmpty() + } + fun useClassMatcher(classMatcher: ClassMatcherUtils?) : MatcherProperties { + this.classMatcher = classMatcher + return this + } + fun requireVersion(requiredVersion: String) : MatcherProperties { + this.requiredVersion = requiredVersion + return this + } } -class StringMatcher @JvmOverloads constructor(val str: String, classMatcher: ClassMatcherUtils? = null) : Matcher(classMatcher) { +abstract class Matcher(private val properties: MatcherProperties? = null) { + override fun toString(): String = properties?.toString().orEmpty() + fun getClassMatcher(): ClassMatcherUtils? = properties?.classMatcher + fun getRequiredVersion(): String? = properties?.requiredVersion + +} + +class StringMatcher @JvmOverloads constructor(val str: String, properties: MatcherProperties? = null) : Matcher(properties) { override fun toString(): String = super.toString() + str } -class SmaliMatcher @JvmOverloads constructor(val str: String, classMatcher: ClassMatcherUtils? = null) : Matcher(classMatcher) { +class SmaliMatcher @JvmOverloads constructor(val str: String, properties: MatcherProperties? = null) : Matcher(properties) { override fun toString(): String = super.toString() + str fun getDescriptor(): String = str; } -class MethodNameMatcher @JvmOverloads constructor(val name: String, classMatcher: ClassMatcherUtils? = null) : Matcher(classMatcher) { +class MethodNameMatcher @JvmOverloads constructor(val name: String, properties: MatcherProperties? = null) : Matcher(properties) { override fun toString(): String = super.toString() + name } -open class ResMatcher @JvmOverloads constructor(var id: Long = 0, classMatcher: ClassMatcherUtils? = null) : Matcher(classMatcher) { +class ReturnTypeMatcher @JvmOverloads constructor(val returnType: Class, properties: MatcherProperties? = null) : Matcher(properties) { + override fun toString(): String = super.toString() + returnType.simpleName +} + +open class ResMatcher @JvmOverloads constructor(var id: Long = 0, properties: MatcherProperties? = null) : Matcher(properties) { open fun toResIdentifier(): String { throw UnsupportedOperationException() } } -class StringResMatcher @JvmOverloads constructor(val str: String, classMatcher: ClassMatcherUtils? = null) : ResMatcher(classMatcher = classMatcher) { +class StringResMatcher @JvmOverloads constructor(val str: String, properties: MatcherProperties? = null) : ResMatcher(properties = properties) { override fun toString(): String = super.toString() + str override fun toResIdentifier(): String = str } -class ZipEntryMatcher @JvmOverloads constructor(val size: Long, classMatcher: ClassMatcherUtils? = null) : ResMatcher(classMatcher = classMatcher) { +class ZipEntryMatcher @JvmOverloads constructor(val size: Long, properties: MatcherProperties? = null) : ResMatcher(properties = properties) { var entryName: String = "" override fun toString(): String = super.toString() + size.toString() override fun toResIdentifier(): String = size.toString() } -class ResIdentifierMatcher @JvmOverloads constructor(val name: String, val defType: String, classMatcher: ClassMatcherUtils? = null) : ResMatcher(classMatcher = classMatcher) { +class ResIdentifierMatcher @JvmOverloads constructor(val name: String, val defType: String, properties: MatcherProperties? = null) : ResMatcher(properties = properties) { override fun toString(): String = super.toString() + String.format("%s.%s", defType, name) override fun toResIdentifier(): String = String.format("%s.%s", defType, name) } \ No newline at end of file diff --git a/app/src/main/java/gm/tieba/tabswitch/hooker/eliminate/PurgeEnter.java b/app/src/main/java/gm/tieba/tabswitch/hooker/eliminate/PurgeEnter.java index 1d984fe1..d63a46d8 100644 --- a/app/src/main/java/gm/tieba/tabswitch/hooker/eliminate/PurgeEnter.java +++ b/app/src/main/java/gm/tieba/tabswitch/hooker/eliminate/PurgeEnter.java @@ -14,6 +14,7 @@ import gm.tieba.tabswitch.dao.AcRules; import gm.tieba.tabswitch.hooker.IHooker; import gm.tieba.tabswitch.hooker.Obfuscated; +import gm.tieba.tabswitch.hooker.deobfuscation.MatcherProperties; import gm.tieba.tabswitch.hooker.deobfuscation.MethodNameMatcher; import gm.tieba.tabswitch.hooker.deobfuscation.Matcher; import gm.tieba.tabswitch.hooker.deobfuscation.ResIdentifierMatcher; @@ -34,8 +35,8 @@ public String key() { @Override public List matchers() { return List.of( - new ResIdentifierMatcher("tbds400", "dimen", ClassMatcherUtils.usingString("enter_forum_login_tip")), - new MethodNameMatcher("onSuccess", ClassMatcherUtils.usingString("enter_forum_login_tip")) + new ResIdentifierMatcher("tbds400", "dimen", MatcherProperties.create().useClassMatcher(ClassMatcherUtils.usingString("enter_forum_login_tip"))), + new MethodNameMatcher("onSuccess", MatcherProperties.create().useClassMatcher(ClassMatcherUtils.usingString("enter_forum_login_tip"))) ); } diff --git a/app/src/main/java/gm/tieba/tabswitch/hooker/eliminate/PurgeMy.java b/app/src/main/java/gm/tieba/tabswitch/hooker/eliminate/PurgeMy.java index 12c0d009..225223a2 100644 --- a/app/src/main/java/gm/tieba/tabswitch/hooker/eliminate/PurgeMy.java +++ b/app/src/main/java/gm/tieba/tabswitch/hooker/eliminate/PurgeMy.java @@ -17,6 +17,7 @@ import gm.tieba.tabswitch.hooker.IHooker; import gm.tieba.tabswitch.hooker.Obfuscated; import gm.tieba.tabswitch.hooker.deobfuscation.Matcher; +import gm.tieba.tabswitch.hooker.deobfuscation.MatcherProperties; import gm.tieba.tabswitch.hooker.deobfuscation.SmaliMatcher; import gm.tieba.tabswitch.util.ClassMatcherUtils; import gm.tieba.tabswitch.util.ReflectUtils; @@ -35,7 +36,10 @@ public String key() { public List matchers() { return List.of( new SmaliMatcher("Lcom/baidu/tieba/personCenter/view/PersonOftenFuncItemView;->(Landroid/content/Context;)V"), - new SmaliMatcher("Lcom/baidu/nadcore/download/basic/AdAppStateManager;->instance()Lcom/baidu/nadcore/download/basic/AdAppStateManager;", ClassMatcherUtils.usingString("隐私设置")) + new SmaliMatcher( + "Lcom/baidu/nadcore/download/basic/AdAppStateManager;->instance()Lcom/baidu/nadcore/download/basic/AdAppStateManager;", + MatcherProperties.create().useClassMatcher(ClassMatcherUtils.usingString("隐私设置")) + ) ); }