Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(Twitter): Native features #504

Merged
merged 2 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
package crimera.patches.twitter.misc.shareMenu.nativeDownloader

import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.fingerprint.MethodFingerprintResult
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
import com.android.tools.smali.dexlib2.dexbacked.instruction.DexBackedInstruction35c
import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedMethodReference
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction

internal abstract class NativeDownloaderMethodFingerprint(
private val methodName: String,
) : MethodFingerprint(customFingerprint = { methodDef, classDef ->
methodDef.name == methodName && classDef.toString() == "Lapp/revanced/integrations/twitter/patches/NativeDownloader;"
})
methodDef.name == methodName && classDef.toString() == "Lapp/revanced/integrations/twitter/patches/NativeDownloader;"
})

internal object TweetObjectFingerprint : MethodFingerprint(strings = listOf("https://x.com/%1\$s/status/%2\$d"))

Expand All @@ -28,87 +30,110 @@ internal object GetTweetIdFingerprint : NativeDownloaderMethodFingerprint("getTw

internal object GetTweetUsernameFingerprint : NativeDownloaderMethodFingerprint("getTweetUsername")

internal object GetTweetProfileNameFingerprint : NativeDownloaderMethodFingerprint("getTweetProfileName")

internal object GetTweetUserIdFingerprint : NativeDownloaderMethodFingerprint("getTweetUserId")

internal object GetTweetMediaFingerprint : NativeDownloaderMethodFingerprint("getTweetMedia")

internal object GetUserNameMethodCaller : MethodFingerprint(
returnType = "Z", opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT,
Opcode.RETURN
)
returnType = "V",
strings =
listOf(
"Ref_ID (Tweet ID)",
"Name",
"User Name",
),
)

