mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-07 01:51:27 +01:00
Compare commits
11 Commits
v5.23.0-de
...
v5.23.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edf20e397d | ||
|
|
5f0541407c | ||
|
|
56b7ba9ba7 | ||
|
|
f8bdf744ab | ||
|
|
f4f36ff273 | ||
|
|
5028c1acb3 | ||
|
|
555c9a5823 | ||
|
|
777957e2d0 | ||
|
|
b3316a5915 | ||
|
|
2ca2bb7692 | ||
|
|
23fd720fa7 |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,3 +1,37 @@
|
||||
# [5.23.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.5...v5.23.0-dev.6) (2025-05-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Correct incorrect fingerprint ([c3bab89](https://github.com/ReVanced/revanced-patches/commit/c3bab89fc4189e38c10eee0caa36289de7e29dfa))
|
||||
|
||||
# [5.23.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.4...v5.23.0-dev.5) (2025-05-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Spotify - Unlock Spotify Premium:** Remove pop up premium ads ([#4842](https://github.com/ReVanced/revanced-patches/issues/4842)) ([00aa200](https://github.com/ReVanced/revanced-patches/commit/00aa2000ba2eef15a0dd827c2bd84c2e85c412e0))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Pandora:** Add `Disable audio ads` and `Unlimited skips` patch ([#4841](https://github.com/ReVanced/revanced-patches/issues/4841)) ([0cf7a4c](https://github.com/ReVanced/revanced-patches/commit/0cf7a4c6be615ed0a52a6bacf87592f5f43ff575))
|
||||
* **Prime Video:** Add `Skip ads` patch ([#4824](https://github.com/ReVanced/revanced-patches/issues/4824)) ([bb672c4](https://github.com/ReVanced/revanced-patches/commit/bb672c4674ddc201b8b2648c3906cfc31ef43f10))
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
|
||||
4
extensions/primevideo/build.gradle.kts
Normal file
4
extensions/primevideo/build.gradle.kts
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:primevideo:stub"))
|
||||
}
|
||||
1
extensions/primevideo/src/main/AndroidManifest.xml
Normal file
1
extensions/primevideo/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -0,0 +1,36 @@
|
||||
package app.revanced.extension.primevideo.ads;
|
||||
|
||||
import com.amazon.avod.fsm.SimpleTrigger;
|
||||
import com.amazon.avod.media.ads.AdBreak;
|
||||
import com.amazon.avod.media.ads.internal.state.AdBreakTrigger;
|
||||
import com.amazon.avod.media.ads.internal.state.AdEnabledPlayerTriggerType;
|
||||
import com.amazon.avod.media.playback.VideoPlayer;
|
||||
import com.amazon.avod.media.ads.internal.state.ServerInsertedAdBreakState;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class SkipAdsPatch {
|
||||
public static void enterServerInsertedAdBreakState(ServerInsertedAdBreakState state, AdBreakTrigger trigger, VideoPlayer player) {
|
||||
try {
|
||||
AdBreak adBreak = trigger.getBreak();
|
||||
|
||||
// There are two scenarios when entering the original method:
|
||||
// 1. Player naturally entered an ad break while watching a video.
|
||||
// 2. User is skipped/scrubbed to a position on the timeline. If seek position is past an ad break,
|
||||
// user is forced to watch an ad before continuing.
|
||||
//
|
||||
// Scenario 2 is indicated by trigger.getSeekStartPosition() != null, so skip directly to the scrubbing
|
||||
// target. Otherwise, just calculate when the ad break should end and skip to there.
|
||||
if (trigger.getSeekStartPosition() != null)
|
||||
player.seekTo(trigger.getSeekTarget().getTotalMilliseconds());
|
||||
else
|
||||
player.seekTo(player.getCurrentPosition() + adBreak.getDurationExcludingAux().getTotalMilliseconds());
|
||||
|
||||
// Send "end of ads" trigger to state machine so everything doesn't get whacky.
|
||||
state.doTrigger(new SimpleTrigger(AdEnabledPlayerTriggerType.NO_MORE_ADS_SKIP_TRANSITION));
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed skipping ads", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
extensions/primevideo/stub/build.gradle.kts
Normal file
17
extensions/primevideo/stub/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
id(libs.plugins.android.library.get().pluginId)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
1
extensions/primevideo/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/primevideo/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.amazon.avod.fsm;
|
||||
|
||||
public final class SimpleTrigger<T> implements Trigger<T> {
|
||||
public SimpleTrigger(T triggerType) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.amazon.avod.fsm;
|
||||
|
||||
public abstract class StateBase<S, T> {
|
||||
// This method orginally has protected access (modified in patch code).
|
||||
public void doTrigger(Trigger<T> trigger) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.amazon.avod.fsm;
|
||||
|
||||
public interface Trigger<T> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.amazon.avod.media;
|
||||
|
||||
public final class TimeSpan {
|
||||
public long getTotalMilliseconds() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.amazon.avod.media.ads;
|
||||
|
||||
import com.amazon.avod.media.TimeSpan;
|
||||
|
||||
public interface AdBreak {
|
||||
TimeSpan getDurationExcludingAux();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
public abstract class AdBreakState extends AdEnabledPlaybackState {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
import com.amazon.avod.media.ads.AdBreak;
|
||||
import com.amazon.avod.media.TimeSpan;
|
||||
|
||||
public class AdBreakTrigger {
|
||||
public AdBreak getBreak() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public TimeSpan getSeekTarget() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public TimeSpan getSeekStartPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
import com.amazon.avod.fsm.StateBase;
|
||||
import com.amazon.avod.media.playback.state.PlayerStateType;
|
||||
import com.amazon.avod.media.playback.state.trigger.PlayerTriggerType;
|
||||
|
||||
public class AdEnabledPlaybackState extends StateBase<PlayerStateType, PlayerTriggerType> {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
public enum AdEnabledPlayerTriggerType {
|
||||
NO_MORE_ADS_SKIP_TRANSITION
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
public class ServerInsertedAdBreakState extends AdBreakState {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.amazon.avod.media.playback;
|
||||
|
||||
public interface VideoPlayer {
|
||||
long getCurrentPosition();
|
||||
|
||||
void seekTo(long positionMs);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.amazon.avod.media.playback.state;
|
||||
|
||||
public interface PlayerStateType {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.amazon.avod.media.playback.state.trigger;
|
||||
|
||||
public interface PlayerTriggerType {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.2
|
||||
version = 5.23.0-dev.6
|
||||
|
||||
@@ -380,6 +380,14 @@ public final class app/revanced/patches/openinghours/misc/fix/crash/FixCrashPatc
|
||||
public static final fun getFixCrashPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/pandora/ads/DisableAudioAdsPatchKt {
|
||||
public static final fun getDisableAudioAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/pandora/misc/EnableUnlimitedSkipsPatchKt {
|
||||
public static final fun getEnableUnlimitedSkipsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/photomath/detection/deviceid/SpoofDeviceIdPatchKt {
|
||||
public static final fun getGetDeviceIdPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -412,6 +420,14 @@ public final class app/revanced/patches/pixiv/ads/HideAdsPatchKt {
|
||||
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/primevideo/ads/SkipAdsPatchKt {
|
||||
public static final fun getSkipAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/primevideo/misc/extension/ExtensionPatchKt {
|
||||
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/protonmail/signature/RemoveSentFromSignaturePatchKt {
|
||||
public static final fun getRemoveSentFromSignaturePatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
@@ -852,6 +868,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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package app.revanced.patches.pandora.ads
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.pandora.shared.constructUserDataFingerprint
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val disableAudioAdsPatch = bytecodePatch(
|
||||
name = "Disable audio ads",
|
||||
) {
|
||||
compatibleWith("com.pandora.android")
|
||||
|
||||
execute {
|
||||
constructUserDataFingerprint.method.apply {
|
||||
// First match is "hasAudioAds".
|
||||
val hasAudioAdsStringIndex = constructUserDataFingerprint.stringMatches!!.first().index
|
||||
val moveResultIndex = indexOfFirstInstructionOrThrow(hasAudioAdsStringIndex, Opcode.MOVE_RESULT)
|
||||
val hasAudioAdsRegister = getInstruction<OneRegisterInstruction>(moveResultIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
moveResultIndex + 1,
|
||||
"const/4 v$hasAudioAdsRegister, 0"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package app.revanced.patches.pandora.misc
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.pandora.shared.constructUserDataFingerprint
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val enableUnlimitedSkipsPatch = bytecodePatch(
|
||||
name = "Enable unlimited skips",
|
||||
) {
|
||||
compatibleWith("com.pandora.android")
|
||||
|
||||
execute {
|
||||
constructUserDataFingerprint.method.apply {
|
||||
// Last match is "skipLimitBehavior".
|
||||
val skipLimitBehaviorStringIndex = constructUserDataFingerprint.stringMatches!!.last().index
|
||||
val moveResultObjectIndex =
|
||||
indexOfFirstInstructionOrThrow(skipLimitBehaviorStringIndex, Opcode.MOVE_RESULT_OBJECT)
|
||||
val skipLimitBehaviorRegister = getInstruction<OneRegisterInstruction>(moveResultObjectIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
moveResultObjectIndex + 1,
|
||||
"const-string v$skipLimitBehaviorRegister, \"unlimited\""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package app.revanced.patches.pandora.shared
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val constructUserDataFingerprint = fingerprint {
|
||||
strings("hasAudioAds", "skipLimitBehavior")
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package app.revanced.patches.primevideo.ads
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val enterServerInsertedAdBreakStateFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
parameters("Lcom/amazon/avod/fsm/Trigger;")
|
||||
returns("V")
|
||||
opcodes(
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.CONST_4
|
||||
)
|
||||
custom { method, classDef ->
|
||||
method.name == "enter" && classDef.type == "Lcom/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState;"
|
||||
}
|
||||
}
|
||||
|
||||
internal val doTriggerFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PROTECTED)
|
||||
returns("V")
|
||||
opcodes(
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.RETURN_VOID
|
||||
)
|
||||
custom { method, classDef ->
|
||||
method.name == "doTrigger" && classDef.type == "Lcom/amazon/avod/fsm/StateBase;"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package app.revanced.patches.primevideo.ads
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.primevideo.misc.extension.sharedExtensionPatch
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val skipAdsPatch = bytecodePatch(
|
||||
name = "Skip ads",
|
||||
description = "Automatically skips video stream ads.",
|
||||
) {
|
||||
compatibleWith("com.amazon.avod.thirdpartyclient"("3.0.403.257"))
|
||||
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
// Skip all the logic in ServerInsertedAdBreakState.enter(), which plays all the ad clips in this
|
||||
// ad break. Instead, force the video player to seek over the entire break and reset the state machine.
|
||||
execute {
|
||||
// Force doTrigger() access to public so we can call it from our extension.
|
||||
doTriggerFingerprint.method.accessFlags = AccessFlags.PUBLIC.value;
|
||||
|
||||
val getPlayerIndex = enterServerInsertedAdBreakStateFingerprint.patternMatch!!.startIndex
|
||||
enterServerInsertedAdBreakStateFingerprint.method.apply {
|
||||
// Get register that stores VideoPlayer:
|
||||
// invoke-virtual ->getPrimaryPlayer()
|
||||
// move-result-object { playerRegister }
|
||||
val playerRegister = getInstruction<OneRegisterInstruction>(getPlayerIndex + 1).registerA
|
||||
|
||||
// Reuse the params from the original method:
|
||||
// p0 = ServerInsertedAdBreakState
|
||||
// p1 = AdBreakTrigger
|
||||
addInstructions(
|
||||
getPlayerIndex + 2,
|
||||
"""
|
||||
invoke-static { p0, p1, v$playerRegister }, Lapp/revanced/extension/primevideo/ads/SkipAdsPatch;->enterServerInsertedAdBreakState(Lcom/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState;Lcom/amazon/avod/media/ads/internal/state/AdBreakTrigger;Lcom/amazon/avod/media/playback/VideoPlayer;)V
|
||||
return-void
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package app.revanced.patches.primevideo.misc.extension
|
||||
|
||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||
|
||||
val sharedExtensionPatch = sharedExtensionPatch("primevideo", applicationInitHook)
|
||||
@@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.primevideo.misc.extension
|
||||
|
||||
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||
|
||||
internal val applicationInitHook = extensionHook {
|
||||
custom { method, classDef ->
|
||||
method.name == "onCreate" && classDef.endsWith("/SplashScreenActivity;")
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,12 @@ package app.revanced.patches.spotify.misc
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
|
||||
internal val accountAttributeFingerprint = fingerprint {
|
||||
custom { _, classDef ->
|
||||
@@ -15,7 +19,7 @@ internal val accountAttributeFingerprint = fingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
internal val productStateProtoFingerprint = fingerprint {
|
||||
internal val productStateProtoGetMapFingerprint = fingerprint {
|
||||
returns("Ljava/util/Map;")
|
||||
custom { _, classDef ->
|
||||
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||
@@ -56,16 +60,40 @@ internal val readPlayerOptionOverridesFingerprint = fingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
internal val homeSectionFingerprint = fingerprint {
|
||||
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") }
|
||||
}
|
||||
|
||||
internal val protobufListsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
custom { method, _ -> method.name == "emptyProtobufList" }
|
||||
}
|
||||
|
||||
internal val homeStructureFingerprint = fingerprint {
|
||||
opcodes(Opcode.IGET_OBJECT, Opcode.RETURN_OBJECT)
|
||||
custom { _, classDef -> classDef.endsWith("homeapi/proto/HomeStructure;") }
|
||||
internal val protobufListRemoveFingerprint = fingerprint {
|
||||
custom { method, _ -> method.name == "remove" }
|
||||
}
|
||||
|
||||
internal val homeSectionFingerprint = fingerprint {
|
||||
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") }
|
||||
}
|
||||
|
||||
internal val homeStructureGetSectionsFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
classDef.endsWith("homeapi/proto/HomeStructure;") && method.indexOfFirstInstruction {
|
||||
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "sections_"
|
||||
} >= 0
|
||||
}
|
||||
}
|
||||
|
||||
internal fun reactivexFunctionApplyWithClassInitFingerprint(className: String) = fingerprint {
|
||||
returns("Ljava/lang/Object;")
|
||||
parameters("Ljava/lang/Object;")
|
||||
custom { method, _ -> method.name == "apply" && method.indexOfFirstInstruction {
|
||||
opcode == Opcode.NEW_INSTANCE && getReference<TypeReference>()?.type?.endsWith(className) == true
|
||||
} >= 0
|
||||
}
|
||||
}
|
||||
|
||||
internal const val PENDRAGON_JSON_FETCH_MESSAGE_REQUEST_CLASS_NAME = "FetchMessageRequest;"
|
||||
internal val pendragonJsonFetchMessageRequestFingerprint =
|
||||
reactivexFunctionApplyWithClassInitFingerprint(PENDRAGON_JSON_FETCH_MESSAGE_REQUEST_CLASS_NAME)
|
||||
|
||||
internal const val PENDRAGON_PROTO_FETCH_MESSAGE_LIST_REQUEST_CLASS_NAME = "FetchMessageListRequest;"
|
||||
internal val pendragonProtoFetchMessageListRequestFingerprint =
|
||||
reactivexFunctionApplyWithClassInitFingerprint(PENDRAGON_PROTO_FETCH_MESSAGE_LIST_REQUEST_CLASS_NAME)
|
||||
|
||||
@@ -60,4 +60,4 @@ val spoofPackageInfoPatch = bytecodePatch(
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -14,4 +14,4 @@ internal val mainActivityOnCreateFingerprint = fingerprint {
|
||||
method.name == "onCreate" && (classDef.type == SPOTIFY_MAIN_ACTIVITY
|
||||
|| classDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,18 +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 elementConfigFingerprint = fingerprint {
|
||||
strings(" enableDroppedFrameLogging", " elementDepthInTree")
|
||||
}
|
||||
|
||||
internal val emptyComponentFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
|
||||
parameters()
|
||||
|
||||
@@ -7,30 +7,24 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
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.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patches.youtube.layout.returnyoutubedislike.conversionContextFingerprint
|
||||
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.indexOfFirstInstructionReversed
|
||||
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.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
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
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||
|
||||
lateinit var addLithoFilter: (String) -> Unit
|
||||
private set
|
||||
@@ -65,42 +59,27 @@ val lithoFilterPatch = bytecodePatch(
|
||||
* The following pseudocode shows how this patch works:
|
||||
*
|
||||
* class SomeOtherClass {
|
||||
* // Called before ComponentContextParser.readComponentIdentifier(...) method.
|
||||
* // Called before ComponentContextParser.parseComponent() method.
|
||||
* public void someOtherMethod(ByteBuffer byteBuffer) {
|
||||
* ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* When patching 19.16:
|
||||
*
|
||||
* class ComponentContextParser {
|
||||
* public Component readComponentIdentifier(...) {
|
||||
* 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;
|
||||
* }
|
||||
* return originalUnpatchedComponent;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* When patching 19.18 and later:
|
||||
*
|
||||
* class ComponentContextParser {
|
||||
* public ComponentIdentifierObj readComponentIdentifier(...) {
|
||||
* ...
|
||||
* if (extensionClass.filter(identifier, pathBuilder)) { // Inserted by this patch.
|
||||
* this.patch_isFiltered = true;
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* public Component parseBytesToComponentContext(...) {
|
||||
* ...
|
||||
* if (this.patch_isFiltered) { // Inserted by this patch.
|
||||
* return emptyComponent;
|
||||
* }
|
||||
* return originalUnpatchedComponent;
|
||||
* return originalUnpatchedComponent; // Original code.
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@@ -115,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
|
||||
""",
|
||||
@@ -134,134 +113,95 @@ val lithoFilterPatch = bytecodePatch(
|
||||
|
||||
// region Hook the method that parses bytes into a ComponentContext.
|
||||
|
||||
// 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()
|
||||
|
||||
// Add a field to store the result of the filtering. This allows checking the field
|
||||
// just before returning so the original code always runs the same when filtering occurs.
|
||||
val lithoFilterResultField = ImmutableField(
|
||||
componentContextParserFingerprint.classDef.type,
|
||||
"patch_isFiltered",
|
||||
"Z",
|
||||
AccessFlags.PRIVATE.value,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
).toMutable()
|
||||
componentContextParserFingerprint.classDef.fields.add(lithoFilterResultField)
|
||||
|
||||
// Returns an empty component instead of the original component.
|
||||
fun returnEmptyComponentInstructions(free: Int): String = """
|
||||
move-object/from16 v$free, p0
|
||||
iget-boolean v$free, v$free, $lithoFilterResultField
|
||||
if-eqz v$free, :unfiltered
|
||||
|
||||
move-object/from16 v$free, p1
|
||||
invoke-static { v$free }, $builderMethodDescriptor
|
||||
move-result-object v$free
|
||||
iget-object v$free, v$free, $emptyComponentField
|
||||
return-object v$free
|
||||
|
||||
:unfiltered
|
||||
nop
|
||||
"""
|
||||
|
||||
// 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) {
|
||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
|
||||
val free = findFreeRegister(index)
|
||||
val conversionContextClass = conversionContextFingerprintToString.originalClassDef
|
||||
|
||||
addInstructionsAtControlFlowLabel(
|
||||
index,
|
||||
returnEmptyComponentInstructions(free)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Read component then store the result.
|
||||
|
||||
readComponentIdentifierFingerprint.method.apply {
|
||||
val returnIndex = indexOfFirstInstructionReversedOrThrow(Opcode.RETURN_OBJECT)
|
||||
if (indexOfFirstInstructionReversed(returnIndex - 1, Opcode.RETURN_OBJECT) >= 0) {
|
||||
throw PatchException("Found multiple return indexes") // Patch needs an update.
|
||||
}
|
||||
|
||||
val elementConfigClass = elementConfigFingerprint.originalClassDef
|
||||
val elementConfigClassType = elementConfigClass.type
|
||||
val elementConfigIndex = indexOfFirstInstructionReversedOrThrow(returnIndex) {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == elementConfigClassType
|
||||
}
|
||||
val elementConfigStringBuilderField = elementConfigClass.fields.single { field ->
|
||||
field.type == "Ljava/lang/StringBuilder;"
|
||||
}
|
||||
|
||||
// Identifier is saved to a field just before the string builder.
|
||||
val putStringBuilderIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<FieldReference>()
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
reference?.definingClass == elementConfigClassType &&
|
||||
reference.type == "Ljava/lang/StringBuilder;"
|
||||
}
|
||||
val elementConfigIdentifierField = getInstruction<ReferenceInstruction>(
|
||||
indexOfFirstInstructionReversedOrThrow(putStringBuilderIndex) {
|
||||
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>()
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
reference?.definingClass == elementConfigClassType &&
|
||||
reference.type == "Ljava/lang/String;"
|
||||
reference?.definingClass == conversionContextClass.type
|
||||
&& reference.type == "Ljava/lang/String;"
|
||||
}
|
||||
).getReference<FieldReference>()
|
||||
it.method.getInstruction<ReferenceInstruction>(index).getReference<FieldReference>()
|
||||
}
|
||||
|
||||
// Could use some of these free registers multiple times, but this is inserting at a
|
||||
// return instruction so there is always multiple 4-bit registers available.
|
||||
val elementConfigRegister = getInstruction<FiveRegisterInstruction>(elementConfigIndex).registerC
|
||||
val identifierRegister = findFreeRegister(returnIndex, elementConfigRegister)
|
||||
val stringBuilderRegister = findFreeRegister(returnIndex, elementConfigRegister, identifierRegister)
|
||||
val thisRegister = findFreeRegister(returnIndex, elementConfigRegister, identifierRegister, stringBuilderRegister)
|
||||
val freeRegister = findFreeRegister(returnIndex, elementConfigRegister, identifierRegister, stringBuilderRegister, thisRegister)
|
||||
// StringBuilder field for the litho path.
|
||||
val conversionContextPathBuilderField = conversionContextClass.fields
|
||||
.single { field -> field.type == "Ljava/lang/StringBuilder;" }
|
||||
|
||||
val invokeFilterInstructions = """
|
||||
iget-object v$identifierRegister, v$elementConfigRegister, $elementConfigIdentifierField
|
||||
iget-object v$stringBuilderRegister, v$elementConfigRegister, $elementConfigStringBuilderField
|
||||
invoke-static { v$identifierRegister, v$stringBuilderRegister }, $EXTENSION_CLASS_DESCRIPTOR->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
|
||||
move-result v$freeRegister
|
||||
move-object/from16 v$thisRegister, p0
|
||||
iput-boolean v$freeRegister, v$thisRegister, $lithoFilterResultField
|
||||
"""
|
||||
val conversionContextResultIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.returnType == conversionContextClass.type
|
||||
} + 1
|
||||
|
||||
if (is_19_18_or_greater) {
|
||||
addInstructionsAtControlFlowLabel(
|
||||
returnIndex,
|
||||
invokeFilterInstructions
|
||||
)
|
||||
} else {
|
||||
val elementConfigMethod = conversionContextFingerprint.originalClassDef.methods
|
||||
.single { method ->
|
||||
!AccessFlags.STATIC.isSet(method.accessFlags) && method.returnType == elementConfigClassType
|
||||
}
|
||||
val conversionContextResultRegister = getInstruction<OneRegisterInstruction>(
|
||||
conversionContextResultIndex
|
||||
).registerA
|
||||
|
||||
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,
|
||||
"""
|
||||
# Element config is a method on a parameter.
|
||||
move-object/from16 v$elementConfigRegister, p2
|
||||
invoke-virtual { v$elementConfigRegister }, $elementConfigMethod
|
||||
move-result-object v$elementConfigRegister
|
||||
|
||||
$invokeFilterInstructions
|
||||
|
||||
${returnEmptyComponentInstructions(freeRegister)}
|
||||
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
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -18,6 +18,7 @@ import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.util.InstructionUtils.Companion.branchOpcodes
|
||||
import app.revanced.util.InstructionUtils.Companion.returnOpcodes
|
||||
import app.revanced.util.InstructionUtils.Companion.writeOpcodes
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.Opcode.*
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
@@ -169,6 +170,15 @@ internal val Instruction.isBranchInstruction: Boolean
|
||||
internal val Instruction.isReturnInstruction: Boolean
|
||||
get() = this.opcode in returnOpcodes
|
||||
|
||||
/**
|
||||
* Adds public [AccessFlags] and removes private and protected flags (if present).
|
||||
*/
|
||||
internal fun Int.toPublicAccessFlags() : Int {
|
||||
return this.or(AccessFlags.PUBLIC.value)
|
||||
.and(AccessFlags.PROTECTED.value.inv())
|
||||
.and(AccessFlags.PRIVATE.value.inv())
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the [MutableMethod] from a given [Method] in a [MutableClass].
|
||||
*
|
||||
|
||||
@@ -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 0–100 välillä</string>
|
||||
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">Pyyhkäisyn läpinäkymättömyyden on oltava välillä 0–100</string>
|
||||
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">Pyyhkäisypeittokuvan läpinäkymättömyyden tulee olla 0–100 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 1–30 välillä</string>
|
||||
<string name="revanced_swipe_text_overlay_size_invalid_toast">Tekstin koon tulee olla 1–30 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ä 0–100, 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ä 0–100</string>
|
||||
<string name="revanced_player_overlay_opacity_invalid_toast">Soittimen peittokuvan läpinäkymättömyyden tulee olla 0–100 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ä 0–100, 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ä 0–100</string>
|
||||
<string name="revanced_miniplayer_opacity_invalid_toast">Minisoittimen peittokuvan läpinäkymättömyyden tulee olla 0–100 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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"><h5>Ứng dụng này xem ra không phải do bạn tự vá.</h5><br>Ứng dụng này có thể không hoạt động chính xác, <b>tiềm ẩn rủi ro hoặc thậm chí gây nguy hiểm khi sử dụng</b>.<br><br>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;<br><br><small>%1$s</small><br>Chúng tôi khuyến nghị bạn nên <b>gỡ cài đặt ứng này và tự vá lại</b> để đảm bảo bạn đang dùng một ứng dụng an toàn và hợp lệ.<p><br>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"><h5>Ứng dụng này xem ra không phải do bạn tự vá.</h5><br>Ứng dụng này có thể không hoạt động chính xác, <b>tiềm ẩn rủi ro hoặc thậm chí gây nguy hiểm khi sử dụng</b>.<br><br>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;<br><br><small>%1$s</small><br>Chúng tôi khuyến nghị bạn nên <b>gỡ cài đặt ứng này và tự vá lại</b> để đảm bảo bạn đang dùng một ứng dụng an toàn và hợp lệ.<p><br>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 mà 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>
|
||||
|
||||
Reference in New Issue
Block a user