|
| 1 | +package app.revanced.patches.twitter.misc.links |
| 2 | + |
| 3 | +import app.revanced.patcher.data.BytecodeContext |
| 4 | +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions |
| 5 | +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction |
| 6 | +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction |
| 7 | +import app.revanced.patcher.patch.BytecodePatch |
| 8 | +import app.revanced.patcher.patch.annotation.CompatiblePackage |
| 9 | +import app.revanced.patcher.patch.annotation.Patch |
| 10 | +import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption |
| 11 | +import app.revanced.patches.twitter.misc.links.fingerprints.LinkBuilderFingerprint |
| 12 | +import app.revanced.patches.twitter.misc.links.fingerprints.LinkResourceGetterFingerprint |
| 13 | +import app.revanced.patches.twitter.misc.links.fingerprints.LinkSharingDomainFingerprint |
| 14 | +import app.revanced.util.exception |
| 15 | +import app.revanced.util.getReference |
| 16 | +import app.revanced.util.indexOfFirstInstructionOrThrow |
| 17 | +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction |
| 18 | +import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c |
| 19 | +import com.android.tools.smali.dexlib2.iface.reference.StringReference |
| 20 | + |
| 21 | +@Patch( |
| 22 | + name = "Change link sharing domain", |
| 23 | + description = "Replaces the domain name of Twitter links when sharing them.", |
| 24 | + compatiblePackages = [CompatiblePackage("com.twitter.android")], |
| 25 | +) |
| 26 | +@Suppress("unused") |
| 27 | +object ChangeLinkSharingDomainPatch : BytecodePatch( |
| 28 | + setOf( |
| 29 | + LinkBuilderFingerprint, |
| 30 | + LinkResourceGetterFingerprint, |
| 31 | + LinkSharingDomainFingerprint, |
| 32 | + ), |
| 33 | +) { |
| 34 | + private var domainName by stringPatchOption( |
| 35 | + key = "domainName", |
| 36 | + default = "fxtwitter.com", |
| 37 | + title = "Domain name", |
| 38 | + description = "The domain name to use when sharing links.", |
| 39 | + required = true, |
| 40 | + ) |
| 41 | + |
| 42 | + // This method is used to build the link that is shared when the "Share via..." button is pressed. |
| 43 | + private const val FORMAT_METHOD_RESOURCE_REFERENCE = |
| 44 | + "Lapp/revanced/integrations/twitter/patches/links/ChangeLinkSharingDomainPatch;->" + |
| 45 | + "formatResourceLink([Ljava/lang/Object;)Ljava/lang/String;" |
| 46 | + |
| 47 | + // This method is used to build the link that is shared when the "Copy link" button is pressed. |
| 48 | + private const val FORMAT_METHOD_REFERENCE = |
| 49 | + "Lapp/revanced/integrations/twitter/patches/links/ChangeLinkSharingDomainPatch;->" + |
| 50 | + "formatLink(JLjava/lang/String;)Ljava/lang/String;" |
| 51 | + |
| 52 | + override fun execute(context: BytecodeContext) { |
| 53 | + LinkSharingDomainFingerprint.result?.let { |
| 54 | + val replacementIndex = it.scanResult.stringsScanResult!!.matches.first().index |
| 55 | + val domainRegister = it.mutableMethod.getInstruction<OneRegisterInstruction>(replacementIndex).registerA |
| 56 | + it.mutableMethod.replaceInstruction( |
| 57 | + replacementIndex, |
| 58 | + "const-string v$domainRegister, \"https://$domainName\"", |
| 59 | + ) |
| 60 | + } ?: throw LinkSharingDomainFingerprint.exception |
| 61 | + |
| 62 | + // Replace the domain name when copying a link with "Copy link" button. |
| 63 | + LinkBuilderFingerprint.result?.let { |
| 64 | + it.mutableMethod.apply { |
| 65 | + addInstructions( |
| 66 | + 0, |
| 67 | + """ |
| 68 | + invoke-static { p0, p1, p2 }, $FORMAT_METHOD_REFERENCE |
| 69 | + move-result-object p0 |
| 70 | + return-object p0 |
| 71 | + """, |
| 72 | + ) |
| 73 | + } |
| 74 | + } ?: throw LinkBuilderFingerprint.exception |
| 75 | + |
| 76 | + // Used in the Share via... dialog. |
| 77 | + LinkResourceGetterFingerprint.result?.mutableMethod?.apply { |
| 78 | + val constWithParameterName = indexOfFirstInstructionOrThrow { |
| 79 | + getReference<StringReference>()?.string?.contains("id.toString()") == true |
| 80 | + } |
| 81 | + |
| 82 | + // Format the link with the new domain name register (2 instructions above the const-string). |
| 83 | + val formatLinkCallIndex = constWithParameterName - 2 |
| 84 | + val formatLinkCall = getInstruction<Instruction35c>(formatLinkCallIndex) |
| 85 | + |
| 86 | + // Replace the original method call with the new method call. |
| 87 | + replaceInstruction( |
| 88 | + formatLinkCallIndex, |
| 89 | + "invoke-static { v${formatLinkCall.registerE} }, $FORMAT_METHOD_RESOURCE_REFERENCE", |
| 90 | + ) |
| 91 | + } ?: throw LinkResourceGetterFingerprint.exception |
| 92 | + } |
| 93 | +} |
0 commit comments