Merge dev
This commit is contained in:
commit
48b3a2d18c
183 changed files with 62850 additions and 59577 deletions
|
|
@ -0,0 +1,54 @@
|
|||
package app.revanced.patches.all.misc.screenshot
|
||||
|
||||
import app.revanced.patcher.extensions.removeInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
private val registerScreenCaptureCallbackMethodReference = ImmutableMethodReference(
|
||||
"Landroid/app/Activity;",
|
||||
"registerScreenCaptureCallback",
|
||||
listOf(
|
||||
"Ljava/util/concurrent/Executor;",
|
||||
"Landroid/app/Activity\$ScreenCaptureCallback;",
|
||||
),
|
||||
"V",
|
||||
)
|
||||
|
||||
private val unregisterScreenCaptureCallbackMethodReference = ImmutableMethodReference(
|
||||
"Landroid/app/Activity;",
|
||||
"unregisterScreenCaptureCallback",
|
||||
listOf(
|
||||
"Landroid/app/Activity\$ScreenCaptureCallback;",
|
||||
),
|
||||
"V",
|
||||
)
|
||||
|
||||
@Suppress("unused")
|
||||
val preventScreenshotDetectionPatch = bytecodePatch(
|
||||
name = "Prevent screenshot detection",
|
||||
description = "Removes the registration of all screen capture callbacks. This prevents the app from detecting screenshots.",
|
||||
use = false,
|
||||
) {
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = { _, _, instruction, instructionIndex ->
|
||||
if (instruction.opcode != Opcode.INVOKE_VIRTUAL) return@transformInstructionsPatch null
|
||||
|
||||
val reference = instruction.getReference<MethodReference>() ?: return@transformInstructionsPatch null
|
||||
|
||||
instructionIndex.takeIf {
|
||||
MethodUtil.methodSignaturesMatch(reference, registerScreenCaptureCallbackMethodReference) ||
|
||||
MethodUtil.methodSignaturesMatch(reference, unregisterScreenCaptureCallbackMethodReference)
|
||||
}
|
||||
},
|
||||
transform = { mutableMethod, instructionIndex ->
|
||||
mutableMethod.removeInstruction(instructionIndex)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package app.revanced.patches.instagram.hide.highlightsTray
|
||||
|
||||
import app.revanced.patcher.composingFirstMethod
|
||||
import app.revanced.patcher.instructions
|
||||
import app.revanced.patcher.invoke
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
|
||||
internal const val TARGET_STRING = "highlights_tray"
|
||||
|
||||
internal val BytecodePatchContext.highlightsUrlBuilderMethodMatch by composingFirstMethod("X-IG-Accept-Hint") {
|
||||
instructions(TARGET_STRING())
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package app.revanced.patches.instagram.hide.highlightsTray
|
||||
|
||||
import app.revanced.patcher.extensions.getInstruction
|
||||
import app.revanced.patcher.extensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val hideHighlightsTrayPatch = bytecodePatch(
|
||||
name = "Hide highlights tray",
|
||||
description = "Hides the highlights tray in profile section.",
|
||||
use = false,
|
||||
) {
|
||||
compatibleWith("com.instagram.android")
|
||||
|
||||
apply {
|
||||
highlightsUrlBuilderMethodMatch.method.apply {
|
||||
val targetStringIndex = highlightsUrlBuilderMethodMatch[0]
|
||||
val targetStringRegister = getInstruction<OneRegisterInstruction>(targetStringIndex).registerA
|
||||
|
||||
replaceInstruction(targetStringIndex, "const-string v$targetStringRegister, \"BOGUS\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,10 +17,5 @@ internal val FEED_ITEM_KEYS_TO_BE_HIDDEN = arrayOf(
|
|||
)
|
||||
|
||||
internal val BytecodePatchContext.feedItemParseFromJsonMethodMatch by composingFirstMethod("FeedItem") {
|
||||
instructions(
|
||||
predicates = unorderedAllOf(
|
||||
predicates =
|
||||
FEED_ITEM_KEYS_TO_BE_HIDDEN.map { it.invoke() }.toTypedArray(),
|
||||
),
|
||||
)
|
||||
instructions(predicates = unorderedAllOf(predicates = FEED_ITEM_KEYS_TO_BE_HIDDEN.map { it() }.toTypedArray()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import app.revanced.patcher.patch.bytecodePatch
|
|||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val hideSuggestedContentPatch = bytecodePatch(
|
||||
val hideSuggestedContent = bytecodePatch(
|
||||
name = "Hide suggested content",
|
||||
description = "Hides suggested stories, reels, threads and survey from feed (Suggested posts will still be shown).",
|
||||
use = false,
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ val openLinksExternallyPatch = bytecodePatch(
|
|||
addInstructions(
|
||||
urlResultObjIndex + 1,
|
||||
"""
|
||||
invoke-static { v$urlRegister }, $EXTENSION_CLASS_DESCRIPTOR->openExternally(Ljava/lang/String;)Z
|
||||
move-result v$urlRegister
|
||||
return v$urlRegister
|
||||
invoke-static/range { v$urlRegister .. v$urlRegister }, $EXTENSION_CLASS_DESCRIPTOR->openExternally(Ljava/lang/String;)Z
|
||||
move-result v0
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
package app.revanced.patches.instagram.misc.removeBuildExpiredPopup
|
||||
|
||||
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
|
||||
import app.revanced.patcher.instructions
|
||||
import app.revanced.patcher.invoke
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
|
||||
internal const val MILLISECOND_IN_A_DAY_LITERAL = 0x5265c00L
|
||||
|
||||
internal val BytecodePatchContext.appUpdateLockoutBuilderMethod by gettingFirstMutableMethodDeclaratively(
|
||||
"android.hardware.sensor.hinge_angle",
|
||||
) {
|
||||
instructions(MILLISECOND_IN_A_DAY_LITERAL())
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package app.revanced.patches.instagram.misc.removeBuildExpiredPopup
|
||||
|
||||
import app.revanced.patcher.extensions.addInstruction
|
||||
import app.revanced.patcher.extensions.getInstruction
|
||||
import app.revanced.patcher.extensions.instructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val removeBuildExpiredPopupPatch = bytecodePatch(
|
||||
name = "Remove build expired popup",
|
||||
description = "Removes the popup that appears after a while, when the app version ages.",
|
||||
) {
|
||||
compatibleWith("com.instagram.android")
|
||||
|
||||
apply {
|
||||
appUpdateLockoutBuilderMethod.apply {
|
||||
val longToIntIndex = instructions.first { it.opcode == Opcode.LONG_TO_INT }.location.index
|
||||
val appAgeRegister = getInstruction<TwoRegisterInstruction>(longToIntIndex).registerA
|
||||
|
||||
// Set app age to 0 days old such that the build expired popup doesn't appear.
|
||||
addInstruction(longToIntIndex + 1, "const v$appAgeRegister, 0x0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,9 +20,7 @@ internal val BytecodePatchContext.storyUrlResponseJsonParserMethodMatch by compo
|
|||
instructions("story_item_to_share_url"())
|
||||
}
|
||||
|
||||
internal val BytecodePatchContext.profileUrlResponseJsonParserMethodMatch by composingFirstMethod(
|
||||
"ProfileUrlResponse",
|
||||
) {
|
||||
internal val BytecodePatchContext.profileUrlResponseJsonParserMethodMatch by composingFirstMethod {
|
||||
name("parseFromJson")
|
||||
instructions("profile_to_share_url"())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ val disableReelsScrollingPatch = bytecodePatch(
|
|||
name = "Disable Reels scrolling",
|
||||
description = "Disables the endless scrolling behavior in Instagram Reels, preventing swiping to the next Reel. " +
|
||||
"Note: On a clean install, the 'Tip' animation may appear but will stop on its own after a few seconds.",
|
||||
use = true,
|
||||
use = false
|
||||
) {
|
||||
compatibleWith("com.instagram.android")
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package app.revanced.patches.kleinanzeigen.ads
|
||||
|
||||
import app.revanced.patcher.definingClass
|
||||
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
|
||||
import app.revanced.patcher.name
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
|
||||
internal val BytecodePatchContext.getLibertyInitMethod by gettingFirstMutableMethodDeclaratively {
|
||||
name("init")
|
||||
definingClass { endsWith("/Liberty;") }
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package app.revanced.patches.kleinanzeigen.ads
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.returnEarly
|
||||
|
||||
@Suppress("unused")
|
||||
val hideAdsPatch = bytecodePatch(
|
||||
name = "Hide ads",
|
||||
description = "Hides sponsored ads and Google Ads. Also happens to disable Microsoft Clarity analytics.",
|
||||
) {
|
||||
compatibleWith("com.ebay.kleinanzeigen")
|
||||
|
||||
apply {
|
||||
getLibertyInitMethod.returnEarly()
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ val hideAdsPatch = bytecodePatch("Hide ads") {
|
|||
apply {
|
||||
admobHelperSetShowAdsMethod.addInstruction(0, "const p1, 0x0")
|
||||
listOf(admobHelperShouldShowAdsMethod, filmFragmentShowAdsMethod, memberExtensionShowAdsMethod).forEach {
|
||||
it.returnEarly(false)
|
||||
it.returnEarly()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package app.revanced.patches.music.layout.hide.general
|
||||
|
||||
import app.revanced.patches.music.misc.litho.filter.lithoFilterPatch
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.shared.layout.hide.general.hideLayoutComponentsPatch
|
||||
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
|
||||
|
||||
val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
|
||||
lithoFilterPatch = lithoFilterPatch,
|
||||
getAddLithoFilter = { addLithoFilter },
|
||||
settingsPatch = settingsPatch,
|
||||
filterClasses = setOf("Lapp/revanced/extension/shared/patches/components/CustomFilter;"),
|
||||
compatibleWithPackages = arrayOf("com.google.android.apps.youtube.music" to setOf("7.29.52", "8.10.52")),
|
||||
)
|
||||
|
|
@ -5,24 +5,10 @@ import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
|||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.util.returnEarly
|
||||
|
||||
@Deprecated("This patch is useless by itself and has been merged into another patch.", ReplaceWith("unlockAndroidAutoMediaBrowserPatch"))
|
||||
@Suppress("unused")
|
||||
val bypassCertificateChecksPatch = bytecodePatch(
|
||||
name = "Bypass certificate checks",
|
||||
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52",
|
||||
),
|
||||
)
|
||||
|
||||
apply {
|
||||
checkCertificateMethod.returnEarly(true)
|
||||
}
|
||||
dependsOn(unlockAndroidAutoMediaBrowserPatch)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package app.revanced.patches.music.misc.androidauto
|
||||
|
||||
import app.revanced.patcher.firstMutableMethodDeclaratively
|
||||
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
|
||||
import app.revanced.patcher.instructions
|
||||
import app.revanced.patcher.invoke
|
||||
import app.revanced.patcher.parameterTypes
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.returnType
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
|
||||
internal val BytecodePatchContext.checkCertificateMethod by gettingFirstMutableMethodDeclaratively(
|
||||
"X509",
|
||||
|
|
@ -14,3 +16,14 @@ internal val BytecodePatchContext.checkCertificateMethod by gettingFirstMutableM
|
|||
parameterTypes("Ljava/lang/String;")
|
||||
instructions("Failed to get certificate"(String::contains))
|
||||
}
|
||||
|
||||
internal val BytecodePatchContext.searchMediaItemsConstructorMethod by gettingFirstMutableMethodDeclaratively(
|
||||
"ytm_media_browser/search_media_items",
|
||||
) {
|
||||
returnType("V")
|
||||
}
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
internal fun ClassDef.getSearchMediaItemsExecuteMethod() = firstMutableMethodDeclaratively {
|
||||
parameterTypes()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package app.revanced.patches.music.misc.androidauto
|
||||
|
||||
import app.revanced.patcher.extensions.fieldReference
|
||||
import app.revanced.patcher.extensions.instructions
|
||||
import app.revanced.patcher.extensions.replaceInstruction
|
||||
import app.revanced.patcher.immutableClassDef
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.registersUsed
|
||||
import app.revanced.util.returnEarly
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
@Suppress("unused")
|
||||
val unlockAndroidAutoMediaBrowserPatch = bytecodePatch(
|
||||
name = "Unlock Android Auto Media Browser",
|
||||
description = "Unlocks Android Auto Media Browser which enables the search function including speech to text.",
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52",
|
||||
),
|
||||
)
|
||||
|
||||
apply {
|
||||
checkCertificateMethod.returnEarly(true)
|
||||
|
||||
searchMediaItemsConstructorMethod.immutableClassDef.getSearchMediaItemsExecuteMethod().apply {
|
||||
val targetIndex = instructions.indexOfFirst {
|
||||
it.opcode == Opcode.IGET_OBJECT && it.fieldReference?.type == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
val register = instructions[targetIndex].registersUsed.first()
|
||||
replaceInstruction(targetIndex, "const-string v$register, \"com.google.android.apps.youtube.music\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,20 +7,15 @@ import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch
|
|||
|
||||
@Suppress("unused")
|
||||
val enableDebuggingPatch = enableDebuggingPatch(
|
||||
block = {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
sharedExtensionPatch = sharedExtensionPatch,
|
||||
settingsPatch = settingsPatch,
|
||||
compatibleWithPackages = arrayOf(
|
||||
"com.google.android.apps.youtube.music" to setOf(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
)
|
||||
)
|
||||
},
|
||||
),
|
||||
// String feature flag does not appear to be present with YT Music.
|
||||
hookStringFeatureFlag = false,
|
||||
preferenceScreen = PreferenceScreen.MISC
|
||||
preferenceScreen = PreferenceScreen.MISC,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package app.revanced.patches.music.misc.litho.filter
|
||||
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.shared.conversionContextToStringMethod
|
||||
import app.revanced.patches.shared.misc.litho.filter.lithoFilterPatch
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
val lithoFilterPatch = lithoFilterPatch(
|
||||
componentCreateInsertionIndex = {
|
||||
// No supported version clobbers p2 so we can just do our things before the return instruction.
|
||||
indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT)
|
||||
},
|
||||
getConversionContextToStringMethod = BytecodePatchContext::conversionContextToStringMethod::get,
|
||||
) {
|
||||
dependsOn(sharedExtensionPatch)
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
package app.revanced.patches.music.shared
|
||||
|
||||
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
|
||||
import app.revanced.patcher.definingClass
|
||||
import app.revanced.patcher.gettingFirstMethodDeclaratively
|
||||
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
|
||||
import app.revanced.patcher.instructions
|
||||
import app.revanced.patcher.invoke
|
||||
import app.revanced.patcher.name
|
||||
import app.revanced.patcher.parameterTypes
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
|
|
@ -16,3 +19,16 @@ internal val BytecodePatchContext.mainActivityOnCreateMethod by gettingFirstMuta
|
|||
returnType("V")
|
||||
parameterTypes("Landroid/os/Bundle;")
|
||||
}
|
||||
|
||||
internal val BytecodePatchContext.conversionContextToStringMethod by gettingFirstMethodDeclaratively(
|
||||
"ConversionContext{containerInternal=",
|
||||
", gridColumnCount=",
|
||||
", gridColumnIndex=",
|
||||
", templateLoggerFactory=",
|
||||
", rootDisposableContainer=",
|
||||
", elementId=",
|
||||
", identifierProperty=",
|
||||
) {
|
||||
name("toString")
|
||||
parameterTypes()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package app.revanced.patches.nothingx.misc.extension
|
||||
|
||||
import app.revanced.patcher.definingClass
|
||||
import app.revanced.patcher.name
|
||||
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||
|
||||
val sharedExtensionPatch = sharedExtensionPatch(
|
||||
extensionName = "nothingx",
|
||||
extensionHook {
|
||||
name("onCreate")
|
||||
definingClass { contains("BaseApplication") }
|
||||
},
|
||||
)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package app.revanced.patches.nothingx.misc.logk1token
|
||||
|
||||
import app.revanced.patcher.definingClass
|
||||
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
|
||||
import app.revanced.patcher.name
|
||||
import app.revanced.patcher.parameterTypes
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.returnType
|
||||
|
||||
/**
|
||||
* Fingerprint for the Application onCreate method.
|
||||
* This is used to trigger scanning for existing log files on app startup.
|
||||
*/
|
||||
internal val BytecodePatchContext.applicationOnCreateMethod by gettingFirstMutableMethodDeclaratively {
|
||||
name("onCreate")
|
||||
definingClass { endsWith("BaseApplication;") }
|
||||
returnType("V")
|
||||
parameterTypes()
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package app.revanced.patches.nothingx.misc.logk1token
|
||||
|
||||
import app.revanced.patcher.extensions.addInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.nothingx.misc.extension.sharedExtensionPatch
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/nothingx/patches/ShowK1TokensPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val showK1TokensPatch = bytecodePatch(
|
||||
name = "Show K1 token(s)",
|
||||
description = "Shows the K1 authentication token(s) in a dialog and logs it to logcat " +
|
||||
"for pairing with GadgetBridge without requiring root access. " +
|
||||
"After installing this patch, pair your watch with the Nothing X app and " +
|
||||
"use the token from the dialog or logcat.",
|
||||
) {
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
compatibleWith("com.nothing.smartcenter"())
|
||||
|
||||
apply {
|
||||
// Hook Application.onCreate to get K1 tokens from database and log files.
|
||||
// This will find K1 tokens that were already written to log files.
|
||||
// p0 is the Application context in onCreate.
|
||||
applicationOnCreateMethod.addInstruction(
|
||||
0,
|
||||
"invoke-static { p0 }, $EXTENSION_CLASS_DESCRIPTOR->showK1Tokens(Landroid/content/Context;)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package app.revanced.patches.shared.layout.hide.general
|
||||
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.shared.misc.settings.preference.InputType
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||
import kotlin.collections.toTypedArray
|
||||
|
||||
internal fun hideLayoutComponentsPatch(
|
||||
lithoFilterPatch: Patch,
|
||||
getAddLithoFilter: () -> (String) -> Unit, // Temporal hack until YouTube can use the shared litho filter patch.
|
||||
settingsPatch: Patch,
|
||||
additionalDependencies: Set<Patch> = emptySet(),
|
||||
filterClasses: Set<String>,
|
||||
vararg compatibleWithPackages: Pair<String, Set<String>?>,
|
||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||
) = bytecodePatch(
|
||||
name = "Hide layout components",
|
||||
description = "Adds options to hide general layout components.",
|
||||
) {
|
||||
dependsOn(
|
||||
lithoFilterPatch,
|
||||
settingsPatch,
|
||||
*additionalDependencies.toTypedArray(),
|
||||
addResourcesPatch,
|
||||
)
|
||||
|
||||
compatibleWith(packages = compatibleWithPackages)
|
||||
|
||||
apply {
|
||||
addResources("shared", "layout.hide.general.hideLayoutComponentsPatch")
|
||||
|
||||
PreferenceScreen.GENERAL.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_custom_filter_screen",
|
||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_custom_filter"),
|
||||
TextPreference("revanced_custom_filter_strings", inputType = InputType.TEXT_MULTI_LINE),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val addLithoFilter = getAddLithoFilter()
|
||||
|
||||
filterClasses.forEach { className ->
|
||||
addLithoFilter(className)
|
||||
}
|
||||
|
||||
executeBlock()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,7 @@ package app.revanced.patches.shared.misc.debugging
|
|||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.getInstruction
|
||||
import app.revanced.patcher.immutableClassDef
|
||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
|
|
@ -22,16 +21,20 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
|||
* Patch shared with YouTube and YT Music.
|
||||
*/
|
||||
internal fun enableDebuggingPatch(
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||
sharedExtensionPatch: Patch,
|
||||
settingsPatch: Patch,
|
||||
vararg compatibleWithPackages: Pair<String, Set<String>>,
|
||||
hookStringFeatureFlag: Boolean,
|
||||
preferenceScreen: BasePreferenceScreen.Screen,
|
||||
additionalDebugPreferences: List<BasePreference> = emptyList(),
|
||||
) = bytecodePatch(
|
||||
name = "Enable debugging",
|
||||
description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
|
||||
) {
|
||||
compatibleWith(packages = compatibleWithPackages)
|
||||
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
resourcePatch {
|
||||
apply {
|
||||
|
|
@ -54,38 +57,28 @@ internal fun enableDebuggingPatch(
|
|||
},
|
||||
)
|
||||
|
||||
block()
|
||||
|
||||
apply {
|
||||
executeBlock()
|
||||
|
||||
addResources("shared", "misc.debugging.enableDebuggingPatch")
|
||||
|
||||
val preferences = mutableSetOf<BasePreference>(
|
||||
val preferences = setOf(
|
||||
SwitchPreference("revanced_debug"),
|
||||
)
|
||||
|
||||
preferences.addAll(additionalDebugPreferences)
|
||||
|
||||
preferences.addAll(
|
||||
listOf(
|
||||
SwitchPreference("revanced_debug_stacktrace"),
|
||||
SwitchPreference("revanced_debug_toast_on_error"),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_export_logs_to_clipboard",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
|
||||
selectable = true,
|
||||
),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_logs_clear_buffer",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
|
||||
selectable = true,
|
||||
),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_feature_flags_manager",
|
||||
tag = "app.revanced.extension.shared.settings.preference.FeatureFlagsManagerPreference",
|
||||
selectable = true,
|
||||
),
|
||||
SwitchPreference("revanced_debug_protobuffer"),
|
||||
SwitchPreference("revanced_debug_stacktrace"),
|
||||
SwitchPreference("revanced_debug_toast_on_error"),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_export_logs_to_clipboard",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
|
||||
selectable = true,
|
||||
),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_logs_clear_buffer",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
|
||||
selectable = true,
|
||||
),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_feature_flags_manager",
|
||||
tag = "app.revanced.extension.shared.settings.preference.FeatureFlagsManagerPreference",
|
||||
selectable = true,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package app.revanced.patches.shared.misc.litho.filter
|
||||
|
||||
import app.revanced.patcher.accessFlags
|
||||
import app.revanced.patcher.composingFirstMethod
|
||||
import app.revanced.patcher.custom
|
||||
import app.revanced.patcher.definingClass
|
||||
import app.revanced.patcher.gettingFirstMethod
|
||||
import app.revanced.patcher.gettingFirstMethodDeclaratively
|
||||
import app.revanced.patcher.gettingFirstMutableMethod
|
||||
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
|
||||
import app.revanced.patcher.immutableClassDef
|
||||
import app.revanced.patcher.instructions
|
||||
import app.revanced.patcher.invoke
|
||||
import app.revanced.patcher.opcodes
|
||||
import app.revanced.patcher.parameterTypes
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.returnType
|
||||
import app.revanced.patcher.strings
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val BytecodePatchContext.lithoFilterMethod by gettingFirstMutableMethodDeclaratively {
|
||||
definingClass { endsWith("/LithoFilterPatch;") }
|
||||
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a method that use the protobuf of our component.
|
||||
*/
|
||||
internal val BytecodePatchContext.protobufBufferReferenceMethod by gettingFirstMutableMethodDeclaratively {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returnType("V")
|
||||
parameterTypes("I", "Ljava/nio/ByteBuffer;")
|
||||
opcodes(Opcode.IPUT, Opcode.INVOKE_VIRTUAL, Opcode.MOVE_RESULT, Opcode.SUB_INT_2ADDR)
|
||||
}
|
||||
|
||||
internal val BytecodePatchContext.componentContextParserMethodMatch by composingFirstMethod {
|
||||
instructions("Number of bits must be positive"())
|
||||
}
|
||||
|
||||
internal val BytecodePatchContext.emptyComponentMethod by gettingFirstMethodDeclaratively("EmptyComponent") {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
|
||||
parameterTypes()
|
||||
custom {
|
||||
immutableClassDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1
|
||||
}
|
||||
}
|
||||
|
||||
internal val BytecodePatchContext.componentCreateMethod by gettingFirstMutableMethod(
|
||||
"Element missing correct type extension",
|
||||
"Element missing type",
|
||||
)
|
||||
|
||||
internal val BytecodePatchContext.lithoThreadExecutorMethod by gettingFirstMutableMethodDeclaratively {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
parameterTypes("I", "I", "I")
|
||||
instructions(1L()) // 1L = default thread timeout.
|
||||
custom { immutableClassDef.superclass == "Ljava/util/concurrent/ThreadPoolExecutor;" }
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
@file:Suppress("SpellCheckingInspection")
|
||||
|
||||
package app.revanced.patches.shared.misc.litho.filter
|
||||
|
||||
import app.revanced.patcher.extensions.addInstruction
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.getInstruction
|
||||
import app.revanced.patcher.extensions.removeInstructions
|
||||
import app.revanced.patcher.extensions.replaceInstruction
|
||||
import app.revanced.patcher.firstClassDef
|
||||
import app.revanced.patcher.immutableClassDef
|
||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.shared.conversionContextToStringMethod
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.findFreeRegister
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
/**
|
||||
* Used to add a hook point to the extension stub.
|
||||
*/
|
||||
lateinit var addLithoFilter: (String) -> Unit
|
||||
private set
|
||||
|
||||
/**
|
||||
* Counts the number of filters added to the static field array.
|
||||
*/
|
||||
private var filterCount = 0
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/patches/litho/LithoFilterPatch;"
|
||||
|
||||
/**
|
||||
* A patch that allows to filter Litho components based on their identifier or path.
|
||||
*
|
||||
* @param componentCreateInsertionIndex The index to insert the filtering code in the component create method.
|
||||
* @param getConversionContextToStringMethod The Method of the conversion context to string method.
|
||||
* @param executeBlock The additional execution block of the patch.
|
||||
* @param block The additional block to build the patch.
|
||||
*/
|
||||
internal fun lithoFilterPatch(
|
||||
componentCreateInsertionIndex: Method.() -> Int,
|
||||
getConversionContextToStringMethod: BytecodePatchContext.() -> Method,
|
||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
) = bytecodePatch(
|
||||
description = "Hooks the method which parses the bytes into a ComponentContext to filter components.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch(),
|
||||
)
|
||||
|
||||
/**
|
||||
* The following patch inserts a hook into the method that parses the bytes into a ComponentContext.
|
||||
* This method contains a StringBuilder object that represents the pathBuilder of the component.
|
||||
* The pathBuilder is used to filter components by their path.
|
||||
*
|
||||
* Additionally, the method contains a reference to the component's identifier.
|
||||
* The identifier is used to filter components by their identifier.
|
||||
*
|
||||
* The protobuf buffer is passed along from a different injection point before the filtering occurs.
|
||||
* The buffer is a large byte array that represents the component tree.
|
||||
* This byte array is searched for strings that indicate the current component.
|
||||
*
|
||||
* All modifications done here must allow all the original code to still execute
|
||||
* even when filtering, otherwise memory leaks or poor app performance may occur.
|
||||
*
|
||||
* The following pseudocode shows how this patch works:
|
||||
*
|
||||
* class SomeOtherClass {
|
||||
* // Called before ComponentContextParser.parseComponent() method.
|
||||
* public void someOtherMethod(ByteBuffer byteBuffer) {
|
||||
* ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* class CreateComponentClass {
|
||||
* public Component createComponent() {
|
||||
* ...
|
||||
*
|
||||
* if (extensionClass.shouldFilter(identifier, path)) { // Inserted by this patch.
|
||||
* return emptyComponent;
|
||||
* }
|
||||
* return originalUnpatchedComponent; // Original code.
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
apply {
|
||||
// Remove dummy filter from extenion static field
|
||||
// and add the filters included during patching.
|
||||
lithoFilterMethod.apply {
|
||||
removeInstructions(2, 4) // Remove dummy filter.
|
||||
|
||||
addLithoFilter = { classDescriptor ->
|
||||
addInstructions(
|
||||
2,
|
||||
"""
|
||||
new-instance v1, $classDescriptor
|
||||
invoke-direct { v1 }, $classDescriptor-><init>()V
|
||||
const/16 v2, ${filterCount++}
|
||||
aput-object v1, v0, v2
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Add an interceptor to steal the protobuf of our component.
|
||||
protobufBufferReferenceMethod.addInstruction(
|
||||
0,
|
||||
"invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
|
||||
)
|
||||
|
||||
// Hook the method that parses bytes into a ComponentContext.
|
||||
// Allow the method to run to completion, and override the
|
||||
// return value with an empty component if it should be filtered.
|
||||
// It is important to allow the original code to always run to completion,
|
||||
// otherwise high memory usage and poor app performance can occur.
|
||||
|
||||
// Find the identifier/path fields of the conversion context.
|
||||
val conversionContextIdentifierField = componentContextParserMethodMatch.let {
|
||||
// Identifier field is loaded just before the string declaration.
|
||||
val index = it.method.indexOfFirstInstructionReversedOrThrow(it[0]) {
|
||||
// Our instruction reads a String from a field of the ConversionContext class.
|
||||
val reference = getReference<FieldReference>()
|
||||
reference?.definingClass == conversionContextToStringMethod.immutableClassDef.type &&
|
||||
reference.type == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
it.method.getInstruction<ReferenceInstruction>(index).getReference<FieldReference>()!!
|
||||
}
|
||||
|
||||
val conversionContextPathBuilderField = conversionContextToStringMethod.immutableClassDef
|
||||
.fields.single { field -> field.type == "Ljava/lang/StringBuilder;" }
|
||||
|
||||
// Find class and methods to create an empty component.
|
||||
val builderMethodDescriptor = emptyComponentMethod.immutableClassDef.methods.single {
|
||||
// The only static method in the class.
|
||||
method ->
|
||||
AccessFlags.STATIC.isSet(method.accessFlags)
|
||||
}
|
||||
|
||||
val emptyComponentField = firstClassDef {
|
||||
// Only one field that matches.
|
||||
type == builderMethodDescriptor.returnType
|
||||
}.fields.single()
|
||||
|
||||
// Match all component creations methods
|
||||
componentCreateMethod.apply {
|
||||
val insertIndex = componentCreateInsertionIndex()
|
||||
val freeRegister = findFreeRegister(insertIndex)
|
||||
val identifierRegister = findFreeRegister(insertIndex, freeRegister)
|
||||
val pathRegister = findFreeRegister(insertIndex, freeRegister, identifierRegister)
|
||||
|
||||
addInstructionsAtControlFlowLabel(
|
||||
insertIndex,
|
||||
"""
|
||||
move-object/from16 v$freeRegister, p2 # ConversionContext parameter
|
||||
check-cast v$freeRegister, ${getConversionContextToStringMethod().immutableClassDef.type} # Check we got the actual ConversionContext
|
||||
|
||||
# Get identifier and path from ConversionContext
|
||||
iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField
|
||||
iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField
|
||||
|
||||
# Check if the component should be filtered.
|
||||
invoke-static { v$identifierRegister, v$pathRegister }, $EXTENSION_CLASS_DESCRIPTOR->isFiltered(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :unfiltered
|
||||
|
||||
# Return an empty component
|
||||
move-object/from16 v$freeRegister, p1
|
||||
invoke-static { v$freeRegister }, $builderMethodDescriptor
|
||||
move-result-object v$freeRegister
|
||||
iget-object v$freeRegister, v$freeRegister, $emptyComponentField
|
||||
return-object v$freeRegister
|
||||
|
||||
:unfiltered
|
||||
nop
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Check if needed in music.
|
||||
// Change Litho thread executor to 1 thread to fix layout issue in unpatched YouTube.
|
||||
lithoThreadExecutorMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->getExecutorCorePoolSize(I)I
|
||||
move-result p1
|
||||
invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->getExecutorMaxThreads(I)I
|
||||
move-result p2
|
||||
""",
|
||||
)
|
||||
|
||||
executeBlock()
|
||||
}
|
||||
|
||||
afterDependents {
|
||||
// Save the number of filters added.
|
||||
lithoFilterMethod.replaceInstruction(0, "const/16 v0, $filterCount")
|
||||
}
|
||||
|
||||
block()
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
package app.revanced.patches.strava.distractions
|
||||
|
||||
import app.revanced.com.android.tools.smali.dexlib2.iface.value.MutableBooleanEncodedValue.Companion.toMutable
|
||||
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableClassDef
|
||||
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
|
||||
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.firstMutableClassDef
|
||||
import app.revanced.patcher.firstMutableMethod
|
||||
import app.revanced.patcher.patch.booleanOption
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.strava.misc.extension.sharedExtensionPatch
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.value.ImmutableBooleanEncodedValue
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.logging.Logger
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/strava/HideDistractionsPatch;"
|
||||
private const val MODULAR_FRAMEWORK_CLASS_DESCRIPTOR_PREFIX = "Lcom/strava/modularframework"
|
||||
|
||||
private const val METHOD_SUFFIX = "\$original"
|
||||
|
||||
private data class FilterablePropertyFingerprint(
|
||||
val name: String,
|
||||
val parameterTypes: List<String> = listOf(),
|
||||
)
|
||||
|
||||
private val fingerprints = arrayOf(
|
||||
FilterablePropertyFingerprint("ChildrenEntries"),
|
||||
FilterablePropertyFingerprint("Entries"),
|
||||
FilterablePropertyFingerprint("Field", listOf("Ljava/lang/String;")),
|
||||
FilterablePropertyFingerprint("Fields"),
|
||||
FilterablePropertyFingerprint("MenuItems"),
|
||||
FilterablePropertyFingerprint("Modules"),
|
||||
FilterablePropertyFingerprint("Properties"),
|
||||
FilterablePropertyFingerprint("StateMap"),
|
||||
FilterablePropertyFingerprint("Submodules"),
|
||||
)
|
||||
|
||||
@Suppress("unused")
|
||||
val hideDistractionsPatch = bytecodePatch(
|
||||
name = "Hide distractions",
|
||||
description = "Hides elements that are not essential.",
|
||||
) {
|
||||
compatibleWith("com.strava")
|
||||
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
val logger = Logger.getLogger(this::class.java.name)
|
||||
|
||||
val options = mapOf(
|
||||
"upselling" to booleanOption(
|
||||
name = "Upselling",
|
||||
description = "Elements that suggest you subscribe.",
|
||||
default = true,
|
||||
required = true,
|
||||
),
|
||||
"promo" to booleanOption(
|
||||
name = "Promotions",
|
||||
default = true,
|
||||
required = true,
|
||||
),
|
||||
"followSuggestions" to booleanOption(
|
||||
name = "Who to Follow",
|
||||
description = "Popular athletes, followers, people near you etc.",
|
||||
default = true,
|
||||
required = true,
|
||||
),
|
||||
"challengeSuggestions" to booleanOption(
|
||||
name = "Suggested Challenges",
|
||||
description = "Random challenges Strava wants you to join.",
|
||||
default = true,
|
||||
required = true,
|
||||
),
|
||||
"joinChallenge" to booleanOption(
|
||||
name = "Join Challenge",
|
||||
description = "Challenges your follows have joined.",
|
||||
default = false,
|
||||
required = true,
|
||||
),
|
||||
"joinClub" to booleanOption(
|
||||
name = "Joined a club",
|
||||
description = "Clubs your follows have joined.",
|
||||
default = false,
|
||||
required = true,
|
||||
),
|
||||
"activityLookback" to booleanOption(
|
||||
name = "Your activity from X years ago",
|
||||
default = false,
|
||||
required = true,
|
||||
),
|
||||
)
|
||||
|
||||
apply {
|
||||
// region Write option values into extension class.
|
||||
|
||||
val extensionClass = firstMutableClassDef { type == EXTENSION_CLASS_DESCRIPTOR }.apply {
|
||||
options.forEach { (key, option) ->
|
||||
staticFields.first { field -> field.name == key }.initialValue =
|
||||
ImmutableBooleanEncodedValue.forBoolean(option.value == true).toMutable()
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Intercept all classes' property getter calls.
|
||||
|
||||
fun MutableMethod.cloneAndIntercept(
|
||||
classDef: MutableClassDef,
|
||||
extensionMethodName: String,
|
||||
extensionMethodParameterTypes: List<String>,
|
||||
) {
|
||||
val extensionMethodReference = ImmutableMethodReference(
|
||||
EXTENSION_CLASS_DESCRIPTOR,
|
||||
extensionMethodName,
|
||||
extensionMethodParameterTypes,
|
||||
returnType,
|
||||
)
|
||||
|
||||
if (extensionClass.directMethods.none { method ->
|
||||
MethodUtil.methodSignaturesMatch(method, extensionMethodReference)
|
||||
}
|
||||
) {
|
||||
logger.info { "Skipped interception of $this due to missing $extensionMethodReference" }
|
||||
return
|
||||
}
|
||||
|
||||
classDef.virtualMethods -= this
|
||||
|
||||
val clone = ImmutableMethod.of(this).toMutable()
|
||||
|
||||
classDef.virtualMethods += clone
|
||||
|
||||
if (implementation != null) {
|
||||
val registers = List(extensionMethodParameterTypes.size) { index -> "p$index" }.joinToString(
|
||||
separator = ",",
|
||||
prefix = "{",
|
||||
postfix = "}",
|
||||
)
|
||||
|
||||
clone.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static $registers, $extensionMethodReference
|
||||
move-result-object v0
|
||||
return-object v0
|
||||
""",
|
||||
)
|
||||
|
||||
logger.fine { "Intercepted $this with $extensionMethodReference" }
|
||||
}
|
||||
|
||||
name += METHOD_SUFFIX
|
||||
|
||||
classDef.virtualMethods += this
|
||||
}
|
||||
|
||||
classDefs.filter { it.type.startsWith(MODULAR_FRAMEWORK_CLASS_DESCRIPTOR_PREFIX) }.forEach { classDef ->
|
||||
val mutableClassDef by lazy { classDefs.getOrReplaceMutable(classDef) }
|
||||
|
||||
classDef.virtualMethods.forEach { method ->
|
||||
fingerprints.find { fingerprint ->
|
||||
method.name == "get${fingerprint.name}" && method.parameterTypes == fingerprint.parameterTypes
|
||||
}?.let { fingerprint ->
|
||||
// Upcast to the interface if this is an interface implementation.
|
||||
val parameterType = classDef.interfaces.find {
|
||||
classDefs.find { interfaceDef -> interfaceDef.type == it }?.virtualMethods?.any { interfaceMethod ->
|
||||
MethodUtil.methodSignaturesMatch(interfaceMethod, method)
|
||||
} == true
|
||||
} ?: classDef.type
|
||||
|
||||
mutableClassDef.firstMutableMethod(method).cloneAndIntercept(
|
||||
mutableClassDef,
|
||||
"filter${fingerprint.name}",
|
||||
listOf(parameterType) + fingerprint.parameterTypes,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
package app.revanced.patches.strava.groupkudos
|
||||
|
||||
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.getInstruction
|
||||
import app.revanced.patcher.extensions.instructions
|
||||
import app.revanced.patcher.immutableClassDef
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.util.childElementsSequence
|
||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.AccessFlags.*
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction11x
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction31i
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
import org.w3c.dom.Element
|
||||
|
||||
private const val VIEW_CLASS_DESCRIPTOR = "Landroid/view/View;"
|
||||
private const val ON_CLICK_LISTENER_CLASS_DESCRIPTOR = "Landroid/view/View\$OnClickListener;"
|
||||
|
||||
private var shakeToKudosStringId = -1
|
||||
private var kudosIdId = -1
|
||||
private var leaveIdId = -1
|
||||
|
||||
private val addGiveKudosButtonToLayoutPatch = resourcePatch {
|
||||
fun String.toResourceId() = substring(2).toInt(16)
|
||||
|
||||
apply {
|
||||
document("res/values/public.xml").use { public ->
|
||||
fun Sequence<Element>.firstByName(name: String) = first {
|
||||
it.getAttribute("name") == name
|
||||
}
|
||||
|
||||
val publicElements = public.documentElement.childElementsSequence().filter {
|
||||
it.tagName == "public"
|
||||
}
|
||||
val idElements = publicElements.filter {
|
||||
it.getAttribute("type") == "id"
|
||||
}
|
||||
val stringElements = publicElements.filter {
|
||||
it.getAttribute("type") == "string"
|
||||
}
|
||||
|
||||
shakeToKudosStringId =
|
||||
stringElements.firstByName("shake_to_kudos_dialog_title").getAttribute("id").toResourceId()
|
||||
|
||||
val kudosIdNode = idElements.firstByName("kudos").apply {
|
||||
kudosIdId = getAttribute("id").toResourceId()
|
||||
}
|
||||
|
||||
document("res/layout/grouped_activities_dialog_group_tab.xml").use { layout ->
|
||||
layout.childNodes.findElementByAttributeValueOrThrow("android:id", "@id/leave_group_button_container")
|
||||
.apply {
|
||||
// Change from "FrameLayout".
|
||||
layout.renameNode(this, namespaceURI, "LinearLayout")
|
||||
|
||||
val leaveButton = childElementsSequence().first()
|
||||
// Get "Leave Group" button ID for bytecode matching.
|
||||
val leaveButtonIdName = leaveButton.getAttribute("android:id").substringAfter('/')
|
||||
leaveIdId = idElements.firstByName(leaveButtonIdName).getAttribute("id").toResourceId()
|
||||
|
||||
// Add surrounding padding to offset decrease on buttons.
|
||||
setAttribute("android:paddingHorizontal", "@dimen/space_2xs")
|
||||
|
||||
// Place buttons next to each other with equal width.
|
||||
val kudosButton = leaveButton.apply {
|
||||
setAttribute("android:layout_width", "0dp")
|
||||
setAttribute("android:layout_weight", "1")
|
||||
// Decrease padding between buttons from "@dimen/button_large_padding" ...
|
||||
setAttribute("android:paddingHorizontal", "@dimen/space_xs")
|
||||
}.cloneNode(true) as Element
|
||||
kudosButton.apply {
|
||||
setAttribute("android:id", "@id/${kudosIdNode.getAttribute("name")}")
|
||||
setAttribute("android:text", "@string/kudos_button")
|
||||
}.let(::appendChild)
|
||||
|
||||
// Downgrade emphasis of "Leave Group" button from "primary".
|
||||
leaveButton.setAttribute("app:emphasis", "secondary")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
val addGiveGroupKudosButtonToGroupActivity = bytecodePatch(
|
||||
name = "Add 'Give Kudos' button to 'Group Activity'",
|
||||
description = "Adds a button that triggers the same action as shaking your phone would.",
|
||||
) {
|
||||
compatibleWith("com.strava")
|
||||
|
||||
dependsOn(addGiveKudosButtonToLayoutPatch)
|
||||
|
||||
apply {
|
||||
val className = initMethod.immutableClassDef.type
|
||||
val onClickListenerClassName = "${className.substringBeforeLast(';')}\$GiveKudosOnClickListener;"
|
||||
|
||||
initMethod.apply {
|
||||
val constLeaveIdInstruction = instructions.filterIsInstance<BuilderInstruction31i>().first {
|
||||
it.narrowLiteral == leaveIdId
|
||||
}
|
||||
val findViewByIdInstruction =
|
||||
getInstruction<BuilderInstruction35c>(constLeaveIdInstruction.location.index + 1)
|
||||
val moveViewInstruction = getInstruction<BuilderInstruction11x>(constLeaveIdInstruction.location.index + 2)
|
||||
val checkCastButtonInstruction =
|
||||
getInstruction<BuilderInstruction21c>(constLeaveIdInstruction.location.index + 3)
|
||||
|
||||
val buttonClassName = checkCastButtonInstruction.getReference<TypeReference>()!!.type
|
||||
|
||||
addInstructions(
|
||||
constLeaveIdInstruction.location.index,
|
||||
"""
|
||||
${constLeaveIdInstruction.opcode.name} v${constLeaveIdInstruction.registerA}, $kudosIdId
|
||||
${findViewByIdInstruction.opcode.name} { v${findViewByIdInstruction.registerC}, v${findViewByIdInstruction.registerD} }, ${findViewByIdInstruction.reference}
|
||||
${moveViewInstruction.opcode.name} v${moveViewInstruction.registerA}
|
||||
${checkCastButtonInstruction.opcode.name} v${checkCastButtonInstruction.registerA}, ${checkCastButtonInstruction.reference}
|
||||
new-instance v0, $onClickListenerClassName
|
||||
invoke-direct { v0, p0 }, $onClickListenerClassName-><init>($className)V
|
||||
invoke-virtual { p3, v0 }, $buttonClassName->setOnClickListener($ON_CLICK_LISTENER_CLASS_DESCRIPTOR)V
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
val actionHandlerMethod = initMethod.immutableClassDef.getActionHandlerMethod()
|
||||
val constShakeToKudosStringIndex = actionHandlerMethod.instructions.indexOfFirst {
|
||||
it is NarrowLiteralInstruction && it.narrowLiteral == shakeToKudosStringId
|
||||
}
|
||||
val getSingletonInstruction = actionHandlerMethod.instructions.filterIsInstance<BuilderInstruction21c>().last {
|
||||
it.opcode == Opcode.SGET_OBJECT && it.location.index < constShakeToKudosStringIndex
|
||||
}
|
||||
|
||||
val outerThisField = ImmutableField(
|
||||
onClickListenerClassName,
|
||||
"outerThis",
|
||||
className,
|
||||
PUBLIC.value or FINAL.value or SYNTHETIC.value,
|
||||
null,
|
||||
listOf(),
|
||||
setOf(),
|
||||
)
|
||||
|
||||
val initFieldMethod = ImmutableMethod(
|
||||
onClickListenerClassName,
|
||||
"<init>",
|
||||
listOf(ImmutableMethodParameter(className, setOf(), "outerThis")),
|
||||
"V",
|
||||
PUBLIC.value or SYNTHETIC.value or CONSTRUCTOR.value,
|
||||
setOf(),
|
||||
setOf(),
|
||||
MutableMethodImplementation(2),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
iput-object p1, p0, $outerThisField
|
||||
return-void
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
val onClickMethod = ImmutableMethod(
|
||||
onClickListenerClassName,
|
||||
"onClick",
|
||||
listOf(ImmutableMethodParameter(VIEW_CLASS_DESCRIPTOR, setOf(), "v")),
|
||||
"V",
|
||||
PUBLIC.value or FINAL.value,
|
||||
setOf(),
|
||||
setOf(),
|
||||
MutableMethodImplementation(2),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
sget-object p1, ${getSingletonInstruction.reference}
|
||||
iget-object p0, p0, $outerThisField
|
||||
invoke-virtual { p0, p1 }, $actionHandlerMethod
|
||||
return-void
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
ImmutableClassDef(
|
||||
onClickListenerClassName,
|
||||
PUBLIC.value or FINAL.value or SYNTHETIC.value,
|
||||
"Ljava/lang/Object;",
|
||||
listOf(ON_CLICK_LISTENER_CLASS_DESCRIPTOR),
|
||||
"ProGuard", // Same as source file name of other classes.
|
||||
listOf(),
|
||||
setOf(outerThisField),
|
||||
setOf(initFieldMethod, onClickMethod),
|
||||
).let(classDefs::add)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package app.revanced.patches.strava.groupkudos
|
||||
|
||||
import app.revanced.patcher.firstMutableMethod
|
||||
import app.revanced.patcher.gettingFirstMutableMethodDeclaratively
|
||||
import app.revanced.patcher.name
|
||||
import app.revanced.patcher.parameterTypes
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
|
||||
internal val BytecodePatchContext.initMethod by gettingFirstMutableMethodDeclaratively {
|
||||
name("<init>")
|
||||
parameterTypes("Lcom/strava/feed/view/modal/GroupTabFragment;", "Z", "Landroidx/fragment/app/FragmentManager;")
|
||||
}
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
internal fun ClassDef.getActionHandlerMethod() = firstMutableMethod("state")
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package app.revanced.patches.strava.media.download
|
||||
|
||||
import app.revanced.patcher.extensions.ExternalLabel
|
||||
import app.revanced.patcher.extensions.addInstruction
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.getInstruction
|
||||
import app.revanced.patcher.extensions.instructions
|
||||
import app.revanced.patcher.firstClassDef
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceType
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.strava.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.writeRegister
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction22c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
|
||||
private const val ACTION_CLASS_DESCRIPTOR = "Lcom/strava/bottomsheet/Action;"
|
||||
private const val MEDIA_CLASS_DESCRIPTOR = "Lcom/strava/photos/data/Media;"
|
||||
private const val MEDIA_DOWNLOAD_CLASS_DESCRIPTOR = "Lapp/revanced/extension/strava/AddMediaDownloadPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val addMediaDownloadPatch = bytecodePatch(
|
||||
name = "Add media download",
|
||||
description = "Extends the full-screen media viewer menu with items to copy or open their URLs or download them directly.",
|
||||
) {
|
||||
compatibleWith("com.strava")
|
||||
|
||||
dependsOn(
|
||||
resourceMappingPatch,
|
||||
sharedExtensionPatch,
|
||||
)
|
||||
|
||||
apply {
|
||||
val fragmentClass = firstClassDef { endsWith("/FullscreenMediaFragment;") }
|
||||
|
||||
// region Extend menu of `FullscreenMediaFragment` with actions.
|
||||
|
||||
fragmentClass.getCreateAndShowFragmentMethod().apply {
|
||||
val setTrueIndex = instructions.indexOfFirst { instruction ->
|
||||
instruction.opcode == Opcode.IPUT_BOOLEAN
|
||||
}
|
||||
val actionRegistrarRegister = getInstruction<BuilderInstruction22c>(setTrueIndex).registerB
|
||||
val actionRegister = instructions.first { instruction ->
|
||||
instruction.getReference<TypeReference>()?.type == ACTION_CLASS_DESCRIPTOR
|
||||
}.writeRegister!!
|
||||
|
||||
fun addMenuItem(actionId: String, string: String, color: String, drawable: String) = addInstructions(
|
||||
setTrueIndex + 1,
|
||||
"""
|
||||
new-instance v$actionRegister, $ACTION_CLASS_DESCRIPTOR
|
||||
sget v${actionRegister + 1}, $MEDIA_DOWNLOAD_CLASS_DESCRIPTOR->$actionId:I
|
||||
const v${actionRegister + 2}, 0x0
|
||||
const v${actionRegister + 3}, ${ResourceType.STRING[string]}
|
||||
const v${actionRegister + 4}, ${ResourceType.COLOR[color]}
|
||||
const v${actionRegister + 5}, ${ResourceType.DRAWABLE[drawable]}
|
||||
move/from16 v${actionRegister + 6}, v${actionRegister + 4}
|
||||
invoke-direct/range { v$actionRegister .. v${actionRegister + 7} }, $ACTION_CLASS_DESCRIPTOR-><init>(ILjava/lang/String;IIIILjava/io/Serializable;)V
|
||||
invoke-virtual { v$actionRegistrarRegister, v$actionRegister }, Lcom/strava/bottomsheet/a;->a(Lcom/strava/bottomsheet/BottomSheetItem;)V
|
||||
""",
|
||||
)
|
||||
|
||||
addMenuItem("ACTION_COPY_LINK", "copy_link", "core_o3", "actions_link_normal_xsmall")
|
||||
addMenuItem(
|
||||
"ACTION_OPEN_LINK",
|
||||
"fallback_menu_item_open_in_browser",
|
||||
"core_o3",
|
||||
"actions_link_external_normal_xsmall",
|
||||
)
|
||||
addMenuItem("ACTION_DOWNLOAD", "download", "core_o3", "actions_download_normal_xsmall")
|
||||
|
||||
// Move media to last parameter of `Action` constructor.
|
||||
val getMediaInstruction = instructions.first { instruction ->
|
||||
instruction.getReference<FieldReference>()?.type == MEDIA_CLASS_DESCRIPTOR
|
||||
}
|
||||
addInstruction(
|
||||
getMediaInstruction.location.index + 1,
|
||||
"move-object/from16 v${actionRegister + 7}, v${getMediaInstruction.writeRegister}",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Handle new actions.
|
||||
|
||||
val actionClass = firstClassDef {
|
||||
type == ACTION_CLASS_DESCRIPTOR
|
||||
}
|
||||
val actionSerializableField = actionClass.instanceFields.first { field ->
|
||||
field.type == "Ljava/io/Serializable;"
|
||||
}
|
||||
|
||||
// Handle "copy link" & "open link" & "download" actions.
|
||||
fragmentClass.getHandleMediaActionMethod().apply {
|
||||
// Call handler if action ID < 0 (= custom).
|
||||
val moveInstruction = instructions.first { instruction ->
|
||||
instruction.opcode == Opcode.MOVE_RESULT
|
||||
}
|
||||
val indexAfterMoveInstruction = moveInstruction.location.index + 1
|
||||
val actionIdRegister = moveInstruction.writeRegister
|
||||
addInstructionsWithLabels(
|
||||
indexAfterMoveInstruction,
|
||||
"""
|
||||
if-gez v$actionIdRegister, :move
|
||||
check-cast p2, $ACTION_CLASS_DESCRIPTOR
|
||||
iget-object v0, p2, $actionSerializableField
|
||||
check-cast v0, $MEDIA_CLASS_DESCRIPTOR
|
||||
invoke-static { v$actionIdRegister, v0 }, $MEDIA_DOWNLOAD_CLASS_DESCRIPTOR->handleAction(I$MEDIA_CLASS_DESCRIPTOR)Z
|
||||
move-result v0
|
||||
return v0
|
||||
""",
|
||||
ExternalLabel("move", instructions[indexAfterMoveInstruction]),
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package app.revanced.patches.strava.media.download
|
||||
|
||||
import app.revanced.patcher.accessFlags
|
||||
import app.revanced.patcher.firstMutableMethodDeclaratively
|
||||
import app.revanced.patcher.parameterTypes
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.returnType
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
internal fun ClassDef.getCreateAndShowFragmentMethod() = firstMutableMethodDeclaratively("mediaType") {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returnType("V")
|
||||
parameterTypes("L")
|
||||
}
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
internal fun ClassDef.getHandleMediaActionMethod() = firstMutableMethodDeclaratively {
|
||||
parameterTypes("Landroid/view/View;", "Lcom/strava/bottomsheet/BottomSheetItem;")
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package app.revanced.patches.strava.mediaupload
|
||||
package app.revanced.patches.strava.media.upload
|
||||
|
||||
import app.revanced.patcher.definingClass
|
||||
import app.revanced.patcher.firstMutableMethodDeclaratively
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package app.revanced.patches.strava.mediaupload
|
||||
package app.revanced.patches.strava.media.upload
|
||||
|
||||
import app.revanced.patcher.firstClassDef
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package app.revanced.patches.strava.misc.extension
|
||||
|
||||
import app.revanced.patcher.definingClass
|
||||
import app.revanced.patcher.name
|
||||
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||
|
||||
internal val applicationOnCreateHook = extensionHook {
|
||||
name("onCreate")
|
||||
definingClass { endsWith("/StravaApplication;") }
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package app.revanced.patches.strava.misc.extension
|
||||
|
||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||
|
||||
val sharedExtensionPatch = sharedExtensionPatch("strava", applicationOnCreateHook)
|
||||
|
|
@ -1,63 +1,24 @@
|
|||
package app.revanced.patches.strava.upselling
|
||||
|
||||
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.removeInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import app.revanced.patches.strava.distractions.hideDistractionsPatch
|
||||
|
||||
@Suppress("unused")
|
||||
val disableSubscriptionSuggestionsPatch = bytecodePatch("Disable subscription suggestions") {
|
||||
@Deprecated("Superseded by \"Hide distractions\" patch", ReplaceWith("hideDistractionsPatch"))
|
||||
val disableSubscriptionSuggestionsPatch = bytecodePatch(
|
||||
name = "Disable subscription suggestions",
|
||||
) {
|
||||
compatibleWith("com.strava")
|
||||
|
||||
apply {
|
||||
val helperMethodName = "getModulesIfNotUpselling"
|
||||
val pageSuffix = "_upsell"
|
||||
val label = "original"
|
||||
|
||||
val className = getModulesMethodMatch.classDef.type
|
||||
val immutableMethod = getModulesMethodMatch.method
|
||||
val returnType = immutableMethod.returnType
|
||||
|
||||
getModulesMethodMatch.classDef.methods.add(
|
||||
ImmutableMethod(
|
||||
className,
|
||||
helperMethodName,
|
||||
emptyList(),
|
||||
returnType,
|
||||
AccessFlags.PRIVATE.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(3),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
iget-object v0, p0, $className->page:Ljava/lang/String;
|
||||
const-string v1, "$pageSuffix"
|
||||
invoke-virtual {v0, v1}, Ljava/lang/String;->endsWith(Ljava/lang/String;)Z
|
||||
move-result v0
|
||||
if-eqz v0, :$label
|
||||
invoke-static {}, Ljava/util/Collections;->emptyList()Ljava/util/List;
|
||||
move-result-object v0
|
||||
return-object v0
|
||||
:$label
|
||||
iget-object v0, p0, $className->modules:Ljava/util/List;
|
||||
return-object v0
|
||||
""",
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val getModulesIndex = getModulesMethodMatch[0]
|
||||
immutableMethod.removeInstruction(getModulesIndex)
|
||||
immutableMethod.addInstructions(
|
||||
getModulesIndex,
|
||||
"""
|
||||
invoke-direct {p0}, $className->$helperMethodName()$returnType
|
||||
move-result-object v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
dependsOn(
|
||||
hideDistractionsPatch.apply {
|
||||
options["Upselling"] = true
|
||||
options["Promotions"] = false
|
||||
options["Who to Follow"] = false
|
||||
options["Suggested Challenges"] = false
|
||||
options["Join Challenge"] = false
|
||||
options["Joined a club"] = false
|
||||
options["Your activity from X years ago"] = false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ package app.revanced.patches.youtube.layout.hide.general
|
|||
import app.revanced.patcher.Match
|
||||
import app.revanced.patcher.extensions.*
|
||||
import app.revanced.patcher.immutableClassDef
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.layout.hide.general.hideLayoutComponentsPatch
|
||||
import app.revanced.patches.shared.misc.mapping.ResourceType
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.*
|
||||
|
|
@ -62,381 +61,363 @@ private const val DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME =
|
|||
private const val COMMENTS_FILTER_CLASS_NAME =
|
||||
"Lapp/revanced/extension/youtube/patches/components/CommentsFilter;"
|
||||
private const val CUSTOM_FILTER_CLASS_NAME =
|
||||
"Lapp/revanced/extension/youtube/patches/components/CustomFilter;"
|
||||
"Lapp/revanced/extension/shared/patches/components/CustomFilter;"
|
||||
private const val KEYWORD_FILTER_CLASS_NAME =
|
||||
"Lapp/revanced/extension/youtube/patches/components/KeywordContentFilter;"
|
||||
|
||||
@Suppress("unused")
|
||||
val hideLayoutComponentsPatch = bytecodePatch(
|
||||
name = "Hide layout components",
|
||||
description = "Adds options to hide general layout components.",
|
||||
|
||||
) {
|
||||
dependsOn(
|
||||
lithoFilterPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
|
||||
lithoFilterPatch = lithoFilterPatch,
|
||||
getAddLithoFilter = { addLithoFilter },
|
||||
settingsPatch = settingsPatch,
|
||||
additionalDependencies = setOf(
|
||||
hideLayoutComponentsResourcePatch,
|
||||
navigationBarHookPatch,
|
||||
versionCheckPatch,
|
||||
resourceMappingPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
),
|
||||
filterClasses = setOf(
|
||||
LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR,
|
||||
DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME,
|
||||
COMMENTS_FILTER_CLASS_NAME,
|
||||
KEYWORD_FILTER_CLASS_NAME,
|
||||
CUSTOM_FILTER_CLASS_NAME,
|
||||
),
|
||||
compatibleWithPackages = arrayOf(
|
||||
"com.google.android.youtube" to setOf(
|
||||
"19.43.41",
|
||||
"20.14.43",
|
||||
"20.21.37",
|
||||
"20.31.40",
|
||||
),
|
||||
),
|
||||
) {
|
||||
addResources("youtube", "layout.hide.general.hideLayoutComponentsPatch")
|
||||
|
||||
PreferenceScreen.PLAYER.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_hide_description_components_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_ai_generated_video_summary_section"),
|
||||
SwitchPreference("revanced_hide_ask_section"),
|
||||
SwitchPreference("revanced_hide_attributes_section"),
|
||||
SwitchPreference("revanced_hide_chapters_section"),
|
||||
SwitchPreference("revanced_hide_featured_links_section"),
|
||||
SwitchPreference("revanced_hide_featured_videos_section"),
|
||||
SwitchPreference("revanced_hide_info_cards_section"),
|
||||
SwitchPreference("revanced_hide_how_this_was_made_section"),
|
||||
SwitchPreference("revanced_hide_hype_points"),
|
||||
SwitchPreference("revanced_hide_key_concepts_section"),
|
||||
SwitchPreference("revanced_hide_podcast_section"),
|
||||
SwitchPreference("revanced_hide_subscribe_button"),
|
||||
SwitchPreference("revanced_hide_transcript_section"),
|
||||
),
|
||||
),
|
||||
PreferenceScreenPreference(
|
||||
"revanced_comments_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_comments_ai_chat_summary"),
|
||||
SwitchPreference("revanced_hide_comments_ai_summary"),
|
||||
SwitchPreference("revanced_hide_comments_channel_guidelines"),
|
||||
SwitchPreference("revanced_hide_comments_by_members_header"),
|
||||
SwitchPreference("revanced_hide_comments_section"),
|
||||
SwitchPreference("revanced_hide_comments_community_guidelines"),
|
||||
SwitchPreference("revanced_hide_comments_create_a_short_button"),
|
||||
SwitchPreference("revanced_hide_comments_emoji_and_timestamp_buttons"),
|
||||
SwitchPreference("revanced_hide_comments_preview_comment"),
|
||||
SwitchPreference("revanced_hide_comments_thanks_button"),
|
||||
),
|
||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||
),
|
||||
SwitchPreference("revanced_hide_channel_bar"),
|
||||
SwitchPreference("revanced_hide_channel_watermark"),
|
||||
SwitchPreference("revanced_hide_crowdfunding_box"),
|
||||
SwitchPreference("revanced_hide_emergency_box"),
|
||||
SwitchPreference("revanced_hide_info_panels"),
|
||||
SwitchPreference("revanced_hide_join_membership_button"),
|
||||
SwitchPreference("revanced_hide_medical_panels"),
|
||||
SwitchPreference("revanced_hide_quick_actions"),
|
||||
SwitchPreference("revanced_hide_related_videos"),
|
||||
SwitchPreference("revanced_hide_subscribers_community_guidelines"),
|
||||
SwitchPreference("revanced_hide_timed_reactions"),
|
||||
)
|
||||
|
||||
apply {
|
||||
addResources("youtube", "layout.hide.general.hideLayoutComponentsPatch")
|
||||
|
||||
PreferenceScreen.PLAYER.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_hide_description_components_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_ai_generated_video_summary_section"),
|
||||
SwitchPreference("revanced_hide_ask_section"),
|
||||
SwitchPreference("revanced_hide_attributes_section"),
|
||||
SwitchPreference("revanced_hide_chapters_section"),
|
||||
SwitchPreference("revanced_hide_featured_links_section"),
|
||||
SwitchPreference("revanced_hide_featured_videos_section"),
|
||||
SwitchPreference("revanced_hide_info_cards_section"),
|
||||
SwitchPreference("revanced_hide_how_this_was_made_section"),
|
||||
SwitchPreference("revanced_hide_hype_points"),
|
||||
SwitchPreference("revanced_hide_key_concepts_section"),
|
||||
SwitchPreference("revanced_hide_podcast_section"),
|
||||
SwitchPreference("revanced_hide_subscribe_button"),
|
||||
SwitchPreference("revanced_hide_transcript_section"),
|
||||
PreferenceScreen.FEED.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_hide_keyword_content_screen",
|
||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_keyword_content_home"),
|
||||
SwitchPreference("revanced_hide_keyword_content_subscriptions"),
|
||||
SwitchPreference("revanced_hide_keyword_content_search"),
|
||||
TextPreference("revanced_hide_keyword_content_phrases", inputType = InputType.TEXT_MULTI_LINE),
|
||||
NonInteractivePreference(
|
||||
key = "revanced_hide_keyword_content_about",
|
||||
tag = "app.revanced.extension.shared.settings.preference.BulletPointPreference",
|
||||
),
|
||||
NonInteractivePreference(
|
||||
key = "revanced_hide_keyword_content_about_whole_words",
|
||||
tag = "app.revanced.extension.youtube.settings.preference.HtmlPreference",
|
||||
),
|
||||
),
|
||||
PreferenceScreenPreference(
|
||||
"revanced_comments_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_comments_ai_chat_summary"),
|
||||
SwitchPreference("revanced_hide_comments_ai_summary"),
|
||||
SwitchPreference("revanced_hide_comments_channel_guidelines"),
|
||||
SwitchPreference("revanced_hide_comments_by_members_header"),
|
||||
SwitchPreference("revanced_hide_comments_section"),
|
||||
SwitchPreference("revanced_hide_comments_community_guidelines"),
|
||||
SwitchPreference("revanced_hide_comments_create_a_short_button"),
|
||||
SwitchPreference("revanced_hide_comments_emoji_and_timestamp_buttons"),
|
||||
SwitchPreference("revanced_hide_comments_preview_comment"),
|
||||
SwitchPreference("revanced_hide_comments_thanks_button"),
|
||||
),
|
||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||
),
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_hide_filter_bar_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_feed"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_related_videos"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_search"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_history"),
|
||||
),
|
||||
SwitchPreference("revanced_hide_channel_bar"),
|
||||
SwitchPreference("revanced_hide_channel_watermark"),
|
||||
SwitchPreference("revanced_hide_crowdfunding_box"),
|
||||
SwitchPreference("revanced_hide_emergency_box"),
|
||||
SwitchPreference("revanced_hide_info_panels"),
|
||||
SwitchPreference("revanced_hide_join_membership_button"),
|
||||
SwitchPreference("revanced_hide_medical_panels"),
|
||||
SwitchPreference("revanced_hide_quick_actions"),
|
||||
SwitchPreference("revanced_hide_related_videos"),
|
||||
SwitchPreference("revanced_hide_subscribers_community_guidelines"),
|
||||
SwitchPreference("revanced_hide_timed_reactions"),
|
||||
)
|
||||
|
||||
PreferenceScreen.FEED.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_hide_keyword_content_screen",
|
||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_keyword_content_home"),
|
||||
SwitchPreference("revanced_hide_keyword_content_subscriptions"),
|
||||
SwitchPreference("revanced_hide_keyword_content_search"),
|
||||
TextPreference("revanced_hide_keyword_content_phrases", inputType = InputType.TEXT_MULTI_LINE),
|
||||
NonInteractivePreference(
|
||||
key = "revanced_hide_keyword_content_about",
|
||||
tag = "app.revanced.extension.shared.settings.preference.BulletPointPreference",
|
||||
),
|
||||
NonInteractivePreference(
|
||||
key = "revanced_hide_keyword_content_about_whole_words",
|
||||
tag = "app.revanced.extension.youtube.settings.preference.HtmlPreference",
|
||||
),
|
||||
),
|
||||
),
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_channel_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_community_button"),
|
||||
SwitchPreference("revanced_hide_for_you_shelf"),
|
||||
SwitchPreference("revanced_hide_join_button"),
|
||||
SwitchPreference("revanced_hide_links_preview"),
|
||||
SwitchPreference("revanced_hide_members_shelf"),
|
||||
SwitchPreference("revanced_hide_store_button"),
|
||||
SwitchPreference("revanced_hide_subscribe_button_in_channel_page"),
|
||||
),
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_hide_filter_bar_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_feed"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_related_videos"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_search"),
|
||||
SwitchPreference("revanced_hide_filter_bar_feed_in_history"),
|
||||
),
|
||||
),
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_channel_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_community_button"),
|
||||
SwitchPreference("revanced_hide_for_you_shelf"),
|
||||
SwitchPreference("revanced_hide_join_button"),
|
||||
SwitchPreference("revanced_hide_links_preview"),
|
||||
SwitchPreference("revanced_hide_members_shelf"),
|
||||
SwitchPreference("revanced_hide_store_button"),
|
||||
SwitchPreference("revanced_hide_subscribe_button_in_channel_page"),
|
||||
),
|
||||
),
|
||||
SwitchPreference("revanced_hide_album_cards"),
|
||||
SwitchPreference("revanced_hide_artist_cards"),
|
||||
SwitchPreference("revanced_hide_chips_shelf"),
|
||||
SwitchPreference("revanced_hide_community_posts"),
|
||||
SwitchPreference("revanced_hide_compact_banner"),
|
||||
SwitchPreference("revanced_hide_expandable_card"),
|
||||
SwitchPreference("revanced_hide_floating_microphone_button"),
|
||||
SwitchPreference(
|
||||
key = "revanced_hide_horizontal_shelves",
|
||||
tag = "app.revanced.extension.shared.settings.preference.BulletPointSwitchPreference",
|
||||
),
|
||||
SwitchPreference("revanced_hide_image_shelf"),
|
||||
SwitchPreference("revanced_hide_latest_posts"),
|
||||
SwitchPreference("revanced_hide_mix_playlists"),
|
||||
SwitchPreference("revanced_hide_movies_section"),
|
||||
SwitchPreference("revanced_hide_notify_me_button"),
|
||||
SwitchPreference("revanced_hide_playables"),
|
||||
SwitchPreference("revanced_hide_show_more_button"),
|
||||
SwitchPreference("revanced_hide_surveys"),
|
||||
SwitchPreference("revanced_hide_ticket_shelf"),
|
||||
SwitchPreference("revanced_hide_upload_time"),
|
||||
SwitchPreference("revanced_hide_video_recommendation_labels"),
|
||||
SwitchPreference("revanced_hide_view_count"),
|
||||
SwitchPreference("revanced_hide_visual_spacer"),
|
||||
SwitchPreference("revanced_hide_doodles"),
|
||||
)
|
||||
),
|
||||
SwitchPreference("revanced_hide_album_cards"),
|
||||
SwitchPreference("revanced_hide_artist_cards"),
|
||||
SwitchPreference("revanced_hide_chips_shelf"),
|
||||
SwitchPreference("revanced_hide_community_posts"),
|
||||
SwitchPreference("revanced_hide_compact_banner"),
|
||||
SwitchPreference("revanced_hide_expandable_card"),
|
||||
SwitchPreference("revanced_hide_floating_microphone_button"),
|
||||
SwitchPreference(
|
||||
key = "revanced_hide_horizontal_shelves",
|
||||
tag = "app.revanced.extension.shared.settings.preference.BulletPointSwitchPreference",
|
||||
),
|
||||
SwitchPreference("revanced_hide_image_shelf"),
|
||||
SwitchPreference("revanced_hide_latest_posts"),
|
||||
SwitchPreference("revanced_hide_mix_playlists"),
|
||||
SwitchPreference("revanced_hide_movies_section"),
|
||||
SwitchPreference("revanced_hide_notify_me_button"),
|
||||
SwitchPreference("revanced_hide_playables"),
|
||||
SwitchPreference("revanced_hide_show_more_button"),
|
||||
SwitchPreference("revanced_hide_surveys"),
|
||||
SwitchPreference("revanced_hide_ticket_shelf"),
|
||||
SwitchPreference("revanced_hide_upload_time"),
|
||||
SwitchPreference("revanced_hide_video_recommendation_labels"),
|
||||
SwitchPreference("revanced_hide_view_count"),
|
||||
SwitchPreference("revanced_hide_visual_spacer"),
|
||||
SwitchPreference("revanced_hide_doodles"),
|
||||
)
|
||||
|
||||
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_custom_filter_screen",
|
||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_custom_filter"),
|
||||
TextPreference("revanced_custom_filter_strings", inputType = InputType.TEXT_MULTI_LINE),
|
||||
),
|
||||
),
|
||||
)
|
||||
// region Mix playlists
|
||||
|
||||
addLithoFilter(LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR)
|
||||
addLithoFilter(DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME)
|
||||
addLithoFilter(COMMENTS_FILTER_CLASS_NAME)
|
||||
addLithoFilter(KEYWORD_FILTER_CLASS_NAME)
|
||||
addLithoFilter(CUSTOM_FILTER_CLASS_NAME)
|
||||
parseElementFromBufferMethodMatch.let {
|
||||
it.method.apply {
|
||||
val startIndex = it[0]
|
||||
val insertIndex = startIndex + 1
|
||||
|
||||
// region Mix playlists
|
||||
val byteArrayParameter = "p3"
|
||||
val conversionContextRegister = getInstruction<TwoRegisterInstruction>(startIndex).registerA
|
||||
val returnEmptyComponentInstruction = instructions.last { it.opcode == Opcode.INVOKE_STATIC }
|
||||
val returnEmptyComponentRegister =
|
||||
(returnEmptyComponentInstruction as FiveRegisterInstruction).registerC
|
||||
val freeRegister =
|
||||
findFreeRegister(insertIndex, conversionContextRegister, returnEmptyComponentRegister)
|
||||
|
||||
parseElementFromBufferMethodMatch.let {
|
||||
it.method.apply {
|
||||
val startIndex = it[0]
|
||||
val insertIndex = startIndex + 1
|
||||
|
||||
val byteArrayParameter = "p3"
|
||||
val conversionContextRegister = getInstruction<TwoRegisterInstruction>(startIndex).registerA
|
||||
val returnEmptyComponentInstruction = instructions.last { it.opcode == Opcode.INVOKE_STATIC }
|
||||
val returnEmptyComponentRegister =
|
||||
(returnEmptyComponentInstruction as FiveRegisterInstruction).registerC
|
||||
val freeRegister =
|
||||
findFreeRegister(insertIndex, conversionContextRegister, returnEmptyComponentRegister)
|
||||
|
||||
addInstructionsWithLabels(
|
||||
insertIndex,
|
||||
"""
|
||||
invoke-static { v$conversionContextRegister, $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :show
|
||||
move-object v$returnEmptyComponentRegister, p1 # Required for 19.47
|
||||
goto :return_empty_component
|
||||
:show
|
||||
nop
|
||||
""",
|
||||
ExternalLabel("return_empty_component", returnEmptyComponentInstruction),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Watermark (legacy code for old versions of YouTube)
|
||||
|
||||
playerOverlayMethod.immutableClassDef.getShowWatermarkMethod().apply {
|
||||
val index = implementation!!.instructions.size - 5
|
||||
|
||||
removeInstruction(index)
|
||||
addInstructions(
|
||||
index,
|
||||
addInstructionsWithLabels(
|
||||
insertIndex,
|
||||
"""
|
||||
invoke-static { v$conversionContextRegister, $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :show
|
||||
move-object v$returnEmptyComponentRegister, p1 # Required for 19.47
|
||||
goto :return_empty_component
|
||||
:show
|
||||
nop
|
||||
""",
|
||||
ExternalLabel("return_empty_component", returnEmptyComponentInstruction),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Watermark (legacy code for old versions of YouTube)
|
||||
|
||||
playerOverlayMethod.immutableClassDef.getShowWatermarkMethod().apply {
|
||||
val index = implementation!!.instructions.size - 5
|
||||
|
||||
removeInstruction(index)
|
||||
addInstructions(
|
||||
index,
|
||||
"""
|
||||
invoke-static {}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->showWatermark()Z
|
||||
move-result p2
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Show more button
|
||||
|
||||
(if (is_20_26_or_greater) hideShowMoreButtonMethodMatch else hideShowMoreLegacyButtonMethodMatch).let {
|
||||
it.method.apply {
|
||||
val moveRegisterIndex = it[-1]
|
||||
val viewRegister = getInstruction<OneRegisterInstruction>(moveRegisterIndex).registerA
|
||||
|
||||
val insertIndex = moveRegisterIndex + 1
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"invoke-static { v$viewRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
||||
"->hideShowMoreButton(Landroid/view/View;)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
// endregion
|
||||
|
||||
// region Show more button
|
||||
// region crowdfunding box
|
||||
crowdfundingBoxMethodMatch.let {
|
||||
it.method.apply {
|
||||
val insertIndex = it[-1]
|
||||
val objectRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
(if (is_20_26_or_greater) hideShowMoreButtonMethodMatch else hideShowMoreLegacyButtonMethodMatch).let {
|
||||
it.method.apply {
|
||||
val moveRegisterIndex = it[-1]
|
||||
val viewRegister = getInstruction<OneRegisterInstruction>(moveRegisterIndex).registerA
|
||||
|
||||
val insertIndex = moveRegisterIndex + 1
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"invoke-static { v$viewRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
||||
"->hideShowMoreButton(Landroid/view/View;)V",
|
||||
)
|
||||
}
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"invoke-static {v$objectRegister}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
||||
"->hideCrowdfundingBox(Landroid/view/View;)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
// endregion
|
||||
|
||||
// region crowdfunding box
|
||||
crowdfundingBoxMethodMatch.let {
|
||||
it.method.apply {
|
||||
val insertIndex = it[-1]
|
||||
val objectRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
// region hide album cards
|
||||
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"invoke-static {v$objectRegister}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
||||
"->hideCrowdfundingBox(Landroid/view/View;)V",
|
||||
)
|
||||
}
|
||||
albumCardsMethodMatch.let {
|
||||
it.method.apply {
|
||||
val checkCastAnchorIndex = it[-1]
|
||||
val insertIndex = checkCastAnchorIndex + 1
|
||||
val register = getInstruction<OneRegisterInstruction>(checkCastAnchorIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
||||
"->hideAlbumCard(Landroid/view/View;)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
// endregion
|
||||
|
||||
// region hide album cards
|
||||
// region hide floating microphone
|
||||
|
||||
albumCardsMethodMatch.let {
|
||||
it.method.apply {
|
||||
val checkCastAnchorIndex = it[-1]
|
||||
val insertIndex = checkCastAnchorIndex + 1
|
||||
val register = getInstruction<OneRegisterInstruction>(checkCastAnchorIndex).registerA
|
||||
showFloatingMicrophoneButtonMethodMatch.let {
|
||||
it.method.apply {
|
||||
val index = it[-1]
|
||||
val register = getInstruction<TwoRegisterInstruction>(index).registerA
|
||||
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
||||
"->hideAlbumCard(Landroid/view/View;)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region hide floating microphone
|
||||
|
||||
showFloatingMicrophoneButtonMethodMatch.let {
|
||||
it.method.apply {
|
||||
val index = it[-1]
|
||||
val register = getInstruction<TwoRegisterInstruction>(index).registerA
|
||||
|
||||
addInstructions(
|
||||
index + 1,
|
||||
"""
|
||||
addInstructions(
|
||||
index + 1,
|
||||
"""
|
||||
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideFloatingMicrophoneButton(Z)Z
|
||||
move-result v$register
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region 'Yoodles'
|
||||
|
||||
yoodlesImageViewMethod.apply {
|
||||
findInstructionIndicesReversedOrThrow {
|
||||
getReference<MethodReference>()?.name == "setImageDrawable"
|
||||
}.forEach { insertIndex ->
|
||||
val drawableRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
|
||||
val imageViewRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerC
|
||||
|
||||
replaceInstruction(
|
||||
insertIndex,
|
||||
"invoke-static { v$imageViewRegister, v$drawableRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->" +
|
||||
"setDoodleDrawable(Landroid/widget/ImageView;Landroid/graphics/drawable/Drawable;)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region hide view count
|
||||
|
||||
hideViewCountMethodMatch.method.apply {
|
||||
val startIndex = hideViewCountMethodMatch[0]
|
||||
var returnStringRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
|
||||
|
||||
// Find the instruction where the text dimension is retrieved.
|
||||
val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_STATIC &&
|
||||
reference?.definingClass == "Landroid/util/TypedValue;" &&
|
||||
reference.returnType == "F" &&
|
||||
reference.name == "applyDimension" &&
|
||||
reference.parameterTypes == listOf("I", "F", "Landroid/util/DisplayMetrics;")
|
||||
}
|
||||
|
||||
// A float value is passed which is used to determine subtitle text size.
|
||||
val floatDimensionRegister = getInstruction<OneRegisterInstruction>(
|
||||
applyDimensionIndex + 1,
|
||||
).registerA
|
||||
|
||||
addInstructions(
|
||||
applyDimensionIndex - 1,
|
||||
"""
|
||||
invoke-static { v$returnStringRegister, v$floatDimensionRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->modifyFeedSubtitleSpan(Landroid/text/SpannableString;F)Landroid/text/SpannableString;
|
||||
move-result-object v$returnStringRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
// endregion
|
||||
|
||||
// region hide filter bar
|
||||
// region 'Yoodles'
|
||||
|
||||
/**
|
||||
* Patch a [Method] with a given [instructions].
|
||||
*
|
||||
* @param RegisterInstruction The type of instruction to get the register from.
|
||||
* @param insertIndexOffset The offset to add to the end index of the [Match.indices].
|
||||
* @param hookRegisterOffset The offset to add to the register of the hook.
|
||||
* @param instructions The instructions to add with the register as a parameter.
|
||||
*/
|
||||
fun <RegisterInstruction : OneRegisterInstruction> Match.patch(
|
||||
insertIndexOffset: Int = 0,
|
||||
hookRegisterOffset: Int = 0,
|
||||
instructions: (Int) -> String,
|
||||
) = method.apply {
|
||||
val endIndex = get(-1)
|
||||
val insertIndex = endIndex + insertIndexOffset
|
||||
val register = getInstruction<RegisterInstruction>(endIndex + hookRegisterOffset).registerA
|
||||
yoodlesImageViewMethod.apply {
|
||||
findInstructionIndicesReversedOrThrow {
|
||||
getReference<MethodReference>()?.name == "setImageDrawable"
|
||||
}.forEach { insertIndex ->
|
||||
val drawableRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
|
||||
val imageViewRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerC
|
||||
|
||||
addInstructions(insertIndex, instructions(register))
|
||||
}
|
||||
|
||||
filterBarHeightMethodMatch.patch<TwoRegisterInstruction> { register ->
|
||||
"""
|
||||
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInFeed(I)I
|
||||
move-result v$register
|
||||
"""
|
||||
}
|
||||
|
||||
searchResultsChipBarMethodMatch.patch<OneRegisterInstruction>(-1, -2) { register ->
|
||||
"""
|
||||
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInSearch(I)I
|
||||
move-result v$register
|
||||
"""
|
||||
}
|
||||
|
||||
relatedChipCloudMethodMatch.patch<OneRegisterInstruction>(1) { register ->
|
||||
"invoke-static { v$register }, " +
|
||||
"$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V"
|
||||
replaceInstruction(
|
||||
insertIndex,
|
||||
"invoke-static { v$imageViewRegister, v$drawableRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->" +
|
||||
"setDoodleDrawable(Landroid/widget/ImageView;Landroid/graphics/drawable/Drawable;)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region hide view count
|
||||
|
||||
hideViewCountMethodMatch.method.apply {
|
||||
val startIndex = hideViewCountMethodMatch[0]
|
||||
var returnStringRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
|
||||
|
||||
// Find the instruction where the text dimension is retrieved.
|
||||
val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_STATIC &&
|
||||
reference?.definingClass == "Landroid/util/TypedValue;" &&
|
||||
reference.returnType == "F" &&
|
||||
reference.name == "applyDimension" &&
|
||||
reference.parameterTypes == listOf("I", "F", "Landroid/util/DisplayMetrics;")
|
||||
}
|
||||
|
||||
// A float value is passed which is used to determine subtitle text size.
|
||||
val floatDimensionRegister = getInstruction<OneRegisterInstruction>(
|
||||
applyDimensionIndex + 1,
|
||||
).registerA
|
||||
|
||||
addInstructions(
|
||||
applyDimensionIndex - 1,
|
||||
"""
|
||||
invoke-static { v$returnStringRegister, v$floatDimensionRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->modifyFeedSubtitleSpan(Landroid/text/SpannableString;F)Landroid/text/SpannableString;
|
||||
move-result-object v$returnStringRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region hide filter bar
|
||||
|
||||
/**
|
||||
* Patch a [Method] with a given [instructions].
|
||||
*
|
||||
* @param RegisterInstruction The type of instruction to get the register from.
|
||||
* @param insertIndexOffset The offset to add to the end index of the [Match.indices].
|
||||
* @param hookRegisterOffset The offset to add to the register of the hook.
|
||||
* @param instructions The instructions to add with the register as a parameter.
|
||||
*/
|
||||
fun <RegisterInstruction : OneRegisterInstruction> Match.patch(
|
||||
insertIndexOffset: Int = 0,
|
||||
hookRegisterOffset: Int = 0,
|
||||
instructions: (Int) -> String,
|
||||
) = method.apply {
|
||||
val endIndex = get(-1)
|
||||
val insertIndex = endIndex + insertIndexOffset
|
||||
val register = getInstruction<RegisterInstruction>(endIndex + hookRegisterOffset).registerA
|
||||
|
||||
addInstructions(insertIndex, instructions(register))
|
||||
}
|
||||
|
||||
filterBarHeightMethodMatch.patch<TwoRegisterInstruction> { register ->
|
||||
"""
|
||||
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInFeed(I)I
|
||||
move-result v$register
|
||||
"""
|
||||
}
|
||||
|
||||
searchResultsChipBarMethodMatch.patch<OneRegisterInstruction>(-1, -2) { register ->
|
||||
"""
|
||||
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInSearch(I)I
|
||||
move-result v$register
|
||||
"""
|
||||
}
|
||||
|
||||
relatedChipCloudMethodMatch.patch<OneRegisterInstruction>(1) { register ->
|
||||
"invoke-static { v$register }, " +
|
||||
"$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package app.revanced.patches.youtube.misc.audiofocus
|
||||
|
||||
import app.revanced.patcher.gettingFirstMutableMethod
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
|
||||
internal val BytecodePatchContext.audioFocusChangeListenerMethod by gettingFirstMutableMethod(
|
||||
"AudioFocus DUCK",
|
||||
"AudioFocus loss; Will lower volume",
|
||||
)
|
||||
|
||||
internal val BytecodePatchContext.audioFocusRequestBuilderMethod by gettingFirstMutableMethod(
|
||||
"Can't build an AudioFocusRequestCompat instance without a listener",
|
||||
)
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package app.revanced.patches.youtube.misc.audiofocus
|
||||
|
||||
import app.revanced.patcher.extensions.ExternalLabel
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/PauseOnAudioInterruptPatch;"
|
||||
|
||||
val pauseOnAudioInterruptPatch = bytecodePatch(
|
||||
name = "Pause on audio interrupt",
|
||||
description = "Adds an option to pause playback instead of lowering volume when other audio plays.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"20.14.43",
|
||||
),
|
||||
)
|
||||
|
||||
apply {
|
||||
addResources("youtube", "misc.audiofocus.pauseOnAudioInterruptPatch")
|
||||
|
||||
PreferenceScreen.MISC.addPreferences(
|
||||
SwitchPreference("revanced_pause_on_audio_interrupt"),
|
||||
)
|
||||
|
||||
// Hook the builder method that creates AudioFocusRequest.
|
||||
// At the start, set the willPauseWhenDucked field (b) to true if setting is enabled.
|
||||
val builderClass = audioFocusRequestBuilderMethod.definingClass
|
||||
|
||||
audioFocusRequestBuilderMethod.addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->shouldPauseOnAudioInterrupt()Z
|
||||
move-result v0
|
||||
if-eqz v0, :skip_override
|
||||
const/4 v0, 0x1
|
||||
iput-boolean v0, p0, $builderClass->b:Z
|
||||
""",
|
||||
ExternalLabel("skip_override", audioFocusRequestBuilderMethod.getInstruction(0)),
|
||||
)
|
||||
|
||||
// Also hook the audio focus change listener as a backup.
|
||||
audioFocusChangeListenerMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->overrideAudioFocusChange(I)I
|
||||
move-result p1
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +1,22 @@
|
|||
package app.revanced.patches.youtube.misc.debugging
|
||||
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch;
|
||||
import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
|
||||
@Suppress("unused")
|
||||
val enableDebuggingPatch = enableDebuggingPatch(
|
||||
block = {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
sharedExtensionPatch = sharedExtensionPatch,
|
||||
settingsPatch = settingsPatch,
|
||||
compatibleWithPackages = arrayOf(
|
||||
"com.google.android.youtube" to setOf(
|
||||
"19.43.41",
|
||||
"20.14.43",
|
||||
"20.21.37",
|
||||
"20.31.40",
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"19.43.41",
|
||||
"20.14.43",
|
||||
"20.21.37",
|
||||
"20.31.40",
|
||||
)
|
||||
)
|
||||
},
|
||||
executeBlock = {
|
||||
addResources("youtube", "misc.debugging.enableDebuggingPatch")
|
||||
},
|
||||
),
|
||||
hookStringFeatureFlag = true,
|
||||
preferenceScreen = PreferenceScreen.MISC,
|
||||
additionalDebugPreferences = listOf(SwitchPreference("revanced_debug_protobuffer"))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
package app.revanced.util
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Comments out the non-standard <app> and <patch> tags.
|
||||
*
|
||||
* Previously this was done on Crowdin after pushing.
|
||||
* But Crowdin preprocessing has randomly failed but still used the unmodified
|
||||
* strings.xml file, which effectively deletes all patch strings from Crowdin.
|
||||
*/
|
||||
internal fun main(args: Array<String>) {
|
||||
if (args.size != 2) {
|
||||
throw RuntimeException("Exactly two arguments are required: <input_file> <output_file>")
|
||||
}
|
||||
|
||||
val inputFilePath = args[0]
|
||||
val inputFile = File(inputFilePath)
|
||||
if (!inputFile.exists()) {
|
||||
throw RuntimeException(
|
||||
"Input file not found: $inputFilePath currentDirectory: " + File(".").canonicalPath
|
||||
)
|
||||
}
|
||||
|
||||
// Comment out the non-standard tags. Otherwise Crowdin interprets the file
|
||||
// not as Android but instead a generic xml file where strings are
|
||||
// identified by xml position and not key.
|
||||
val content = inputFile.readText()
|
||||
val tagRegex = """((<app\s+.*>)|(</app>)|(<patch\s+.*>)|(</patch>))""".toRegex()
|
||||
val modifiedContent = content.replace(tagRegex, """<!-- $1 -->""")
|
||||
|
||||
// Write modified content to the output file (creates file if it doesn't exist).
|
||||
val outputFilePath = args[1]
|
||||
val outputFile = File(outputFilePath)
|
||||
outputFile.parentFile?.mkdirs()
|
||||
outputFile.writeText(modifiedContent)
|
||||
|
||||
println("Preprocessed strings.xml to: $outputFilePath")
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -224,12 +224,14 @@ Second \"item\" text"</string>
|
|||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
</patch>
|
||||
<patch id="misc.announcements.announcementsPatch">
|
||||
<string name="revanced_announcements_dialog_dismiss">খাৰিজ কৰক</string>
|
||||
<string name="youtube.misc.announcements.announcementsPatch.revanced_announcements_dialog_dismiss">খাৰিজ কৰক</string>
|
||||
</patch>
|
||||
<patch id="misc.loopvideo.loopVideoPatch">
|
||||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -21,60 +21,60 @@ Second \"item\" text"</string>
|
|||
<resources>
|
||||
<app id="shared">
|
||||
<patch id="layout.branding.baseCustomBrandingPatch">
|
||||
<string name="revanced_custom_branding_name_title">نام برنامه</string>
|
||||
<string name="shared.layout.branding.baseCustomBrandingPatch.revanced_custom_branding_name_title">نام برنامه</string>
|
||||
<!-- Translations of this should be identical to revanced_custom_branding_icon_entry_5 -->
|
||||
<string name="revanced_custom_branding_icon_title">آیکون برنامه</string>
|
||||
<string name="shared.layout.branding.baseCustomBrandingPatch.revanced_custom_branding_icon_title">آیکون برنامه</string>
|
||||
<!-- Translation of this should be identical to revanced_header_logo_entry_5 -->
|
||||
<!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 -->
|
||||
</patch>
|
||||
<patch id="misc.checks.checkEnvironmentPatch">
|
||||
<string name="revanced_check_environment_failed_title">بررسی ناموفق بود</string>
|
||||
<string name="revanced_check_environment_dialog_open_official_source_button">رفتن به وبسایت رسمی</string>
|
||||
<string name="revanced_check_environment_dialog_ignore_button">نادیده بگیر</string>
|
||||
<string name="revanced_check_environment_failed_message"><h5>به نظر نمیرسد این برنامه توسط شما وصله شده باشد.</h5><br>این برنامه ممکن است به درستی کار نکند، <b>ممکن است استفاده از آن مضر یا حتی خطرناک باشد</b>.<br><بر>این برنامه از قبل دریافت شده است یا این چک از قبل دریافت شده است else:<br><br><small>%1$s</small><br>اکیداً توصیه میشود که <b>این برنامه را حذف نصب کنید و خودتان آن را وصله کنید</b> برای اطمینان از اینکه از یک برنامه معتبر و ایمن استفاده میکنید.<p><br>اگر نادیده گرفته شود، این هشدار فقط دو بار نشان داده میشود.</string>
|
||||
<string name="revanced_check_environment_not_same_patching_device">روی دستگاه دیگری وصله شده است</string>
|
||||
<string name="revanced_check_environment_manager_not_expected_installer">به وسیله ReVanced Manager نصب نشده است</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time">بیشتر از ۱۰ دقیقه پیش وصله شده است</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_days">%s روز پیش وصله شده است</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_invalid">تاریخ ایجاد APK مخدوش شده است</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_failed_title">بررسی ناموفق بود</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_dialog_open_official_source_button">رفتن به وبسایت رسمی</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_dialog_ignore_button">نادیده بگیر</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_failed_message"><h5>به نظر نمیرسد این برنامه توسط شما وصله شده باشد.</h5><br>این برنامه ممکن است به درستی کار نکند، <b>ممکن است استفاده از آن مضر یا حتی خطرناک باشد</b>.<br><بر>این برنامه از قبل دریافت شده است یا این چک از قبل دریافت شده است else:<br><br><small>%1$s</small><br>اکیداً توصیه میشود که <b>این برنامه را حذف نصب کنید و خودتان آن را وصله کنید</b> برای اطمینان از اینکه از یک برنامه معتبر و ایمن استفاده میکنید.<p><br>اگر نادیده گرفته شود، این هشدار فقط دو بار نشان داده میشود.</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_same_patching_device">روی دستگاه دیگری وصله شده است</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_manager_not_expected_installer">به وسیله ReVanced Manager نصب نشده است</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time">بیشتر از ۱۰ دقیقه پیش وصله شده است</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time_days">%s روز پیش وصله شده است</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time_invalid">تاریخ ایجاد APK مخدوش شده است</string>
|
||||
</patch>
|
||||
<patch id="misc.dns.checkWatchHistoryDomainNameResolutionPatch">
|
||||
</patch>
|
||||
<patch id="misc.settings.settingsResourcePatch">
|
||||
<string name="revanced_settings_submenu_title">تنظیمات</string>
|
||||
<string name="revanced_settings_reset">بازنشانی</string>
|
||||
<string name="revanced_settings_restart">راهاندازی مجدد</string>
|
||||
<string name="revanced_settings_import">واردکردن</string>
|
||||
<string name="revanced_settings_import_copy">رونوشت</string>
|
||||
<string name="revanced_settings_import_reset">بازگرداندن تنظیمات ReVanced به پیشفرض</string>
|
||||
<string name="revanced_settings_import_success">%d تنظیمات وارد شدند</string>
|
||||
<string name="revanced_settings_import_failure_parse">واردکردن انجام نشد: %s</string>
|
||||
<string name="revanced_settings_search_hint">تنظیمات جستجو</string>
|
||||
<string name="revanced_settings_search_no_results_title">نتایجی برای %s یافت نشد</string>
|
||||
<string name="revanced_settings_search_no_results_summary">کلیدواژه دیگری را امتحان کنید</string>
|
||||
<string name="revanced_settings_search_remove_message">حذف از تاریخچه جستجو؟</string>
|
||||
<string name="revanced_settings_search_empty_history_title">تاریخچه جستجو خالی است</string>
|
||||
<string name="revanced_settings_search_history_title">نمایش تاریخچه جستجوی تنظیمات</string>
|
||||
<string name="revanced_show_menu_icons_title">نمایش آیکون تنظیمات ReVanced</string>
|
||||
<string name="revanced_show_menu_icons_summary_on">نمادهای تنظیمات نشان داده میشوند</string>
|
||||
<string name="revanced_show_menu_icons_summary_off">نمادهای تنظیمات نمایش داده نمی شوند</string>
|
||||
<string name="revanced_language_title">زبان ReVanced</string>
|
||||
<string name="revanced_language_DEFAULT">زبان برنامه</string>
|
||||
<string name="revanced_pref_import_export_title">وارد کردن/صادر کردن</string>
|
||||
<string name="revanced_pref_import_export_summary">وارد کردن / صادر کردن تنظیمات ReVanced</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_submenu_title">تنظیمات</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_reset">بازنشانی</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_restart">راهاندازی مجدد</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_import">واردکردن</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_import_copy">رونوشت</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_import_reset">بازگرداندن تنظیمات ReVanced به پیشفرض</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_import_success">%d تنظیمات وارد شدند</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_import_failure_parse">واردکردن انجام نشد: %s</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_hint">تنظیمات جستجو</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_no_results_title">نتایجی برای %s یافت نشد</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_no_results_summary">کلیدواژه دیگری را امتحان کنید</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_remove_message">حذف از تاریخچه جستجو؟</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_empty_history_title">تاریخچه جستجو خالی است</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_history_title">نمایش تاریخچه جستجوی تنظیمات</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_show_menu_icons_title">نمایش آیکون تنظیمات ReVanced</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_show_menu_icons_summary_on">نمادهای تنظیمات نشان داده میشوند</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_show_menu_icons_summary_off">نمادهای تنظیمات نمایش داده نمی شوند</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_language_title">زبان ReVanced</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_language_DEFAULT">زبان برنامه</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_pref_import_export_title">وارد کردن/صادر کردن</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_pref_import_export_summary">وارد کردن / صادر کردن تنظیمات ReVanced</string>
|
||||
<!-- Settings about dialog. -->
|
||||
<string name="revanced_settings_about_links_body">شما درحال استفاده از نسخه <i>%s</i> از پچ Revanced هستید</string>
|
||||
<string name="revanced_settings_about_links_dev_header">توجه</string>
|
||||
<string name="revanced_settings_about_links_header">لینکهای رسمی</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_about_links_body">شما درحال استفاده از نسخه <i>%s</i> از پچ Revanced هستید</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_about_links_dev_header">توجه</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_about_links_header">لینکهای رسمی</string>
|
||||
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
|
||||
and changes made here must also be made there. -->
|
||||
</patch>
|
||||
<patch id="misc.gms.gmsCoreSupportResourcePatch">
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
<string name="gms_core_toast_not_installed_message">MicroG GmsCore نصب نشده است. آنرا نصب کنید.</string>
|
||||
<string name="gms_core_dialog_title">اقدام لازم است</string>
|
||||
<string name="gms_core_dialog_open_website_text">باز کردن تارنما</string>
|
||||
<string name="gms_core_dialog_continue_text">ادامه</string>
|
||||
<string name="shared.misc.gms.gmsCoreSupportResourcePatch.gms_core_toast_not_installed_message">MicroG GmsCore نصب نشده است. آنرا نصب کنید.</string>
|
||||
<string name="shared.misc.gms.gmsCoreSupportResourcePatch.gms_core_dialog_title">اقدام لازم است</string>
|
||||
<string name="shared.misc.gms.gmsCoreSupportResourcePatch.gms_core_dialog_open_website_text">باز کردن تارنما</string>
|
||||
<string name="shared.misc.gms.gmsCoreSupportResourcePatch.gms_core_dialog_continue_text">ادامه</string>
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
|
||||
</patch>
|
||||
|
|
@ -82,22 +82,22 @@ Second \"item\" text"</string>
|
|||
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
|
||||
</patch>
|
||||
<patch id="misc.debugging.enableDebuggingPatch">
|
||||
<string name="revanced_debug_screen_title">عیبیابی</string>
|
||||
<string name="revanced_debug_screen_summary">فعال یا غیرفعال کردن گزینههای عیب یابی</string>
|
||||
<string name="revanced_debug_title">گزارش عیب</string>
|
||||
<string name="revanced_debug_summary_on">لاگ عیب فعال است</string>
|
||||
<string name="revanced_debug_summary_off">لاگ عیب غیرفعال است</string>
|
||||
<string name="shared.misc.debugging.enableDebuggingPatch.revanced_debug_screen_title">عیبیابی</string>
|
||||
<string name="shared.misc.debugging.enableDebuggingPatch.revanced_debug_screen_summary">فعال یا غیرفعال کردن گزینههای عیب یابی</string>
|
||||
<string name="shared.misc.debugging.enableDebuggingPatch.revanced_debug_title">گزارش عیب</string>
|
||||
<string name="shared.misc.debugging.enableDebuggingPatch.revanced_debug_summary_on">لاگ عیب فعال است</string>
|
||||
<string name="shared.misc.debugging.enableDebuggingPatch.revanced_debug_summary_off">لاگ عیب غیرفعال است</string>
|
||||
</patch>
|
||||
<patch id="misc.privacy.sanitizeSharingLinksPatch">
|
||||
</patch>
|
||||
</app>
|
||||
<app id="youtube">
|
||||
<patch id="misc.settings.settingsPatch">
|
||||
<string name="revanced_settings_screen_00_about_title">درباره</string>
|
||||
<string name="revanced_settings_screen_04_general_title">عمومی</string>
|
||||
<string name="revanced_settings_screen_05_player_title">اجراکننده</string>
|
||||
<string name="revanced_settings_screen_07_seekbar_title">نوار جریان پخش</string>
|
||||
<string name="revanced_settings_screen_12_video_title">ويدئو</string>
|
||||
<string name="youtube.misc.settings.settingsPatch.revanced_settings_screen_00_about_title">درباره</string>
|
||||
<string name="youtube.misc.settings.settingsPatch.revanced_settings_screen_04_general_title">عمومی</string>
|
||||
<string name="youtube.misc.settings.settingsPatch.revanced_settings_screen_05_player_title">اجراکننده</string>
|
||||
<string name="youtube.misc.settings.settingsPatch.revanced_settings_screen_07_seekbar_title">نوار جریان پخش</string>
|
||||
<string name="youtube.misc.settings.settingsPatch.revanced_settings_screen_12_video_title">ويدئو</string>
|
||||
</patch>
|
||||
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
|
||||
</patch>
|
||||
|
|
@ -108,8 +108,8 @@ Second \"item\" text"</string>
|
|||
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
|
||||
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
||||
This button usually appears when searching for a YT creator. -->
|
||||
<string name="revanced_hide_show_more_button_title">پنهان سازی دکمه \'نمایش بیشتر\'</string>
|
||||
<string name="revanced_hide_ticket_shelf_title">پنهان سازی قفسه بلیط</string>
|
||||
<string name="youtube.layout.hide.general.hideLayoutComponentsPatch.revanced_hide_show_more_button_title">پنهان سازی دکمه \'نمایش بیشتر\'</string>
|
||||
<string name="youtube.layout.hide.general.hideLayoutComponentsPatch.revanced_hide_ticket_shelf_title">پنهان سازی قفسه بلیط</string>
|
||||
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
||||
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
|
||||
<!-- 'Join' should be translated using the same localized wording YouTube displays.
|
||||
|
|
@ -241,12 +241,12 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="layout.sponsorblock.sponsorBlockResourcePatch">
|
||||
<!-- Translations should use language similar to 'revanced_ryd_compact_layout_title'. -->
|
||||
<string name="revanced_sb_general">عمومی</string>
|
||||
<string name="revanced_sb_settings_copy">رونوشت</string>
|
||||
<string name="youtube.layout.sponsorblock.sponsorBlockResourcePatch.revanced_sb_general">عمومی</string>
|
||||
<string name="youtube.layout.sponsorblock.sponsorBlockResourcePatch.revanced_sb_settings_copy">رونوشت</string>
|
||||
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
|
||||
<!-- A segment start and end time, such as "02:10 to 03:40". -->
|
||||
<!-- Shown in the settings preferences, and translations can be any text length. -->
|
||||
<string name="revanced_sb_about_title">درباره</string>
|
||||
<string name="youtube.layout.sponsorblock.sponsorBlockResourcePatch.revanced_sb_about_title">درباره</string>
|
||||
</patch>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
</patch>
|
||||
|
|
@ -283,6 +283,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
@ -356,10 +358,10 @@ Second \"item\" text"</string>
|
|||
<!-- Twitch specific internal debug mode, and not the same as 'revanced_debug_title'. -->
|
||||
</patch>
|
||||
<patch id="misc.settings.settingsPatch">
|
||||
<string name="revanced_about_title">درباره</string>
|
||||
<string name="revanced_twitch_debug_title">گزارش عیب</string>
|
||||
<string name="revanced_twitch_debug_summary_on">لاگ عیب فعال است</string>
|
||||
<string name="revanced_twitch_debug_summary_off">لاگ عیب غیرفعال است</string>
|
||||
<string name="twitch.misc.settings.settingsPatch.revanced_about_title">درباره</string>
|
||||
<string name="twitch.misc.settings.settingsPatch.revanced_twitch_debug_title">گزارش عیب</string>
|
||||
<string name="twitch.misc.settings.settingsPatch.revanced_twitch_debug_summary_on">لاگ عیب فعال است</string>
|
||||
<string name="twitch.misc.settings.settingsPatch.revanced_twitch_debug_summary_off">لاگ عیب غیرفعال است</string>
|
||||
</patch>
|
||||
</app>
|
||||
</resources>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -38,8 +38,7 @@ Second \"item\" text"</string>
|
|||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
|
||||
<string name="revanced_spoof_video_streams_screen_summary">प्लेबैक समस्याओं को रोकने के लिए क्लाइंट वीडियो स्ट्रीम को स्पूफ करें</string>
|
||||
<string name="revanced_spoof_video_streams_screen_summary">प्लेबैक समस्याओं को रोकने के लिए क्लाइंट वीडियो स्ट्रीम को स्पूफ करें</string>
|
||||
<string name="shared.misc.fix.playback.spoofVideoStreamsPatch.revanced_spoof_video_streams_screen_summary">प्लेबैक समस्याओं को रोकने के लिए क्लाइंट वीडियो स्ट्रीम को स्पूफ करें</string>
|
||||
</patch>
|
||||
<patch id="misc.audio.forceOriginalAudioPatch">
|
||||
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
|
||||
|
|
@ -231,6 +230,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -26,12 +26,12 @@ Second \"item\" text"</string>
|
|||
<!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 -->
|
||||
</patch>
|
||||
<patch id="misc.checks.checkEnvironmentPatch">
|
||||
<string name="revanced_check_environment_failed_title">Provjere nisu uspjele</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_failed_title">Provjere nisu uspjele</string>
|
||||
</patch>
|
||||
<patch id="misc.dns.checkWatchHistoryDomainNameResolutionPatch">
|
||||
</patch>
|
||||
<patch id="misc.settings.settingsResourcePatch">
|
||||
<string name="revanced_settings_save">Sačuvaj</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_save">Sačuvaj</string>
|
||||
<!-- Settings about dialog. -->
|
||||
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
|
||||
and changes made here must also be made there. -->
|
||||
|
|
@ -231,6 +231,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -26,25 +26,25 @@ Second \"item\" text"</string>
|
|||
<!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 -->
|
||||
</patch>
|
||||
<patch id="misc.checks.checkEnvironmentPatch">
|
||||
<string name="revanced_check_environment_failed_title">ಪರಿಶೀಲನೆ ವಿಫಲವಾಗಿದೆ</string>
|
||||
<string name="revanced_check_environment_dialog_open_official_source_button">ಅಧಿಕೃತ ಜಾಲತಾಣ ತೆರೆಯಿರಿ</string>
|
||||
<string name="revanced_check_environment_dialog_ignore_button">ನಿರ್ಲಕ್ಷಿಸು</string>
|
||||
<string name="revanced_check_environment_failed_message"><h5>ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ನೀವು ಪ್ಯಾಚ್ ಮಾಡಿದಂತೆ ಕಾಣುತ್ತಿಲ್ಲ.</h5><br>ಈ ಅಪ್ಲಿಕೇಶನ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸದೇ ಇರಬಹುದು, <b> ಹಾಗು ಉಪಯೋಗಿಸಲು ಹಾನಿಕಾರಕ ಅಥವಾ ಅಪಾಯಕಾರಿಯಾಗಿರಬಹುದು</b>.<br><br>ಈ ಅಪ್ಲಿಕೇಶನ್ ಮೊದಲೇ ಪ್ಯಾಚ್ ಆಗಿದೆ ಅಥವಾ ಬೇರೆಯವರಿಂದ ಪಡೆದದ್ದು ಎಂದು ಈ ಪರಿಶೀಲನೆಗಳು ಸೂಚಿಸುತ್ತವೆ:<br><br><small>%1$s</small><br>ನೀವು ದೃಢೀಕೃತ ಮತ್ತು ಸುರಕ್ಷಿತ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು <b>ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನ್ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ ಮತ್ತು ನೀವೇ ಪ್ಯಾಚ್ ಮಾಡಿ</b> ಎಂದು ಬಲವಾಗಿ ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ.<p><br>ನಿರ್ಲಕ್ಷಿಸಿದರೆ, ಈ ಎಚ್ಚರಿಕೆಯನ್ನು ಎರಡು ಬಾರಿ ಮಾತ್ರ ತೋರಿಸಲಾಗುತ್ತದೆ.</string>
|
||||
<string name="revanced_check_environment_not_same_patching_device">ಬೇರೆ ಸಾಧನದಲ್ಲಿ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string>
|
||||
<string name="revanced_check_environment_manager_not_expected_installer">ReVanced Manager ನಿಂದ ಸ್ಥಾಪಿಸಿದ್ದಲ್ಲ</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time">10 ನಿಮಿಷಗಳಿಗಿಂತ ಮುಂಚೆ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_days">%s ದಿನಗಳ ಹಿಂದೆ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_invalid">APK ನಿರ್ಮಾಣ ದಿನಾಂಕವು ಭ್ರಷ್ಟಗೊಂಡಿದೆ</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_failed_title">ಪರಿಶೀಲನೆ ವಿಫಲವಾಗಿದೆ</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_dialog_open_official_source_button">ಅಧಿಕೃತ ಜಾಲತಾಣ ತೆರೆಯಿರಿ</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_dialog_ignore_button">ನಿರ್ಲಕ್ಷಿಸು</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_failed_message"><h5>ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ನೀವು ಪ್ಯಾಚ್ ಮಾಡಿದಂತೆ ಕಾಣುತ್ತಿಲ್ಲ.</h5><br>ಈ ಅಪ್ಲಿಕೇಶನ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸದೇ ಇರಬಹುದು, <b> ಹಾಗು ಉಪಯೋಗಿಸಲು ಹಾನಿಕಾರಕ ಅಥವಾ ಅಪಾಯಕಾರಿಯಾಗಿರಬಹುದು</b>.<br><br>ಈ ಅಪ್ಲಿಕೇಶನ್ ಮೊದಲೇ ಪ್ಯಾಚ್ ಆಗಿದೆ ಅಥವಾ ಬೇರೆಯವರಿಂದ ಪಡೆದದ್ದು ಎಂದು ಈ ಪರಿಶೀಲನೆಗಳು ಸೂಚಿಸುತ್ತವೆ:<br><br><small>%1$s</small><br>ನೀವು ದೃಢೀಕೃತ ಮತ್ತು ಸುರಕ್ಷಿತ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು <b>ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನ್ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ ಮತ್ತು ನೀವೇ ಪ್ಯಾಚ್ ಮಾಡಿ</b> ಎಂದು ಬಲವಾಗಿ ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ.<p><br>ನಿರ್ಲಕ್ಷಿಸಿದರೆ, ಈ ಎಚ್ಚರಿಕೆಯನ್ನು ಎರಡು ಬಾರಿ ಮಾತ್ರ ತೋರಿಸಲಾಗುತ್ತದೆ.</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_same_patching_device">ಬೇರೆ ಸಾಧನದಲ್ಲಿ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_manager_not_expected_installer">ReVanced Manager ನಿಂದ ಸ್ಥಾಪಿಸಿದ್ದಲ್ಲ</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time">10 ನಿಮಿಷಗಳಿಗಿಂತ ಮುಂಚೆ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time_days">%s ದಿನಗಳ ಹಿಂದೆ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time_invalid">APK ನಿರ್ಮಾಣ ದಿನಾಂಕವು ಭ್ರಷ್ಟಗೊಂಡಿದೆ</string>
|
||||
</patch>
|
||||
<patch id="misc.dns.checkWatchHistoryDomainNameResolutionPatch">
|
||||
</patch>
|
||||
<patch id="misc.settings.settingsResourcePatch">
|
||||
<string name="revanced_settings_submenu_title">ಸಂಯೋಜನೆಗಳು</string>
|
||||
<string name="revanced_settings_confirm_user_dialog_title">ನೀವು ಮುಂದುವರಿಯಲು ಖಚಿತವಾಗಿ ಬಯಸುತ್ತೀರಾ?</string>
|
||||
<string name="revanced_settings_reset">ಮರುಹೊಂದಿಸಿ</string>
|
||||
<string name="revanced_settings_reset_color">ಬಣ್ಣ ಮರುಹೊಂದಿಸಿ</string>
|
||||
<string name="revanced_settings_color_invalid">ಅಮಾನ್ಯ ಬಣ್ಣ</string>
|
||||
<string name="revanced_settings_restart_title">ಮರುಪ್ರಾರಂಭದ ಅಗತ್ಯವಿದೆ</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_submenu_title">ಸಂಯೋಜನೆಗಳು</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_confirm_user_dialog_title">ನೀವು ಮುಂದುವರಿಯಲು ಖಚಿತವಾಗಿ ಬಯಸುತ್ತೀರಾ?</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_reset">ಮರುಹೊಂದಿಸಿ</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_reset_color">ಬಣ್ಣ ಮರುಹೊಂದಿಸಿ</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_color_invalid">ಅಮಾನ್ಯ ಬಣ್ಣ</string>
|
||||
<string name="shared.misc.settings.settingsResourcePatch.revanced_settings_restart_title">ಮರುಪ್ರಾರಂಭದ ಅಗತ್ಯವಿದೆ</string>
|
||||
<!-- Settings about dialog. -->
|
||||
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
|
||||
and changes made here must also be made there. -->
|
||||
|
|
@ -244,6 +244,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -224,12 +224,14 @@ Second \"item\" text"</string>
|
|||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
</patch>
|
||||
<patch id="misc.announcements.announcementsPatch">
|
||||
<string name="revanced_announcements_dialog_dismiss">Melepaskan</string>
|
||||
<string name="youtube.misc.announcements.announcementsPatch.revanced_announcements_dialog_dismiss">Melepaskan</string>
|
||||
</patch>
|
||||
<patch id="misc.loopvideo.loopVideoPatch">
|
||||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -26,8 +26,8 @@ Second \"item\" text"</string>
|
|||
<!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 -->
|
||||
</patch>
|
||||
<patch id="misc.checks.checkEnvironmentPatch">
|
||||
<string name="revanced_check_environment_manager_not_expected_installer">ReVanced Manager द्वारा स्थापित छैन</string>
|
||||
<string name="revanced_check_environment_not_near_patch_time_invalid">APK निर्माण मिति खराब भएको छ</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_manager_not_expected_installer">ReVanced Manager द्वारा स्थापित छैन</string>
|
||||
<string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time_invalid">APK निर्माण मिति खराब भएको छ</string>
|
||||
</patch>
|
||||
<patch id="misc.dns.checkWatchHistoryDomainNameResolutionPatch">
|
||||
</patch>
|
||||
|
|
@ -231,6 +231,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<!--
|
||||
|
||||
All strings must have a unique path, even if the same string is declared in two different apps.
|
||||
|
|
@ -229,6 +229,8 @@ Second \"item\" text"</string>
|
|||
</patch>
|
||||
<patch id="misc.loopvideo.button.loopVideoButtonPatch">
|
||||
</patch>
|
||||
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
|
||||
</patch>
|
||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||
</patch>
|
||||
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue