diff --git a/patches/api/patches.api b/patches/api/patches.api index 9c3b806d38..27a9e66488 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -284,6 +284,10 @@ public final class app/revanced/patches/instagram/hide/navigation/HideNavigation public static final fun getHideNavigationButtonsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/instagram/hide/reshare/HideReshareButtonPatchKt { + public static final fun getHideReshareButtonPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/instagram/hide/stories/HideStoriesKt { public static final fun getHideStoriesPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/hide/reshare/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/reshare/Fingerprints.kt new file mode 100644 index 0000000000..c6d58c32ea --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/reshare/Fingerprints.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.instagram.hide.reshare + +import app.revanced.patcher.fingerprint +import app.revanced.util.indexOfFirstInstruction +import app.revanced.util.indexOfFirstLiteralInstruction +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.formats.SparseSwitchPayload + +// The hash code of the field of interest. It is used as the key of a hashmap +internal val hashedFieldInteger = "enable_media_notes_production".hashCode() + +internal val feedResponseMediaParserFingerprint = fingerprint { + strings("array_out_of_bounds_exception", "null_pointer_exception", "MediaDict") + custom { method, _ -> + method.indexOfFirstInstruction { + opcode == Opcode.SPARSE_SWITCH_PAYLOAD && + (this as SparseSwitchPayload).switchElements.any { it.key == hashedFieldInteger } + } >= 0 + } +} + +internal val reelPostsResponseMediaParserFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + parameters("L", "L", "L", "[I") + returns("L") + custom { method, _ -> + method.indexOfFirstLiteralInstruction(hashedFieldInteger.toLong()) >= 0 + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/hide/reshare/HideReshareButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/reshare/HideReshareButtonPatch.kt new file mode 100644 index 0000000000..820fbda1f7 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/reshare/HideReshareButtonPatch.kt @@ -0,0 +1,72 @@ +package app.revanced.patches.instagram.hide.reshare + +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstLiteralInstructionOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.builder.instruction.BuilderSparseSwitchPayload +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +@Suppress("unused") +val hideReshareButtonPatch = bytecodePatch( + name = "Hide reshare button", + description = "Hides the reshare button from both posts and reels.", + use = false +) { + compatibleWith("com.instagram.android") + + execute { + feedResponseMediaParserFingerprint.method.apply { + // Each json field is parsed in a switch statement, where the case of the switch is the hashed field name. + + // First, find the switch payload where our field of interest is being processed. So find the payload that + // has a key == to our field of interest. + val switchPayload = implementation!!.instructions.first { ins -> + ins.opcode == Opcode.SPARSE_SWITCH_PAYLOAD && + (ins as BuilderSparseSwitchPayload).switchElements.any { it.key == hashedFieldInteger } + } as BuilderSparseSwitchPayload + + // Get the target label, so find the instruction offset where the switch case is pointing to. + val switchTargetLabel = switchPayload.switchElements + .first { it.key == hashedFieldInteger } + .target + + // From that label, navigate forward until our field of interest is being instantiated. + val moveResultIndex = indexOfFirstInstructionOrThrow( + switchTargetLabel.location.index, + Opcode.MOVE_RESULT_OBJECT + ) + + val moveResultRegister = getInstruction(moveResultIndex).registerA + + addInstruction( + moveResultIndex + 1, + "sget-object v$moveResultRegister, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;" + ) + } + + reelPostsResponseMediaParserFingerprint.method.apply { + // Each json field is parsed in a series of if statements, where the if comparison is done comparing the hashed field name. + + // Get index of "const v*, -0x207dadd2". + val switchIndex = indexOfFirstLiteralInstructionOrThrow(hashedFieldInteger.toLong()) + + // Here an internal Instagram native library is used to handle settings of experiments, using hash codes of the fields. + // Find where our field of interest is being instantiated. + val moveBooleanResultIndex = indexOfFirstInstructionOrThrow(switchIndex) { + getReference()?.name == "getOptionalBooleanValueByHashCode" + } + 1 + + val moveBooleanResultRegister = getInstruction(moveBooleanResultIndex).registerA + + addInstruction( + moveBooleanResultIndex + 1, + "sget-object v$moveBooleanResultRegister, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;" + ) + } + } +}