@Patch(
compatiblePackages = [CompatiblePackage("com.twitter.android")],
)
class NativeDownloaderHooksPatch : BytecodePatch(
setOf(
GetTweetClassFingerprint,
GetTweetIdFingerprint,
GetTweetUsernameFingerprint,
GetTweetMediaFingerprint,
GetUserNameMethodCaller,
TweetObjectFingerprint,
),
) {
class NativeDownloaderHooksPatch :
BytecodePatch(
setOf(
GetTweetClassFingerprint,
GetTweetIdFingerprint,
GetTweetUsernameFingerprint,
GetTweetMediaFingerprint,
GetUserNameMethodCaller,
TweetObjectFingerprint,
GetTweetProfileNameFingerprint,
GetTweetUserIdFingerprint,
),
) {
private fun MutableMethod.changeFirstString(value: String) {
this.getInstructions().firstOrNull { it.opcode == Opcode.CONST_STRING }?.let { instruction ->
val register = (instruction as BuilderInstruction21c).registerA
this.replaceInstruction(instruction.location.index, "const-string v$register, \"$value\"")
} ?: throw PatchException("const-string not found for method: ${this.name}")
}

private fun Reference.getMethodFromReference(): Char {
val ref = this.toString()
val index = ref.indexOf("->")
return ref[index + 2]
}
private fun MethodFingerprintResult.getMethodName(index: Int): String =
(this.mutableMethod.getInstruction<ReferenceInstruction>(index).reference as DexBackedMethodReference).name

override fun execute(context: BytecodeContext) {
val getTweetObjectFingerprint = TweetObjectFingerprint.result ?: throw PatchException("bruh")
val getTweetObjectResult = TweetObjectFingerprint.result ?: throw PatchException("bruh")

val tweetObjectClass = getTweetObjectFingerprint.classDef
val tweetObjectClass = getTweetObjectResult.classDef
val tweetObjectClassName = tweetObjectClass.toString().removePrefix("L").removeSuffix(";")

val getIdMethod = tweetObjectClass.methods.firstOrNull { mutableMethod ->
mutableMethod.name == "getId"
} ?: throw PatchException("getIdMethod not found")
val getIdMethod =
tweetObjectClass.methods.firstOrNull { mutableMethod ->
mutableMethod.name == "getId"
} ?: throw PatchException("getIdMethod not found")

val getUserNameMethodCaller =
GetUserNameMethodCaller.result ?: throw PatchException("Could not find UserNameMethodCaller fingerprint")
val getUserNameMethodName = getUserNameMethodCaller.method.implementation?.instructions?.firstOrNull { t ->
t.opcode == Opcode.INVOKE_STATIC
}.let {
(it as DexBackedInstruction35c).reference.getMethodFromReference()
}

val getUsernameMethod = tweetObjectClass.methods.firstOrNull { mutableMethod ->
mutableMethod.name.contains(getUserNameMethodName) && mutableMethod.returnType == "Ljava/lang/String;"
} ?: throw PatchException("getUsernameMethod not found")
var getUsernameMethod = ""
var getProfileNameMethod = ""
getUserNameMethodCaller.scanResult.stringsScanResult!!.matches.forEach { match ->
val str = match.string
if (str == "Name") {
getProfileNameMethod = getUserNameMethodCaller.getMethodName(match.index + 1)
}
if (str == "User Name") {
getUsernameMethod = getUserNameMethodCaller.getMethodName(match.index + 1)
}
}

val getMediaObjectMethod = tweetObjectClass.methods.firstOrNull { methodDef ->
methodDef.implementation?.instructions?.map { it.opcode }?.toList() == listOf(
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.RETURN_OBJECT,
)
} ?: throw PatchException("getMediaObject not found")
val getTweetUserIdMethod =
getTweetObjectResult.classDef.methods
.last {
it.returnType.equals("J")
}.name

val getMediaObjectMethod =
tweetObjectClass.methods.firstOrNull { methodDef ->
methodDef.implementation
?.instructions
?.map { it.opcode }
?.toList() ==
listOf(
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.RETURN_OBJECT,
)
} ?: throw PatchException("getMediaObject not found")

GetTweetClassFingerprint.result?.mutableMethod?.changeFirstString(tweetObjectClassName)
?: throw GetTweetClassFingerprint.exception

GetTweetIdFingerprint.result?.mutableMethod?.changeFirstString(getIdMethod.name)
?: throw GetTweetIdFingerprint.exception

GetTweetUsernameFingerprint.result?.mutableMethod?.changeFirstString(getUsernameMethod.name)
GetTweetUsernameFingerprint.result?.mutableMethod?.changeFirstString(getUsernameMethod)
?: throw GetTweetUsernameFingerprint.exception

GetTweetProfileNameFingerprint.result?.mutableMethod?.changeFirstString(getProfileNameMethod)
?: throw GetTweetProfileNameFingerprint.exception

GetTweetUserIdFingerprint.result?.mutableMethod?.changeFirstString(getTweetUserIdMethod)
?: throw GetTweetUserIdFingerprint.exception

GetTweetMediaFingerprint.result?.mutableMethod?.changeFirstString(getMediaObjectMethod.name)
?: throw GetTweetMediaFingerprint.exception
}
Expand Down
16 changes: 16 additions & 0 deletions src/main/resources/twitter/settings/values-v21/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,20 @@
<item>7</item>
<item>13</item>
</string-array>

<string-array name="piko_array_native_download_filename">
<item>username_tweetid</item>
<item>name_tweetid</item>
<item>userid_tweetid</item>
<item>tweetid</item>
<item>Current timestamp</item>
</string-array>

<string-array name="piko_array_native_download_filename_val">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</string-array>
</resources>
20 changes: 12 additions & 8 deletions src/main/resources/twitter/settings/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,22 @@
<string name="piko_pref_download_media_link_handle_copy_media_link">Copy media link</string>
<string name="piko_pref_download_media_link_handle_share_media_link">Share media link</string>

<!-- Downloader Options -->
<!-- Native -->
<string name="piko_title_native">Native features</string>
<string name="piko_title_native_downloader">Native downloader</string>
<string name="piko_title_native_downloader_toggle">Enable native downloader</string>
<string name="piko_pref_native_downloader_alert_title">Download media</string>
<string name="piko_pref_native_downloader_download_all">Download all</string>
<string name="piko_pref_native_downloader_no_media">No media found</string>
<string name="piko_pref_native_downloader_filename_title">Save filename pattern</string>

<string name="piko_title_native_translator">Native post translation</string>
<string name="piko_native_translator_toggle">Enable native post translation</string>
<string name="piko_native_translator_provider">Translation providers</string>
<string name="piko_native_translator_to_lang">Translate to</string>
<string name="piko_native_translator_zero_text">No text found to translate</string>
<string name="piko_native_translator_google">Google translator</string>
<string name="piko_native_translator_google_v2">Google translator V2</string>

<!-- Ads Settings -->
<string name="piko_title_ads">Ads</string>
Expand Down Expand Up @@ -152,13 +163,6 @@
<string name="piko_single_page_settings">Single settings page</string>
<string name="piko_single_page_settings_desc">View settings in a single page</string>

<string name="piko_native_translator">Enable native post translation</string>
<string name="piko_native_translator_provider">Translation providers</string>
<string name="piko_native_translator_to_lang">Translate to</string>
<string name="piko_native_translator_zero_text">No text found to translate</string>
<string name="piko_native_translator_google">Google translator</string>
<string name="piko_native_translator_google_v2">Google translator V2</string>

<string name="piko_pref_search_type_ahead_users">Users</string>
<string name="piko_pref_search_type_ahead_events">Events</string>
<string name="piko_pref_search_type_ahead_ordered_section">Ordered section</string>
Expand Down
Loading