Compare commits

...

7 Commits

Author SHA1 Message Date
semantic-release-bot
555c9a5823 chore: Release v5.23.0-dev.4 [skip ci]
# [5.23.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.3...v5.23.0-dev.4) (2025-05-06)

### Features

* **Spotify:** Add `Sanitize sharing links` patch ([#4829](https://github.com/ReVanced/revanced-patches/issues/4829)) ([777957e](777957e2d0))
2025-05-06 07:35:22 +00:00
Dawid Krajcarz
777957e2d0 feat(Spotify): Add Sanitize sharing links patch (#4829) 2025-05-06 11:31:56 +04:00
github-actions[bot]
b3316a5915 chore: Sync translations (#4915) 2025-05-06 11:31:12 +04:00
semantic-release-bot
2ca2bb7692 chore: Release v5.23.0-dev.3 [skip ci]
# [5.23.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.2...v5.23.0-dev.3) (2025-05-05)

### Bug Fixes

* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([23fd720](23fd720fa7))
2025-05-05 11:28:44 +00:00
LisoUseInAIKyrios
23fd720fa7 fix(YouTube): Simplify litho filtering patch (#4910) 2025-05-05 15:25:25 +04:00
semantic-release-bot
1f08586ae8 chore: Release v5.23.0-dev.2 [skip ci]
# [5.23.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.1...v5.23.0-dev.2) (2025-05-04)

### Bug Fixes

* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([60fdf4c](60fdf4c44c))
2025-05-04 09:58:25 +00:00
LisoUseInAIKyrios
60fdf4c44c fix(YouTube): Improve litho filtering performance (#4904) 2025-05-04 13:55:12 +04:00
19 changed files with 458 additions and 240 deletions

View File

@@ -1,3 +1,24 @@
# [5.23.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.3...v5.23.0-dev.4) (2025-05-06)
### Features
* **Spotify:** Add `Sanitize sharing links` patch ([#4829](https://github.com/ReVanced/revanced-patches/issues/4829)) ([2e3511d](https://github.com/ReVanced/revanced-patches/commit/2e3511d03c8198bbdb9336888df038a33fb3ab8c))
# [5.23.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.2...v5.23.0-dev.3) (2025-05-05)
### Bug Fixes
* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([bd53955](https://github.com/ReVanced/revanced-patches/commit/bd53955df738bb7b819eb91a3e776e9d2ca5c74a))
# [5.23.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.1...v5.23.0-dev.2) (2025-05-04)
### Bug Fixes
* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([7b43986](https://github.com/ReVanced/revanced-patches/commit/7b43986871a68e5cb43331d2fb2fdb9ef67438ad))
# [5.23.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0-dev.1) (2025-05-02)

View File

@@ -0,0 +1,43 @@
package app.revanced.extension.spotify.misc.privacy;
import android.net.Uri;
import java.util.List;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public final class SanitizeSharingLinksPatch {
/**
* Parameters that are considered undesirable and should be stripped away.
*/
private static final List<String> SHARE_PARAMETERS_TO_REMOVE = List.of(
"si", // Share tracking parameter.
"utm_source" // Share source, such as "copy-link".
);
/**
* Injection point.
*/
public static String sanitizeUrl(String url) {
try {
Uri uri = Uri.parse(url);
Uri.Builder builder = uri.buildUpon().clearQuery();
for (String paramName : uri.getQueryParameterNames()) {
if (!SHARE_PARAMETERS_TO_REMOVE.contains(paramName)) {
for (String value : uri.getQueryParameters(paramName)) {
builder.appendQueryParameter(paramName, value);
}
}
}
return builder.build().toString();
} catch (Exception ex) {
Logger.printException(() -> "sanitizeUrl failure", ex);
return url;
}
}
}

View File

@@ -87,6 +87,10 @@ public final class LithoFilterPatch {
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
*/
private static final ThreadLocal<ByteBuffer> bufferThreadLocal = new ThreadLocal<>();
/**
* Results of calling {@link #filter(String, StringBuilder)}.
*/
private static final ThreadLocal<Boolean> filterResult = new ThreadLocal<>();
static {
for (Filter filter : filters) {
@@ -140,11 +144,22 @@ public final class LithoFilterPatch {
}
}
/**
* Injection point.
*/
public static boolean shouldFilter() {
Boolean shouldFilter = filterResult.get();
return shouldFilter != null && shouldFilter;
}
/**
* Injection point. Called off the main thread, and commonly called by multiple threads at the same time.
*/
@SuppressWarnings("unused")
public static boolean filter(@Nullable String lithoIdentifier, @NonNull StringBuilder pathBuilder) {
public static void filter(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
filterResult.set(handleFiltering(lithoIdentifier, pathBuilder));
}
private static boolean handleFiltering(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
try {
if (pathBuilder.length() == 0) {
return false;

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
version = 5.23.0-dev.1
version = 5.23.0-dev.4

View File

@@ -852,6 +852,10 @@ public final class app/revanced/patches/spotify/misc/fix/SpoofSignaturePatchKt {
public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt {
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/navbar/PremiumNavbarTabPatchKt {
public static final fun getPremiumNavbarTabPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1534,6 +1538,7 @@ public final class app/revanced/patches/yuka/misc/unlockpremium/UnlockPremiumPat
public final class app/revanced/util/BytecodeUtilsKt {
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;[Lapp/revanced/patcher/util/smali/ExternalLabel;)V
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;D)Z
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;F)Z
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z

View File

@@ -0,0 +1,41 @@
package app.revanced.patches.spotify.misc.privacy
import app.revanced.patcher.fingerprint
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
internal val shareCopyUrlFingerprint = fingerprint {
returns("Ljava/lang/Object;")
parameters("Ljava/lang/Object;")
strings("clipboard", "Spotify Link")
custom { method, _ ->
method.name == "invokeSuspend"
}
}
internal val shareCopyUrlLegacyFingerprint = fingerprint {
returns("Ljava/lang/Object;")
parameters("Ljava/lang/Object;")
strings("clipboard", "createNewSession failed")
custom { method, _ ->
method.name == "apply"
}
}
internal val formatAndroidShareSheetUrlFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Ljava/lang/String;")
parameters("L", "Ljava/lang/String;")
literal {
'\n'.code.toLong()
}
}
internal val formatAndroidShareSheetUrlLegacyFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
returns("Ljava/lang/String;")
parameters("Lcom/spotify/share/social/sharedata/ShareData;", "Ljava/lang/String;")
literal {
'\n'.code.toLong()
}
}

View File

@@ -0,0 +1,70 @@
package app.revanced.patches.spotify.misc.privacy
import app.revanced.patcher.Fingerprint
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/spotify/misc/privacy/SanitizeSharingLinksPatch;"
@Suppress("unused")
val sanitizeSharingLinksPatch = bytecodePatch(
name = "Sanitize sharing links",
description = "Removes the tracking query parameters from links before they are shared.",
) {
compatibleWith("com.spotify.music")
dependsOn(sharedExtensionPatch)
execute {
val extensionMethodDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->" +
"sanitizeUrl(Ljava/lang/String;)Ljava/lang/String;"
val copyFingerprint = if (IS_SPOTIFY_LEGACY_APP_TARGET) {
shareCopyUrlLegacyFingerprint
} else {
shareCopyUrlFingerprint
}
copyFingerprint.method.apply {
val newPlainTextInvokeIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "newPlainText"
}
val register = getInstruction<FiveRegisterInstruction>(newPlainTextInvokeIndex).registerD
addInstructions(
newPlainTextInvokeIndex,
"""
invoke-static { v$register }, $extensionMethodDescriptor
move-result-object v$register
"""
)
}
// Android native share sheet is used for all other quick share types (X, WhatsApp, etc).
val shareUrlParameter : String
val shareSheetFingerprint : Fingerprint
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
shareSheetFingerprint = formatAndroidShareSheetUrlLegacyFingerprint
shareUrlParameter = "p2"
} else {
shareSheetFingerprint = formatAndroidShareSheetUrlFingerprint
shareUrlParameter = "p1"
}
shareSheetFingerprint.method.addInstructions(
0,
"""
invoke-static { $shareUrlParameter }, $extensionMethodDescriptor
move-result-object $shareUrlParameter
"""
)
}
}

View File

@@ -5,18 +5,6 @@ import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val conversionContextFingerprint = fingerprint {
returns("Ljava/lang/String;")
parameters()
strings(
", widthConstraint=",
", heightConstraint=",
", templateLoggerFactory=",
", rootDisposableContainer=",
"ConversionContext{containerInternal=",
)
}
internal val dislikeFingerprint = fingerprint {
returns("V")
strings("like/dislike")

View File

@@ -18,6 +18,7 @@ import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.addSettingPreference
import app.revanced.patches.youtube.misc.settings.newIntent
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.conversionContextFingerprintToString
import app.revanced.patches.youtube.shared.rollingNumberTextViewAnimationUpdateFingerprint
import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId
import app.revanced.patches.youtube.video.videoid.hookVideoId
@@ -113,11 +114,11 @@ val returnYouTubeDislikePatch = bytecodePatch(
// This hook handles all situations, as it's where the created Spans are stored and later reused.
// Find the field name of the conversion context.
val conversionContextField = textComponentConstructorFingerprint.originalClassDef.fields.find {
it.type == conversionContextFingerprint.originalClassDef.type
it.type == conversionContextFingerprintToString.originalClassDef.type
} ?: throw PatchException("Could not find conversion context field")
textComponentLookupFingerprint.match(textComponentConstructorFingerprint.originalClassDef)
textComponentLookupFingerprint.method.apply {
.method.apply {
// Find the instruction for creating the text data object.
val textDataClassType = textComponentDataFingerprint.originalClassDef.type
@@ -160,12 +161,12 @@ val returnYouTubeDislikePatch = bytecodePatch(
addInstructionsAtControlFlowLabel(
insertIndex,
"""
# Copy conversion context
move-object/from16 v$tempRegister, p0
iget-object v$tempRegister, v$tempRegister, $conversionContextField
invoke-static { v$tempRegister, v$charSequenceRegister }, $EXTENSION_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
move-result-object v$charSequenceRegister
""",
# Copy conversion context
move-object/from16 v$tempRegister, p0
iget-object v$tempRegister, v$tempRegister, $conversionContextField
invoke-static { v$tempRegister, v$charSequenceRegister }, $EXTENSION_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
move-result-object v$charSequenceRegister
"""
)
}
@@ -201,11 +202,9 @@ val returnYouTubeDislikePatch = bytecodePatch(
val charSequenceFieldReference =
getInstruction<ReferenceInstruction>(dislikesIndex).reference
val registerCount = implementation!!.registerCount
val conversionContextRegister = implementation!!.registerCount - parameters.size + 1
// This register is being overwritten, so it is free to use.
val freeRegister = registerCount - 1
val conversionContextRegister = registerCount - parameters.size + 1
val freeRegister = findFreeRegister(insertIndex, charSequenceInstanceRegister, conversionContextRegister)
addInstructions(
insertIndex,

View File

@@ -5,10 +5,6 @@ import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
/**
* In 19.17 and earlier, this resolves to the same method as [readComponentIdentifierFingerprint].
* In 19.18+ this resolves to a different method.
*/
internal val componentContextParserFingerprint = fingerprint {
strings(
"TreeNode result must be set.",
@@ -17,11 +13,21 @@ internal val componentContextParserFingerprint = fingerprint {
)
}
/**
* Resolves to the class found in [componentContextParserFingerprint].
* When patching 19.16 this fingerprint matches the same method as [componentContextParserFingerprint].
*/
internal val componentContextSubParserFingerprint = fingerprint {
strings(
"Number of bits must be positive"
)
}
internal val lithoFilterFingerprint = fingerprint {
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
returns("V")
custom { _, classDef ->
classDef.endsWith("LithoFilterPatch;")
classDef.endsWith("/LithoFilterPatch;")
}
}
@@ -37,14 +43,6 @@ internal val protobufBufferReferenceFingerprint = fingerprint {
)
}
/**
* In 19.17 and earlier, this resolves to the same method as [componentContextParserFingerprint].
* In 19.18+ this resolves to a different method.
*/
internal val readComponentIdentifierFingerprint = fingerprint {
strings("Number of bits must be positive")
}
internal val emptyComponentFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
parameters()

View File

@@ -4,25 +4,25 @@ package app.revanced.patches.youtube.misc.litho.filter
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_18_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_05_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.shared.conversionContextFingerprintToString
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findFreeRegister
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@@ -53,42 +53,33 @@ val lithoFilterPatch = bytecodePatch(
* The buffer is a large byte array that represents the component tree.
* This byte array is searched for strings that indicate the current component.
*
* The following pseudocode shows how the patch works:
* 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.parseBytesToComponentContext method.
* // Called before ComponentContextParser.parseComponent() method.
* public void someOtherMethod(ByteBuffer byteBuffer) {
* ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
* ...
* }
* }
*
* When patching 19.17 and earlier:
*
* class ComponentContextParser {
* public ComponentContext ReadComponentIdentifierFingerprint(...) {
* public Component parseComponent() {
* ...
* if (extensionClass.filter(identifier, pathBuilder)); // Inserted by this patch.
*
* // Checks if the component should be filtered.
* // Sets a thread local with the filtering result.
* extensionClass.filter(identifier, pathBuilder); // Inserted by this patch.
*
* ...
*
* if (extensionClass.shouldFilter()) { // Inserted by this patch.
* return emptyComponent;
* ...
* }
* }
*
* When patching 19.18 and later:
*
* class ComponentContextParser {
* public ComponentContext parseBytesToComponentContext(...) {
* ...
* if (ReadComponentIdentifierFingerprint() == null); // Inserted by this patch.
* return emptyComponent;
* ...
* }
*
* public ComponentIdentifierObj readComponentIdentifier(...) {
* ...
* if (extensionClass.filter(identifier, pathBuilder)); // Inserted by this patch.
* return null;
* ...
* }
* return originalUnpatchedComponent; // Original code.
* }
* }
*/
@@ -103,7 +94,7 @@ val lithoFilterPatch = bytecodePatch(
2,
"""
new-instance v1, $classDescriptor
invoke-direct {v1}, $classDescriptor-><init>()V
invoke-direct { v1 }, $classDescriptor-><init>()V
const/16 v2, ${filterCount++}
aput-object v1, v0, v2
""",
@@ -115,110 +106,105 @@ val lithoFilterPatch = bytecodePatch(
protobufBufferReferenceFingerprint.method.addInstruction(
0,
" invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
"invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
)
// endregion
// region Hook the method that parses bytes into a ComponentContext.
val readComponentMethod = readComponentIdentifierFingerprint.originalMethod
// Get the only static method in the class.
val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.first { method ->
AccessFlags.STATIC.isSet(method.accessFlags)
}
// Only one field.
val emptyComponentField = classBy { classDef ->
builderMethodDescriptor.returnType == classDef.type
}!!.immutableClass.fields.single()
// Returns an empty component instead of the original component.
fun createReturnEmptyComponentInstructions(register: Int): String =
"""
move-object/from16 v$register, p1
invoke-static { v$register }, $builderMethodDescriptor
move-result-object v$register
iget-object v$register, v$register, $emptyComponentField
return-object v$register
"""
// 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 memory leaks and poor app performance can occur.
//
// The extension filtering result needs to be saved off somewhere, but cannot
// save to a class field since the target class is called by multiple threads.
// It would be great if there was a way to change the register count of the
// method implementation and save the result to a high register to later use
// in the method, but there is no simple way to do that.
// Instead save the extension filter result to a thread local and check the
// filtering result at each method return index.
// String field for the litho identifier.
componentContextParserFingerprint.method.apply {
// 19.18 and later require patching 2 methods instead of one.
// Otherwise the modifications done here are the same for all targets.
if (is_19_18_or_greater) {
// Get the method name of the ReadComponentIdentifierFingerprint call.
val readComponentMethodCallIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == readComponentMethod.definingClass &&
reference.name == readComponentMethod.name
val conversionContextClass = conversionContextFingerprintToString.originalClassDef
val conversionContextIdentifierField = componentContextSubParserFingerprint.match(
componentContextParserFingerprint.originalClassDef
).let {
// Identifier field is loaded just before the string declaration.
val index = it.method.indexOfFirstInstructionReversedOrThrow(
it.stringMatches!!.first().index
) {
val reference = getReference<FieldReference>()
reference?.definingClass == conversionContextClass.type
&& reference.type == "Ljava/lang/String;"
}
// Result of read component, and also a free register.
val register = getInstruction<OneRegisterInstruction>(readComponentMethodCallIndex + 1).registerA
// Insert after 'move-result-object'
val insertHookIndex = readComponentMethodCallIndex + 2
// Return an EmptyComponent instead of the original component if the filterState method returns true.
addInstructionsWithLabels(
insertHookIndex,
"""
if-nez v$register, :unfiltered
# Component was filtered in ReadComponentIdentifierFingerprint hook
${createReturnEmptyComponentInstructions(register)}
""",
ExternalLabel("unfiltered", getInstruction(insertHookIndex)),
)
it.method.getInstruction<ReferenceInstruction>(index).getReference<FieldReference>()
}
}
// endregion
// StringBuilder field for the litho path.
val conversionContextPathBuilderField = conversionContextClass.fields
.single { field -> field.type == "Ljava/lang/StringBuilder;" }
// region Read component then store the result.
val conversionContextResultIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.returnType == conversionContextClass.type
} + 1
readComponentIdentifierFingerprint.method.apply {
val insertHookIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.IPUT_OBJECT &&
getReference<FieldReference>()?.type == "Ljava/lang/StringBuilder;"
}
val stringBuilderRegister = getInstruction<TwoRegisterInstruction>(insertHookIndex).registerA
// Identifier is saved to a field just before the string builder.
val identifierRegister = getInstruction<TwoRegisterInstruction>(
indexOfFirstInstructionReversedOrThrow(insertHookIndex) {
opcode == Opcode.IPUT_OBJECT &&
getReference<FieldReference>()?.type == "Ljava/lang/String;"
},
val conversionContextResultRegister = getInstruction<OneRegisterInstruction>(
conversionContextResultIndex
).registerA
val freeRegister = findFreeRegister(insertHookIndex, identifierRegister, stringBuilderRegister)
val invokeFilterInstructions = """
invoke-static { v$identifierRegister, v$stringBuilderRegister }, $EXTENSION_CLASS_DESCRIPTOR->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
move-result v$freeRegister
if-eqz v$freeRegister, :unfiltered
"""
addInstructionsWithLabels(
insertHookIndex,
if (is_19_18_or_greater) {
"""
$invokeFilterInstructions
# Return null, and the ComponentContextParserFingerprint hook
# handles returning an empty component.
const/4 v$freeRegister, 0x0
return-object v$freeRegister
"""
} else {
"""
$invokeFilterInstructions
${createReturnEmptyComponentInstructions(freeRegister)}
"""
},
ExternalLabel("unfiltered", getInstruction(insertHookIndex)),
val identifierRegister = findFreeRegister(
conversionContextResultIndex, conversionContextResultRegister
)
val stringBuilderRegister = findFreeRegister(
conversionContextResultIndex, conversionContextResultRegister, identifierRegister
)
// Check if the component should be filtered, and save the result to a thread local.
addInstructionsAtControlFlowLabel(
conversionContextResultIndex + 1,
"""
iget-object v$identifierRegister, v$conversionContextResultRegister, $conversionContextIdentifierField
iget-object v$stringBuilderRegister, v$conversionContextResultRegister, $conversionContextPathBuilderField
invoke-static { v$identifierRegister, v$stringBuilderRegister }, $EXTENSION_CLASS_DESCRIPTOR->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)V
"""
)
// Get the only static method in the class.
val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.single {
method -> AccessFlags.STATIC.isSet(method.accessFlags)
}
// Only one field.
val emptyComponentField = classBy { classDef ->
classDef.type == builderMethodDescriptor.returnType
}!!.immutableClass.fields.single()
// Check at each return value if the component is filtered,
// and return an empty component if filtering is needed.
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { returnIndex ->
val freeRegister = findFreeRegister(returnIndex)
addInstructionsAtControlFlowLabel(
returnIndex,
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->shouldFilter()Z
move-result v$freeRegister
if-eqz v$freeRegister, :unfiltered
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
"""
)
}
}
// endregion

View File

@@ -4,6 +4,21 @@ import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val conversionContextFingerprintToString = fingerprint {
parameters()
strings(
"ConversionContext{containerInternal=",
", widthConstraint=",
", heightConstraint=",
", templateLoggerFactory=",
", rootDisposableContainer=",
", identifierProperty="
)
custom { method, _ ->
method.name == "toString"
}
}
internal val autoRepeatFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")

View File

@@ -11,6 +11,7 @@ import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
@@ -207,6 +208,26 @@ fun MutableMethod.injectHideViewCall(
"invoke-static { v$viewRegister }, $classDescriptor->$targetMethod(Landroid/view/View;)V",
)
/**
* Inserts instructions at a given index, using the existing control flow label at that index.
* Inserted instructions can have it's own control flow labels as well.
*
* Effectively this changes the code from:
* :label
* (original code)
*
* Into:
* :label
* (patch code)
* (original code)
*/
// TODO: delete this on next major version bump.
fun MutableMethod.addInstructionsAtControlFlowLabel(
insertIndex: Int,
instructions: String
) = addInstructionsAtControlFlowLabel(insertIndex, instructions, *arrayOf<ExternalLabel>())
/**
* Inserts instructions at a given index, using the existing control flow label at that index.
* Inserted instructions can have it's own control flow labels as well.
@@ -223,13 +244,14 @@ fun MutableMethod.injectHideViewCall(
fun MutableMethod.addInstructionsAtControlFlowLabel(
insertIndex: Int,
instructions: String,
vararg externalLabels: ExternalLabel
) {
// Duplicate original instruction and add to +1 index.
addInstruction(insertIndex + 1, getInstruction(insertIndex))
// Add patch code at same index as duplicated instruction,
// so it uses the original instruction control flow label.
addInstructionsWithLabels(insertIndex + 1, instructions)
addInstructionsWithLabels(insertIndex + 1, instructions, *externalLabels)
// Remove original non duplicated instruction.
removeInstruction(insertIndex)

View File

@@ -460,15 +460,29 @@ Säädä äänenvoimakkuutta pyyhkäisemällä pystysuoraan näytön oikealta pu
<string name="revanced_swipe_lowest_value_enable_auto_brightness_summary_on">Automaattinen kirkkaus otetaan käyttöön pyyhkäisemällä alhaisimpaan arvoon</string>
<string name="revanced_swipe_lowest_value_enable_auto_brightness_summary_off">Pienimpään arvoon alas pyyhkäiseminen ei ota käyttöön automaattista kirkkautta</string>
<string name="revanced_swipe_lowest_value_enable_auto_brightness_overlay_text">Automaattinen</string>
<string name="revanced_swipe_overlay_timeout_title">Pyyhkäisyikkunan aikakatkaisu</string>
<string name="revanced_swipe_overlay_timeout_title">Pyyhkäisypeittokuvan aikakatkaisu</string>
<string name="revanced_swipe_overlay_timeout_summary">Kuinka monta millisekuntia ikkuna on näkyvissä</string>
<string name="revanced_swipe_overlay_background_opacity_title">Pyyhkäisypeittokuvan taustan läpinäkymättömyys</string>
<string name="revanced_swipe_overlay_background_opacity_summary">Läpinäkymättömyysarvo 0100 välillä</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">Pyyhkäisyn läpinäkymättömyyden on oltava välillä 0100</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">Pyyhkäisypeittokuvan läpinäkymättömyyden tulee olla 0100 välillä</string>
<string name="revanced_swipe_overlay_progress_color_title">Pyyhkäisypeittokuvan edistymispalkin väri</string>
<string name="revanced_swipe_overlay_progress_color_summary">Äänenvoimakkuuden ja kirkkauden säätimien edistymispalkin väri</string>
<string name="revanced_swipe_overlay_progress_color_invalid_toast">Virheellinen edistymispalkin väri</string>
<string name="revanced_swipe_text_overlay_size_title">Pyyhkäisypeittokuvan tekstin koko</string>
<string name="revanced_swipe_text_overlay_size_summary">Pyyhkäisypeittokuvan tekstin koko 130 välillä</string>
<string name="revanced_swipe_text_overlay_size_invalid_toast">Tekstin koon tulee olla 130 välillä</string>
<string name="revanced_swipe_threshold_title">Pyyhkäisyn kynnysraja</string>
<string name="revanced_swipe_threshold_summary">Pyyhkäisyä varten tarvittavan kynnyksen määrä</string>
<string name="revanced_swipe_volume_sensitivity_title">Äänenvoimakkuuden pyyhkäisyn herkkyys</string>
<string name="revanced_swipe_volume_sensitivity_summary">Kuinka paljon äänenvoimakkuus muuttuu pyyhkäisyä kohden</string>
<string name="revanced_swipe_overlay_style_title">Pyyhkäisypeittokuvan tyyli</string>
<string name="revanced_swipe_overlay_style_entry_1">Vaakasuuntainen peittokuva</string>
<string name="revanced_swipe_overlay_style_entry_2">Vaakasuuntainen peittokuva (minimaalinen ylhäällä)</string>
<string name="revanced_swipe_overlay_style_entry_3">Vaakasuuntainen peittokuva (minimaalinen keskellä)</string>
<string name="revanced_swipe_overlay_style_entry_4">Pyöreä peittokuva</string>
<string name="revanced_swipe_overlay_style_entry_5">Pyöreä peittokuva (minimaalinen)</string>
<string name="revanced_swipe_overlay_style_entry_6">Pystysuuntainen peittokuva</string>
<string name="revanced_swipe_overlay_style_entry_7">Pystysuuntainen peittokuva (minimaalinen)</string>
<string name="revanced_swipe_change_video_title">Ota videon vaihto pyyhkäisemällä käyttöön</string>
<string name="revanced_swipe_change_video_summary_on">Pyyhkäisemällä kokoruututilassa siirrytään seuraavaan/edelliseen videoon</string>
<string name="revanced_swipe_change_video_summary_off">Pyyhkäisemällä kokoruututilassa ei siirrytä seuraavaan/edelliseen videoon</string>
@@ -802,7 +816,7 @@ Asetukset → Toisto → Toista seuraava video automaattisesti"</string>
<patch id="layout.player.overlay.customPlayerOverlayOpacityResourcePatch">
<string name="revanced_player_overlay_opacity_title">Soittimen peittokuvan läpinäkymättömyys</string>
<string name="revanced_player_overlay_opacity_summary">Läpinäkymättömyysarvo välillä 0100, jossa 0 on läpinäkyvä</string>
<string name="revanced_player_overlay_opacity_invalid_toast">Soittimen peittokuvan läpinäkymättömyyden on oltava välillä 0100</string>
<string name="revanced_player_overlay_opacity_invalid_toast">Soittimen peittokuvan läpinäkymättömyyden tulee olla 0100 välillä</string>
</patch>
<patch id="layout.returnyoutubedislike.returnYouTubeDislikePatch">
<!-- 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. -->
@@ -903,7 +917,7 @@ Tämä ominaisuus toimii parhaiten, kun videon laatu on 720p tai alhaisempi ja k
<string name="revanced_sb_enable_create_segment_sum_off">Luo uusi osio -painiketta ei näytetä</string>
<string name="revanced_sb_general_adjusting">Uuden osion ajoituksen säätö</string>
<string name="revanced_sb_general_adjusting_sum">Kuinka monta millisekuntia ajansäätöpainikkeet liikkuvat uusia osioita luotaessa</string>
<string name="revanced_sb_general_adjusting_invalid">Arvon on oltava positiivinen luku</string>
<string name="revanced_sb_general_adjusting_invalid">Arvon tulee olla positiivinen luku</string>
<string name="revanced_sb_guidelines_preference_title">Näytä ohjeet</string>
<string name="revanced_sb_guidelines_preference_sum">Ohjeet sisältävät sääntöjä ja vinkkejä uusien osioiden luomiseen</string>
<string name="revanced_sb_guidelines_popup_title">Noudata ohjeita</string>
@@ -1201,10 +1215,10 @@ Pyyhkäise laajentaaksesi tai sulkeaksesi"</string>
<string name="revanced_miniplayer_hide_rewind_forward_summary_off">Eteenpäin ja taaksepäin näytetään</string>
<string name="revanced_miniplayer_width_dip_title">Aloituskoko</string>
<string name="revanced_miniplayer_width_dip_summary">Alkuperäinen näyttökoko pikseleinä</string>
<string name="revanced_miniplayer_width_dip_invalid_toast">Pikselikoon on oltava välillä %1$s ja %2$s</string>
<string name="revanced_miniplayer_width_dip_invalid_toast">Pikselikoon tulee olla %1$s ja %2$s välillä</string>
<string name="revanced_miniplayer_opacity_title">Peittokuvan läpinäkymättömyys</string>
<string name="revanced_miniplayer_opacity_summary">Läpinäkymättömyysarvo välillä 0100, jossa 0 on läpinäkyvä</string>
<string name="revanced_miniplayer_opacity_invalid_toast">Minisoittimen peittokuvan läpinäkymättömyyden on oltava välillä 0100</string>
<string name="revanced_miniplayer_opacity_invalid_toast">Minisoittimen peittokuvan läpinäkymättömyyden tulee olla 0100 välillä</string>
</patch>
<patch id="layout.theme.themePatch">
<string name="revanced_gradient_loading_screen_title">Ota liukuvärillinen latausruutu käyttöön</string>
@@ -1295,6 +1309,7 @@ Tämä voi avata korkealaatuisemmat videot"</string>
<string name="microg_settings_summary">GmsCoren asetukset</string>
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch">
<string name="microg_offline_account_login_error">Jos olet äskettäin muuttanut tilisi kirjautumistietoja, poista ja asenna MicroG uudelleen.</string>
</patch>
<patch id="misc.links.bypassURLRedirectsPatch">
<string name="revanced_bypass_url_redirects_title">Ohita URL-osoitteen uudelleenohjaukset</string>
@@ -1352,7 +1367,7 @@ Tämä voi avata korkealaatuisemmat videot"</string>
<string name="revanced_custom_speed_menu_summary_off">Omaa nopeusvalikkoa ei näytetä</string>
<string name="revanced_custom_playback_speeds_title">Omat toistonopeudet</string>
<string name="revanced_custom_playback_speeds_summary">Lisää tai muuta omia toistonopeuksia</string>
<string name="revanced_custom_playback_speeds_invalid">Omien nopeuksien on oltava alle %s</string>
<string name="revanced_custom_playback_speeds_invalid">Omien nopeuksien tulee olla alle %s</string>
<string name="revanced_custom_playback_speeds_parse_exception">Virheelliset omat toistonopeudet</string>
<string name="revanced_custom_playback_speeds_auto">Automaattinen</string>
<string name="revanced_speed_tap_and_hold_title">Oma napauta ja pidä pohjassa -nopeus</string>

View File

@@ -64,7 +64,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. -->
<string name="gms_core_toast_not_installed_message">MicroG GmsCore がインストールされていません。インストールしてください。</string>
<string name="gms_core_dialog_title">必ず実行してください</string>
<string name="gms_core_dialog_not_whitelisted_not_allowed_in_background_message">"MicroG GmsCore はバックグラウンドで実行するための権限を持っていません。
<string name="gms_core_dialog_not_whitelisted_not_allowed_in_background_message">"MicroG GmsCore はバックグラウンドで動くための権限を持っていません。
下記ウェブサイト「Don't kill my app」の携帯電話メーカー別のガイドに従い、MicroG GmsCore に対するデバイスの設定を変更してください。
@@ -80,7 +80,7 @@ MicroG GmsCore に対する電池の最適化を無効にしても、バッテ
</app>
<app id="youtube">
<patch id="misc.settings.settingsPatch">
<string name="revanced_settings_screen_00_about_title">このアプリについて</string>
<string name="revanced_settings_screen_00_about_title">ReVanced について</string>
<string name="revanced_settings_screen_01_ads_title">広告</string>
<string name="revanced_settings_screen_02_alt_thumbnails_title">代替サムネイル</string>
<string name="revanced_settings_screen_03_feed_title">フィード</string>
@@ -854,7 +854,7 @@ MicroG GmsCore に対する電池の最適化を無効にしても、バッテ
<string name="revanced_ryd_toast_on_connection_error_title">API 利用不可時にトーストを表示</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">Return YouTube Dislike が利用できない場合、トースト ポップアップが表示されます</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">Return YouTube Dislike が利用できない場合でもトースト ポップアップは表示されません</string>
<string name="revanced_ryd_about">このアプリについて</string>
<string name="revanced_ryd_about">Return YouTube Dislike について</string>
<string name="revanced_ryd_attribution_summary">このデータはReturn YouTube Dislike APIによって提供されています。詳細はここをタップしてください</string>
<!-- Statistic strings are shown in the settings only when ReVanced debug mode is enabled. Typical users will never see these. -->
<string name="revanced_ryd_statistics_category_title">このデバイスでのReturnYouTubeDislike API 統計情報</string>
@@ -1087,7 +1087,7 @@ MicroG GmsCore に対する電池の最適化を無効にしても、バッテ
<string name="revanced_sb_color_invalid">色の値が無効です</string>
<string name="revanced_sb_reset_color">色をリセット</string>
<string name="revanced_sb_reset">リセット</string>
<string name="revanced_sb_about">このアプリについて</string>
<string name="revanced_sb_about">SponsorBlock について</string>
<string name="revanced_sb_about_api_sum">SponsorBlock APIによって提供されるデータです。詳細はこちらをタップしてください。</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
@@ -1311,7 +1311,7 @@ Automotive レイアウト
<string name="microg_settings_summary">GmsCore の設定</string>
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch">
<string name="microg_offline_account_login_error">最近アカウントのログイン情報を変更した場合は、MicroGをアンインストールして再インストールしてください。</string>
<string name="microg_offline_account_login_error">最近アカウントのログイン情報を変更した場合は、MicroG をアンインストールして再インストールしてください。</string>
</patch>
<patch id="misc.links.bypassURLRedirectsPatch">
<string name="revanced_bypass_url_redirects_title">URL リダイレクトを回避する</string>

View File

@@ -712,8 +712,8 @@ Da biste prikazali meni „Audio snimak”, promenite opciju „Lažirani video
<string name="revanced_hide_shorts_paused_overlay_buttons_summary_on">Dugmad u plejeru pri pauzi su skrivena</string>
<string name="revanced_hide_shorts_paused_overlay_buttons_summary_off">Dugmad u plejeru pri pauzi su prikazana</string>
<string name="revanced_hide_shorts_shop_button_title">Sakrij dugme „Prodavnica”</string>
<string name="revanced_hide_shorts_shop_button_summary_on">Dugme „Kupovina” je skriveno</string>
<string name="revanced_hide_shorts_shop_button_summary_off">Dugme „Kupovina” je prikazano</string>
<string name="revanced_hide_shorts_shop_button_summary_on">Dugme „Prodavnica” je skriveno</string>
<string name="revanced_hide_shorts_shop_button_summary_off">Dugme „Prodavnica” je prikazano</string>
<string name="revanced_hide_shorts_super_thanks_button_title">Sakrij dugme za kupovinu „Superhvala”</string>
<string name="revanced_hide_shorts_super_thanks_button_summary_on">Dugme „Superhvala” je skriveno</string>
<string name="revanced_hide_shorts_super_thanks_button_summary_off">Dugme „Superhvala” je prikazano</string>
@@ -733,8 +733,8 @@ Da biste prikazali meni „Audio snimak”, promenite opciju „Lažirani video
<string name="revanced_hide_shorts_upcoming_button_summary_on">Dugme „Predstojeće” je skriveno</string>
<string name="revanced_hide_shorts_upcoming_button_summary_off">Dugme „Predstojeće” je prikazano</string>
<string name="revanced_hide_shorts_green_screen_button_title">Sakrij dugme „Zeleni ekran”</string>
<string name="revanced_hide_shorts_green_screen_button_summary_on">Dugme „Green Screen” je skriveno</string>
<string name="revanced_hide_shorts_green_screen_button_summary_off">Dugme „Green Screen” je prikazano</string>
<string name="revanced_hide_shorts_green_screen_button_summary_on">Dugme „Zeleni ekran” je skriveno</string>
<string name="revanced_hide_shorts_green_screen_button_summary_off">Dugme „Zeleni ekran” je prikazano</string>
<string name="revanced_hide_shorts_hashtag_button_title">Sakrij dugme heš-oznake</string>
<string name="revanced_hide_shorts_hashtag_button_summary_on">Dugme heš-oznake je skriveno</string>
<string name="revanced_hide_shorts_hashtag_button_summary_off">Dugme heš-oznake je prikazano</string>

View File

@@ -712,8 +712,8 @@ Second \"item\" text"</string>
<string name="revanced_hide_shorts_paused_overlay_buttons_summary_on">Дугмад у плејеру при паузи су скривена</string>
<string name="revanced_hide_shorts_paused_overlay_buttons_summary_off">Дугмад у плејеру при паузи су приказана</string>
<string name="revanced_hide_shorts_shop_button_title">Сакриј дугме „Продавница”</string>
<string name="revanced_hide_shorts_shop_button_summary_on">Дугме „Куповина” је скривено</string>
<string name="revanced_hide_shorts_shop_button_summary_off">Дугме „Куповина” је приказано</string>
<string name="revanced_hide_shorts_shop_button_summary_on">Дугме „Продавница” је скривено</string>
<string name="revanced_hide_shorts_shop_button_summary_off">Дугме „Продавница” је приказано</string>
<string name="revanced_hide_shorts_super_thanks_button_title">Сакриј дугме за куповину „Суперхвала”</string>
<string name="revanced_hide_shorts_super_thanks_button_summary_on">Дугме „Суперхвала” је скривено</string>
<string name="revanced_hide_shorts_super_thanks_button_summary_off">Дугме „Суперхвала” је приказано</string>
@@ -733,8 +733,8 @@ Second \"item\" text"</string>
<string name="revanced_hide_shorts_upcoming_button_summary_on">Дугме „Предстојеће” је скривено</string>
<string name="revanced_hide_shorts_upcoming_button_summary_off">Дугме „Предстојеће” је приказано</string>
<string name="revanced_hide_shorts_green_screen_button_title">Сакриј дугме „Зелени екран”</string>
<string name="revanced_hide_shorts_green_screen_button_summary_on">Дугме „Green Screenје скривено</string>
<string name="revanced_hide_shorts_green_screen_button_summary_off">Дугме „Green Screenје приказано</string>
<string name="revanced_hide_shorts_green_screen_button_summary_on">Дугме „Зелени екранје скривено</string>
<string name="revanced_hide_shorts_green_screen_button_summary_off">Дугме „Зелени екранје приказано</string>
<string name="revanced_hide_shorts_hashtag_button_title">Сакриј дугме хеш-ознаке</string>
<string name="revanced_hide_shorts_hashtag_button_summary_on">Дугме хеш-ознаке је скривено</string>
<string name="revanced_hide_shorts_hashtag_button_summary_off">Дугме хеш-ознаке је приказано</string>

View File

@@ -1311,7 +1311,7 @@ Second \"item\" text"</string>
<string name="microg_settings_summary">Відкрити GmsCore для налаштування та входу в обліковий запис Google</string>
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch">
<string name="microg_offline_account_login_error">Якщо ви нещодавно змінили дані для входу у свій обліковий запис, видаліть і повторно встановіть MicroG.</string>
<string name="microg_offline_account_login_error">Якщо Ви нещодавно змінили дані для входу у свій обліковий запис, видаліть і повторно встановіть MicroG.</string>
</patch>
<patch id="misc.links.bypassURLRedirectsPatch">
<string name="revanced_bypass_url_redirects_title">Обхід URL переадресацій</string>

View File

@@ -24,7 +24,7 @@ Second \"item\" text"</string>
<string name="revanced_check_environment_failed_title">Kiểm tra thất bại</string>
<string name="revanced_check_environment_dialog_open_official_source_button">Mở trang web chính thức</string>
<string name="revanced_check_environment_dialog_ignore_button">Bỏ qua</string>
<string name="revanced_check_environment_failed_message">&lt;h5&gt;Ứng dụng này xem ra không phải do bạn tự vá.&lt;/h5&gt;&lt;br&gt;Ứng dụng này có thể không hoạt động chính xác, &lt;b&gt;tiềm ẩn rủi ro hoặc thậm chí gây nguy hiểm khi sử dụng&lt;/b&gt;.&lt;br&gt;&lt;br&gt;Những kiểm tra này ngụ ý rằng ứng dụng được vá sẵn hoặc lấy từ nguồn khác;&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;Chúng tôi khuyến nghị bạn nên &lt;b&gt;gỡ cài đặt ứng này và tự vá lại&lt;/b&gt; để đảm bảo bạn đang dùng một ứng dụng an toàn và hợp lệ.&lt;p&gt;&lt;br&gt;Cảnh báo này sẽ chỉ hiện hai lần, hãy cân nhắc trước khi bỏ qua.</string>
<string name="revanced_check_environment_failed_message">&lt;h5&gt;Ứng dụng này xem ra không phải do bạn tự vá.&lt;/h5&gt;&lt;br&gt;Ứng dụng này có thể không hoạt động chính xác, &lt;b&gt;tiềm ẩn rủi ro hoặc thậm chí gây nguy hiểm khi sử dụng&lt;/b&gt;.&lt;br&gt;&lt;br&gt;Những kiểm tra dưới đây cho thấy rằng ứng dụng được vá sẵn hoặc lấy từ nguồn khác;&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;Chúng tôi khuyến nghị bạn nên &lt;b&gt;gỡ cài đặt ứng này và tự vá lại&lt;/b&gt; để đảm bảo bạn đang dùng một ứng dụng an toàn và hợp lệ.&lt;p&gt;&lt;br&gt;Cảnh báo này sẽ chỉ hiện hai lần, hãy cân nhắc trước khi bỏ qua.</string>
<string name="revanced_check_environment_not_same_patching_device">Đã vá trên một thiết bị khác</string>
<string name="revanced_check_environment_manager_not_expected_installer">Không được cài đặt bởi ReVanced Manager</string>
<string name="revanced_check_environment_not_near_patch_time">Đã vá hơn 10 phút trước</string>
@@ -315,7 +315,7 @@ Nếu cài đặt này được bật và Doodle đang hiển thị tại khu v
<string name="revanced_hide_keyword_content_search_title">Ẩn kết quả tìm kiếm bằng từ khóa</string>
<string name="revanced_hide_keyword_content_search_summary_on">Kết quả tìm kiếm đã được lọc bằng từ khóa</string>
<string name="revanced_hide_keyword_content_search_summary_off">Kết quả tìm kiếm không được lọc bằng từ khóa</string>
<string name="revanced_hide_keyword_content_subscriptions_title">Ẩn video đăng ký bằng từ khóa</string>
<string name="revanced_hide_keyword_content_subscriptions_title">Ẩn video kênh đăng ký bằng từ khóa</string>
<string name="revanced_hide_keyword_content_subscriptions_summary_on">Video ở thẻ đăng ký đã được lọc bằng từ khóa</string>
<string name="revanced_hide_keyword_content_subscriptions_summary_off">Video ở thẻ đăng ký không được lọc bằng từ khóa</string>
<string name="revanced_hide_keyword_content_phrases_title">Từ khóa để ẩn</string>
@@ -399,7 +399,7 @@ Tính năng này chỉ khả dụng cho các thiết bị cũ hơn"</string>
<patch id="interaction.copyvideourl.copyVideoUrlResourcePatch">
<string name="revanced_share_copy_url_success">Đã chép URL vào bảng nhớ tạm</string>
<string name="revanced_share_copy_url_timestamp_success">Đã chép URL với dấu thời gian</string>
<string name="revanced_copy_video_url_title">Hiện nút sao chép url video</string>
<string name="revanced_copy_video_url_title">Hiện nút sao chép URL video</string>
<string name="revanced_copy_video_url_summary_on">Nút được hiển thị. Chạm để sao chép video URL. Chạm và giữ để sao chép với dấu thời gian</string>
<string name="revanced_copy_video_url_summary_off">Nút không được hiển thị</string>
<string name="revanced_copy_video_url_timestamp_title">Hiện nút sao chép URL với dấu thời gian</string>
@@ -416,7 +416,7 @@ Tính năng này chỉ khả dụng cho các thiết bị cũ hơn"</string>
<string name="revanced_external_downloader_screen_title">Tải xuống bên ngoài</string>
<string name="revanced_external_downloader_screen_summary">Các thiết lập trình tải xuống bên ngoài</string>
<string name="revanced_external_downloader_title">Hiện nút tải xuống bên ngoài</string>
<string name="revanced_external_downloader_summary_on">Nút tải xuống trong trình phát được hiển thị</string>
<string name="revanced_external_downloader_summary_on">Nút tải xuống trong trình phát đã được hiển thị</string>
<string name="revanced_external_downloader_summary_off">Nút tải xuống trong trình phát không được hiển thị</string>
<!-- 'download action button' should be translated using the same wording as the translation of 'revanced_hide_download_button_title' -->
<string name="revanced_external_downloader_action_button_title">Thay thế nút hành động Tải xuống</string>
@@ -446,12 +446,12 @@ Tính năng này chỉ khả dụng cho các thiết bị cũ hơn"</string>
<string name="revanced_swipe_volume_summary_on">"Đã bật vuốt âm lượng toàn màn hình
Điều chỉnh âm lượng bằng cách vuốt dọc ở bên phải màn hình"</string>
<string name="revanced_swipe_volume_summary_off">Vuốt âm lượng được tắt</string>
<string name="revanced_swipe_press_to_engage_title">Bật cử chỉ nhấn-để-vuốt</string>
<string name="revanced_swipe_press_to_engage_summary_on">Nhấn-để-vuốt đã bật</string>
<string name="revanced_swipe_press_to_engage_summary_off">Nhấn-để-vuốt đã tắt</string>
<string name="revanced_swipe_volume_summary_off">Vuốt âm lượng toàn màn hình đã tắt</string>
<string name="revanced_swipe_press_to_engage_title">Bật cử chỉ nhấn giữ để vuốt</string>
<string name="revanced_swipe_press_to_engage_summary_on">Nhấn giữ để vuốt đã bật</string>
<string name="revanced_swipe_press_to_engage_summary_off">Nhấn giữ để vuốt đã tắt</string>
<string name="revanced_swipe_haptic_feedback_title">Bật phản hồi xúc giác</string>
<string name="revanced_swipe_haptic_feedback_summary_on">Phản hồi xúc giác đã bật</string>
<string name="revanced_swipe_haptic_feedback_summary_on">Phản hồi xúc giác đã được bật</string>
<string name="revanced_swipe_haptic_feedback_summary_off">Phản hồi xúc giác đã tắt</string>
<string name="revanced_swipe_save_and_restore_brightness_title">Lưu và khôi phục độ sáng</string>
<string name="revanced_swipe_save_and_restore_brightness_summary_on">Lưu và khôi phục độ sáng khi thoát hoặc vào toàn màn hình</string>
@@ -496,8 +496,8 @@ Tính năng này chỉ khả dụng cho các thiết bị cũ hơn"</string>
<string name="revanced_hide_buttons_screen_title">Các nút hành động</string>
<string name="revanced_hide_buttons_screen_summary">Ẩn hoặc hiện nút dưới video</string>
<string name="revanced_disable_like_subscribe_glow_title">Tắt hiệu ứng phát sáng nút Thích và Đăng ký</string>
<string name="revanced_disable_like_subscribe_glow_summary_on">Nút Thích và Đăng ký sẽ không phát sáng khi được đề cập đến</string>
<string name="revanced_disable_like_subscribe_glow_summary_off">Nút Thích và Đăng ký sẽ phát sáng khi được đề cập đến</string>
<string name="revanced_disable_like_subscribe_glow_summary_on">Nút Thích và Đăng ký sẽ không phát sáng khi được tương tác</string>
<string name="revanced_disable_like_subscribe_glow_summary_off">Nút Thích và Đăng ký sẽ phát sáng khi được tương tác</string>
<string name="revanced_hide_like_dislike_button_title">Ẩn Thích và Không thích</string>
<string name="revanced_hide_like_dislike_button_summary_on">Các nút Thích và Không thích đã bị ẩn</string>
<string name="revanced_hide_like_dislike_button_summary_off">Các nút Thích và Không thích được hiển thị</string>
@@ -525,8 +525,8 @@ Tính năng này chỉ khả dụng cho các thiết bị cũ hơn"</string>
<!-- 'Ask' should be translated with the same localized wording that YouTube displays.
Button only shows if the user ip is from specific region such as the USA or EU. -->
<string name="revanced_hide_ask_button_title">Ẩn Hỏi</string>
<string name="revanced_hide_ask_button_summary_on">Nút Hỏi đã bị ẩn</string>
<string name="revanced_hide_ask_button_summary_off">Nút Hỏi được hiển thị</string>
<string name="revanced_hide_ask_button_summary_on">Nút hỏi đã bị ẩn</string>
<string name="revanced_hide_ask_button_summary_off">Nút hỏi được hiển thị</string>
<!-- 'Clip' should be translated with the same localized wording that YouTube displays. -->
<string name="revanced_hide_clip_button_title">Ẩn Tạo đoạn video</string>
<string name="revanced_hide_clip_button_summary_on">Nút tạo đoạn video đã bị ẩn</string>
@@ -538,26 +538,26 @@ Tính năng này chỉ khả dụng cho các thiết bị cũ hơn"</string>
</patch>
<patch id="layout.buttons.navigation.navigationButtonsPatch">
<string name="revanced_navigation_buttons_screen_title">Các nút điều hướng</string>
<string name="revanced_navigation_buttons_screen_summary">Ẩn hoặc hiện các nút ở thanh điều hướng</string>
<string name="revanced_navigation_buttons_screen_summary">Ẩn hoặc thay đổi các nút ở thanh điều hướng</string>
<!-- 'Home' should be translated using the same localized wording YouTube displays for the tab. -->
<string name="revanced_hide_home_button_title">Ẩn Trang chính</string>
<string name="revanced_hide_home_button_summary_on">Nút trang chính đã bị ẩn</string>
<string name="revanced_hide_home_button_summary_off">Nút trang chính được hiển thị</string>
<string name="revanced_hide_home_button_title">Ẩn Trang ch</string>
<string name="revanced_hide_home_button_summary_on">Nút trang ch đã bị ẩn</string>
<string name="revanced_hide_home_button_summary_off">Nút trang ch được hiển thị</string>
<!-- 'Shorts' should be translated using the same localized wording YouTube displays the tab. -->
<string name="revanced_hide_shorts_button_title">Ẩn Shorts</string>
<string name="revanced_hide_shorts_button_summary_on">Nút Shorts đã bị ẩn</string>
<string name="revanced_hide_shorts_button_summary_off">Nút Shorts được hiển thị</string>
<!-- The Create button has no display name. Translate normally. -->
<string name="revanced_hide_create_button_title">Ẩn Tạo mới</string>
<string name="revanced_hide_create_button_title">Ẩn Tạo</string>
<string name="revanced_hide_create_button_summary_on">Nút tạo đã bị ẩn</string>
<string name="revanced_hide_create_button_summary_off">Nút tạo được hiển thị</string>
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays the tab. -->
<string name="revanced_hide_subscriptions_button_title">Ẩn Đăng ký</string>
<string name="revanced_hide_subscriptions_button_summary_on">Nút đăng ký đã bị ẩn</string>
<string name="revanced_hide_subscriptions_button_summary_off">Nút Đăng ký được hiển thị</string>
<string name="revanced_hide_subscriptions_button_title">Ẩn Kênh đăng ký</string>
<string name="revanced_hide_subscriptions_button_summary_on">Nút kênh đăng ký đã bị ẩn</string>
<string name="revanced_hide_subscriptions_button_summary_off">Nút kênh đăng ký được hiển thị</string>
<string name="revanced_hide_notifications_button_title">Ẩn Thông báo</string>
<string name="revanced_hide_notifications_button_summary_on">Nút Thông báo đã bị ẩn</string>
<string name="revanced_hide_notifications_button_summary_off">Nút Thông báo được hiển thị</string>
<string name="revanced_hide_notifications_button_summary_on">Nút thông báo đã bị ẩn</string>
<string name="revanced_hide_notifications_button_summary_off">Nút thông báo được hiển thị</string>
<!-- 'Notifications' should be translated using the same localized wording YouTube displays the tab. -->
<string name="revanced_switch_create_with_notifications_button_title">Chuyển vị nút Tạo với nút Thông báo</string>
<string name="revanced_switch_create_with_notifications_button_summary_on">"Nút tạo được chuyển đổi với nút Thông báo
@@ -577,7 +577,7 @@ Nếu việc thay đổi cài đặt này không có hiệu lực, hãy thử ch
<string name="revanced_disable_translucent_navigation_bar_light_title">Vô hiệu hóa thanh điều hướng trong suốt ở chế độ sáng</string>
<string name="revanced_disable_translucent_navigation_bar_light_summary_on">Thanh điều hướng ở chế độ sáng không trong suốt</string>
<string name="revanced_disable_translucent_navigation_bar_light_summary_off">Thanh điều hướng ở chế độ sáng là đục hoặc trong mờ</string>
<string name="revanced_disable_translucent_navigation_bar_dark_title">Vô hiệu hoá thanh điều hướng trong mờ tối</string>
<string name="revanced_disable_translucent_navigation_bar_dark_title">Vô hiệu hoá thanh điều hướng trong chế độ tối</string>
<string name="revanced_disable_translucent_navigation_bar_dark_summary_on">Thanh điều hướng ở chế độ tối không trong suốt</string>
<string name="revanced_disable_translucent_navigation_bar_dark_summary_off">Thanh điều hướng ở chế độ tối là đục hoặc trong mờ</string>
</patch>
@@ -601,7 +601,7 @@ Nếu việc thay đổi cài đặt này không có hiệu lực, hãy thử ch
<string name="revanced_hide_player_flyout_loop_video_summary_on">Trình đơn lặp video đã bị ẩn</string>
<string name="revanced_hide_player_flyout_loop_video_summary_off">Trình đơn lặp video được hiển thị</string>
<!-- 'Ambient mode' should be translated using the same localized wording YouTube displays for the menu item. -->
<string name="revanced_hide_player_flyout_ambient_mode_title">Ẩn chế độ môi trường</string>
<string name="revanced_hide_player_flyout_ambient_mode_title">Ẩn Chế độ môi trường</string>
<string name="revanced_hide_player_flyout_ambient_mode_summary_on">Trình đơn chế độ môi trường đã bị ẩn</string>
<string name="revanced_hide_player_flyout_ambient_mode_summary_off">Trình đơn chế độ môi trường được hiển thị</string>
<string name="revanced_hide_player_flyout_stable_volume_title">Ẩn Âm lượng ổn định</string>
@@ -661,8 +661,8 @@ Nếu việc thay đổi cài đặt này không có hiệu lực, hãy thử ch
<string name="revanced_hide_endscreen_cards_summary_off">Thẻ kết thúc màn hình được hiển thị</string>
</patch>
<patch id="layout.hide.fullscreenambientmode.disableFullscreenAmbientModePatch">
<string name="revanced_disable_fullscreen_ambient_mode_title">Tắt chế độ môi trường khi toàn màn hình</string>
<string name="revanced_disable_fullscreen_ambient_mode_summary_on">Chế độ môi trường được tắt</string>
<string name="revanced_disable_fullscreen_ambient_mode_title">Tắt Chế độ môi trường khi toàn màn hình</string>
<string name="revanced_disable_fullscreen_ambient_mode_summary_on">Chế độ môi trường đã tắt</string>
<string name="revanced_disable_fullscreen_ambient_mode_summary_off">Chế độ môi trường được bật</string>
</patch>
<patch id="layout.hide.infocards.hideInfocardsResourcePatch">
@@ -687,12 +687,12 @@ Nếu việc thay đổi cài đặt này không có hiệu lực, hãy thử ch
<string name="revanced_shorts_player_screen_title">Trình phát Shorts</string>
<string name="revanced_shorts_player_screen_summary">Ẩn hoặc hiện các thành phần trong trình phát Shorts</string>
<!-- 'home' should be translated using the same localized wording YouTube displays for the home tab. -->
<string name="revanced_hide_shorts_home_title">Ẩn Shorts trong bảng tin trang chính</string>
<string name="revanced_hide_shorts_home_summary_on">Ẩn trong nguồn cấp dữ liệu trang chủ và video liên quan</string>
<string name="revanced_hide_shorts_home_summary_off">Hiện trong nguồn cấp dữ liệu trang chủ và video liên quan</string>
<string name="revanced_hide_shorts_home_title">Ẩn Shorts trong thẻ trang ch</string>
<string name="revanced_hide_shorts_home_summary_on">Ẩn trong thẻ trang chủ và video liên quan</string>
<string name="revanced_hide_shorts_home_summary_off">Hiện trong thẻ trang chủ và video liên quan</string>
<!-- 'subscription' should be translated using the same localized wording YouTube displays for the subscription tab. -->
<string name="revanced_hide_shorts_subscriptions_title">Ẩn Shorts trong bảng tin đăng ký</string>
<string name="revanced_hide_shorts_subscriptions_summary_on">Bị ẩn trong nguồn đăng ký</string>
<string name="revanced_hide_shorts_subscriptions_title">Ẩn Shorts trong thẻ kênh đăng ký</string>
<string name="revanced_hide_shorts_subscriptions_summary_on">Bị ẩn trong thẻ kênh đăng ký</string>
<string name="revanced_hide_shorts_subscriptions_summary_off">Được hiện trong nguồn đăng ký</string>
<string name="revanced_hide_shorts_search_title">Ẩn Shorts trong kết quả tìm kiếm</string>
<string name="revanced_hide_shorts_search_summary_on">Bị ẩn trong kết quả tìm kiếm</string>
@@ -806,10 +806,10 @@ Cài đặt → Phát → Tự động phát video tiếp theo"</string>
</patch>
<patch id="layout.player.fullscreen.exitFullscreenPatch">
<string name="revanced_exit_fullscreen_title">Thoát chế độ toàn màn hình khi kết thúc video</string>
<string name="revanced_exit_fullscreen_entry_1">Đã tắt</string>
<string name="revanced_exit_fullscreen_entry_2">Chân dung</string>
<string name="revanced_exit_fullscreen_entry_3">Phong cảnh</string>
<string name="revanced_exit_fullscreen_entry_4">Chân dung và phong cảnh</string>
<string name="revanced_exit_fullscreen_entry_1">Tắt</string>
<string name="revanced_exit_fullscreen_entry_2">Chế độ dọc</string>
<string name="revanced_exit_fullscreen_entry_3">Chế độ ngang</string>
<string name="revanced_exit_fullscreen_entry_4">Chế độ dọc và ngang</string>
</patch>
<patch id="layout.player.fullscreen.openVideosFullscreen">
<string name="revanced_open_videos_fullscreen_portrait_title">Mở video ở chế độ toàn màn hình dọc</string>
@@ -916,10 +916,10 @@ Tính năng này hoạt động tốt nhất với chất lượng video 720p tr
<string name="revanced_sb_general_time_without_sum_off">Thời lượng đầy đủ của video được hiện</string>
<string name="revanced_sb_create_segment_category">Tạo các phân đoạn mới</string>
<string name="revanced_sb_enable_create_segment">Hiện nút Tạo phân đoạn mới</string>
<string name="revanced_sb_enable_create_segment_sum_on">Nút tạo phân đoạn mới được hiển thị</string>
<string name="revanced_sb_enable_create_segment_sum_on">Nút tạo phân đoạn mới đã được hiển thị</string>
<string name="revanced_sb_enable_create_segment_sum_off">Nút tạo phân đoạn mới không được hiển thị</string>
<string name="revanced_sb_general_adjusting">Điều chỉnh bước tua của phân đoạn mới</string>
<string name="revanced_sb_general_adjusting_sum">Số mili-giây của các nút điều chỉnh thay đổi khi tạo phân đoạn mới</string>
<string name="revanced_sb_general_adjusting_sum">Số mili-giây các nút điều chỉnh thời gian sẽ tua khi tạo phân đoạn mới</string>
<string name="revanced_sb_general_adjusting_invalid">Giá trị phải là một số dương</string>
<string name="revanced_sb_guidelines_preference_title">Xem hướng dẫn</string>
<string name="revanced_sb_guidelines_preference_sum">Hướng dẫn bao gồm các quy tắc và mẹo về cách tạo phân đoạn mới</string>
@@ -1174,9 +1174,9 @@ Giới hạn: Sử dụng nút quay lại trên thanh công cụ có thể khôn
<string name="revanced_miniplayer_screen_title">Trình phát thu nhỏ</string>
<string name="revanced_miniplayer_screen_summary">Thay đổi kiểu trình phát thu nhỏ trong ứng dụng</string>
<string name="revanced_miniplayer_type_title">Loại trình phát thu nhỏ</string>
<string name="revanced_miniplayer_type_entry_0">Đã tắt</string>
<string name="revanced_miniplayer_type_entry_0">Tắt</string>
<string name="revanced_miniplayer_type_entry_1">Mặc định</string>
<string name="revanced_miniplayer_type_entry_2">Thu gọn</string>
<string name="revanced_miniplayer_type_entry_2">Tối giản</string>
<string name="revanced_miniplayer_type_entry_3">Máy tính bảng</string>
<string name="revanced_miniplayer_type_entry_4">Hiện đại 1</string>
<string name="revanced_miniplayer_type_entry_5">Hiện đại 2</string>
@@ -1185,22 +1185,22 @@ Giới hạn: Sử dụng nút quay lại trên thanh công cụ có thể khôn
<string name="revanced_miniplayer_rounded_corners_title">Bật góc bo tròn</string>
<string name="revanced_miniplayer_rounded_corners_summary_on">Góc được bo tròn</string>
<string name="revanced_miniplayer_rounded_corners_summary_off">Góc vuông</string>
<string name="revanced_miniplayer_double_tap_action_title">Bật nhấp đôi và chụm để thay đổi kích thước</string>
<string name="revanced_miniplayer_double_tap_action_summary_on">"Thao tác nhấn đúp và vuốt để thay đổi kích thước được bật
<string name="revanced_miniplayer_double_tap_action_title">Bật nhấp đúp và chụm để thay đổi kích thước</string>
<string name="revanced_miniplayer_double_tap_action_summary_on">"Thao tác nhấn đúp và chụm để thay đổi kích thước đã được bật
• Nhấn đúp để tăng kích thước trình phát nhỏ
• Nhấn đúp để tăng kích thước trình phát thu nhỏ
• Nhấn đúp lại để khôi phục kích thước ban đầu"</string>
<string name="revanced_miniplayer_double_tap_action_summary_off">Chạm đôi và chụm để thay đổi kích thước được tắt</string>
<string name="revanced_miniplayer_double_tap_action_summary_off">Chạm đôi và chụm để thay đổi kích thước đã tắt</string>
<string name="revanced_miniplayer_drag_and_drop_title">Bật kéo và thả</string>
<string name="revanced_miniplayer_drag_and_drop_summary_on">"Kéo và thả được bật
<string name="revanced_miniplayer_drag_and_drop_summary_on">"Kéo và thả đã được bật
Trình phát nhỏ có thể được kéo đến bất kỳ góc nào của màn hình"</string>
<string name="revanced_miniplayer_drag_and_drop_summary_off">Kéo và thả được tắt</string>
Trình phát thu nhỏ có thể được kéo đến bất kỳ góc nào của màn hình"</string>
<string name="revanced_miniplayer_drag_and_drop_summary_off">Kéo và thả đã tắt</string>
<string name="revanced_miniplayer_horizontal_drag_title">Bật cử chỉ kéo ngang</string>
<string name="revanced_miniplayer_horizontal_drag_summary_on">"Cử chỉ kéo ngang được bật
<string name="revanced_miniplayer_horizontal_drag_summary_on">"Cử chỉ kéo ngang đã được bật
Trình phát nhỏ có thể được kéo ra khỏi màn hình sang trái hoặc phải"</string>
<string name="revanced_miniplayer_horizontal_drag_summary_off">Cử chỉ kéo ngang được tắt</string>
Trình phát thu nhỏ có thể được kéo ra mép màn hình sang bên trái hoặc phải"</string>
<string name="revanced_miniplayer_horizontal_drag_summary_off">Cử chỉ kéo ngang đã tắt</string>
<string name="revanced_miniplayer_hide_overlay_buttons_title">Ẩn các nút lớp phủ</string>
<string name="revanced_miniplayer_hide_overlay_buttons_summary_on">Các nút lớp phủ đã bị ẩn</string>
<string name="revanced_miniplayer_hide_overlay_buttons_summary_off">Các nút lớp phủ được hiển thị</string>
@@ -1212,15 +1212,15 @@ Vuốt để mở rộng hoặc đóng"</string>
<string name="revanced_miniplayer_hide_subtext_title">Ẩn văn bản phụ</string>
<string name="revanced_miniplayer_hide_subtext_summary_on">Văn bản phụ đã bị ẩn</string>
<string name="revanced_miniplayer_hide_subtext_summary_off">Văn bản phụ được hiển thị</string>
<string name="revanced_miniplayer_hide_rewind_forward_title">Ẩn các nút bỏ quả đến tiếp và trước đó </string>
<string name="revanced_miniplayer_hide_rewind_forward_summary_on">Các nút bỏ quả đến tiếp và trước đó đã bị ẩn</string>
<string name="revanced_miniplayer_hide_rewind_forward_summary_off">Các nút bỏ quả đến tiếp và trước đó được hiển thị</string>
<string name="revanced_miniplayer_hide_rewind_forward_title">Ẩn các nút tua nhanh và tua lại</string>
<string name="revanced_miniplayer_hide_rewind_forward_summary_on">Các nút tua nhanh và tua lại đã bị ẩn</string>
<string name="revanced_miniplayer_hide_rewind_forward_summary_off">Các nút tua nhanh và tua lại được hiển thị</string>
<string name="revanced_miniplayer_width_dip_title">Kích thước ban đầu</string>
<string name="revanced_miniplayer_width_dip_summary">Kích thước ban đầu trên màn hình, bằng pixel</string>
<string name="revanced_miniplayer_width_dip_invalid_toast">Pixel phải nằm giữa %1$s và %2$s</string>
<string name="revanced_miniplayer_opacity_title">Độ mờ lớp phủ</string>
<string name="revanced_miniplayer_opacity_summary">Giá trị độ mờ của lớp phủ trình phát trong khoảng từ 0 đến 100, trong đó 0 là trong suốt</string>
<string name="revanced_miniplayer_opacity_invalid_toast">Độ phủ mờ trình phát thu nhỏ phải nằm giữa 0-100</string>
<string name="revanced_miniplayer_opacity_invalid_toast">Độ mờ lớp phủ trình phát thu nhỏ phải nằm trong khoảng từ 0 đến 100</string>
</patch>
<patch id="layout.theme.themePatch">
<string name="revanced_gradient_loading_screen_title">Bật màn hình tải màu dốc</string>
@@ -1292,9 +1292,9 @@ Nhấn vào đây để tìm hiểu thêm về DeArrow"</string>
<string name="revanced_check_watch_history_domain_name_dialog_ignore">Không hiện lại</string>
</patch>
<patch id="misc.autorepeat.autoRepeatPatch">
<string name="revanced_auto_repeat_title">Bật tự phát lại</string>
<string name="revanced_auto_repeat_summary_on">Tự phát lại được bật</string>
<string name="revanced_auto_repeat_summary_off">Tự phát lại được tắt</string>
<string name="revanced_auto_repeat_title">Bật tự phát lặp lại</string>
<string name="revanced_auto_repeat_summary_on">Tự phát lặp lại đã được bật</string>
<string name="revanced_auto_repeat_summary_off">Tự phát lặp lại đã tắt</string>
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
<string name="revanced_spoof_device_dimensions_title">Giả mạo kích thước thiết bị</string>
@@ -1325,7 +1325,7 @@ Bật tính năng này có thể mở khóa chất lượng video cao hơn"</str
</patch>
<patch id="misc.privacy.removeTrackingQueryParameterPatch">
<string name="revanced_remove_tracking_query_parameter_title">Loại bỏ tham số truy vấn theo dõi</string>
<string name="revanced_remove_tracking_query_parameter_summary_on">Tham số truy vấn theo dõi được loại bỏ khỏi liên kết</string>
<string name="revanced_remove_tracking_query_parameter_summary_on">Tham số truy vấn theo dõi đã bị loại bỏ khỏi liên kết</string>
<string name="revanced_remove_tracking_query_parameter_summary_off">Tham số truy vấn theo dõi không được loại bỏ khỏi liên kết</string>
</patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
@@ -1373,7 +1373,7 @@ Bật tính năng này có thể mở khóa chất lượng video cao hơn"</str
<string name="revanced_custom_playback_speeds_parse_exception">Tốc độ phát lại tùy chỉnh không hợp lệ</string>
<string name="revanced_custom_playback_speeds_auto">Tự động</string>
<string name="revanced_speed_tap_and_hold_title">Tốc độ chạm và giữ tùy chỉnh</string>
<string name="revanced_speed_tap_and_hold_summary">Tốc độ phát lại giữa 0-8</string>
<string name="revanced_speed_tap_and_hold_summary">Tốc độ phát từ 0 đến 8</string>
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">
<string name="revanced_remember_playback_speed_last_selected_title">Nhớ các thay đổi tốc độ phát</string>