mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-05-20 16:27:11 +02:00
chore: Merge branch dev
to main
(#4899)
This commit is contained in:
commit
c80745b4c8
55
CHANGELOG.md
55
CHANGELOG.md
@ -1,3 +1,58 @@
|
||||
# [5.23.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.6...v5.23.0-dev.7) (2025-05-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([49ca329](https://github.com/ReVanced/revanced-patches/commit/49ca3290a726cdba7bc9b62ffcd8d46e6f04778e))
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([7b43986](https://github.com/ReVanced/revanced-patches/commit/7b43986871a68e5cb43331d2fb2fdb9ef67438ad))
|
||||
|
||||
# [5.23.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0-dev.1) (2025-05-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Lightroom:** Constrain patches to last working version ([efef03b](https://github.com/ReVanced/revanced-patches/commit/efef03b80da21552d0d8be6913faba64e4fb5ed1))
|
||||
|
||||
# [5.22.0](https://github.com/ReVanced/revanced-patches/compare/v5.21.0...v5.22.0) (2025-05-01)
|
||||
|
||||
|
||||
|
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 {
|
||||
}
|
@ -130,6 +130,7 @@ public final class UnlockPremiumPatch {
|
||||
|
||||
/**
|
||||
* Injection point. Remove ads sections from home.
|
||||
* Depends on patching protobuffer list remove method.
|
||||
*/
|
||||
public static void removeHomeSections(List<Section> sections) {
|
||||
try {
|
||||
|
@ -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.22.0
|
||||
version = 5.23.0-dev.7
|
||||
|
@ -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;
|
||||
}
|
||||
@ -1534,6 +1554,7 @@ public final class app/revanced/patches/yuka/misc/unlockpremium/UnlockPremiumPat
|
||||
|
||||
public final class app/revanced/util/BytecodeUtilsKt {
|
||||
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||
public static final fun addInstructionsAtControlFlowLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;[Lapp/revanced/patcher/util/smali/ExternalLabel;)V
|
||||
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;D)Z
|
||||
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;F)Z
|
||||
public static final fun containsLiteralInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z
|
||||
|
@ -7,7 +7,7 @@ import app.revanced.patcher.patch.bytecodePatch
|
||||
val disableMandatoryLoginPatch = bytecodePatch(
|
||||
name = "Disable mandatory login",
|
||||
) {
|
||||
compatibleWith("com.adobe.lrmobile")
|
||||
compatibleWith("com.adobe.lrmobile"("10.0.2"))
|
||||
|
||||
execute {
|
||||
isLoggedInFingerprint.method.apply {
|
||||
|
@ -7,7 +7,7 @@ import app.revanced.patcher.patch.bytecodePatch
|
||||
val unlockPremiumPatch = bytecodePatch(
|
||||
name = "Unlock premium",
|
||||
) {
|
||||
compatibleWith("com.adobe.lrmobile")
|
||||
compatibleWith("com.adobe.lrmobile"("10.0.2"))
|
||||
|
||||
execute {
|
||||
// Set hasPremium = true.
|
||||
|
@ -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)
|
||||
|
@ -4,22 +4,25 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.spotify.misc.check.checkEnvironmentPatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
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 app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.util.toPublicAccessFlags
|
||||
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.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
import java.util.logging.Logger
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/UnlockPremiumPatch;"
|
||||
@ -42,14 +45,18 @@ val unlockPremiumPatch = bytecodePatch(
|
||||
)
|
||||
|
||||
execute {
|
||||
// Make _value accessible so that it can be overridden in the extension.
|
||||
accountAttributeFingerprint.classDef.fields.first { it.name == "value_" }.apply {
|
||||
// Add public flag and remove private.
|
||||
accessFlags = accessFlags.or(AccessFlags.PUBLIC.value).and(AccessFlags.PRIVATE.value.inv())
|
||||
fun MutableClass.publicizeField(fieldName: String) {
|
||||
fields.first { it.name == fieldName }.apply {
|
||||
// Add public and remove private flag.
|
||||
accessFlags = accessFlags.toPublicAccessFlags()
|
||||
}
|
||||
}
|
||||
|
||||
// Make _value accessible so that it can be overridden in the extension.
|
||||
accountAttributeFingerprint.classDef.publicizeField("value_")
|
||||
|
||||
// Override the attributes map in the getter method.
|
||||
productStateProtoFingerprint.method.apply {
|
||||
productStateProtoGetMapFingerprint.method.apply {
|
||||
val getAttributesMapIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
|
||||
val attributesMapRegister = getInstruction<TwoRegisterInstruction>(getAttributesMapIndex).registerA
|
||||
|
||||
@ -62,19 +69,20 @@ val unlockPremiumPatch = bytecodePatch(
|
||||
|
||||
|
||||
// Add the query parameter trackRows to show popular tracks in the artist page.
|
||||
buildQueryParametersFingerprint.apply {
|
||||
val addQueryParameterConditionIndex = method.indexOfFirstInstructionReversedOrThrow(
|
||||
stringMatches!!.first().index, Opcode.IF_EQZ
|
||||
buildQueryParametersFingerprint.method.apply {
|
||||
val addQueryParameterConditionIndex = indexOfFirstInstructionReversedOrThrow(
|
||||
buildQueryParametersFingerprint.stringMatches!!.first().index, Opcode.IF_EQZ
|
||||
)
|
||||
|
||||
method.replaceInstruction(addQueryParameterConditionIndex, "nop")
|
||||
replaceInstruction(addQueryParameterConditionIndex, "nop")
|
||||
}
|
||||
|
||||
|
||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
||||
return@execute Logger.getLogger(this::class.java.name).warning(
|
||||
Logger.getLogger(this::class.java.name).warning(
|
||||
"Patching a legacy Spotify version. Patch functionality may be limited."
|
||||
)
|
||||
return@execute
|
||||
}
|
||||
|
||||
|
||||
@ -105,48 +113,39 @@ val unlockPremiumPatch = bytecodePatch(
|
||||
val shufflingContextCallIndex = indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.name == "shufflingContext"
|
||||
}
|
||||
val boolRegister = getInstruction<FiveRegisterInstruction>(shufflingContextCallIndex).registerD
|
||||
|
||||
val registerBool = getInstruction<FiveRegisterInstruction>(shufflingContextCallIndex).registerD
|
||||
addInstruction(
|
||||
shufflingContextCallIndex,
|
||||
"sget-object v$registerBool, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;"
|
||||
"sget-object v$boolRegister, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Disable the "Spotify Premium" upsell experiment in context menus.
|
||||
contextMenuExperimentsFingerprint.apply {
|
||||
val moveIsEnabledIndex = method.indexOfFirstInstructionOrThrow(
|
||||
stringMatches!!.first().index, Opcode.MOVE_RESULT
|
||||
contextMenuExperimentsFingerprint.method.apply {
|
||||
val moveIsEnabledIndex = indexOfFirstInstructionOrThrow(
|
||||
contextMenuExperimentsFingerprint.stringMatches!!.first().index, Opcode.MOVE_RESULT
|
||||
)
|
||||
val isUpsellEnabledRegister = method.getInstruction<OneRegisterInstruction>(moveIsEnabledIndex).registerA
|
||||
val isUpsellEnabledRegister = getInstruction<OneRegisterInstruction>(moveIsEnabledIndex).registerA
|
||||
|
||||
method.replaceInstruction(moveIsEnabledIndex, "const/4 v$isUpsellEnabledRegister, 0")
|
||||
replaceInstruction(moveIsEnabledIndex, "const/4 v$isUpsellEnabledRegister, 0")
|
||||
}
|
||||
|
||||
|
||||
// Make featureTypeCase_ accessible so we can check the home section type in the extension.
|
||||
homeSectionFingerprint.classDef.fields.first { it.name == "featureTypeCase_" }.apply {
|
||||
// Add public flag and remove private.
|
||||
accessFlags = accessFlags.or(AccessFlags.PUBLIC.value).and(AccessFlags.PRIVATE.value.inv())
|
||||
}
|
||||
|
||||
val protobufListClassName = with(protobufListsFingerprint.originalMethod) {
|
||||
val protobufListClassDef = with(protobufListsFingerprint.originalMethod) {
|
||||
val emptyProtobufListGetIndex = indexOfFirstInstructionOrThrow(Opcode.SGET_OBJECT)
|
||||
getInstruction(emptyProtobufListGetIndex).getReference<FieldReference>()!!.definingClass
|
||||
}
|
||||
// Find the protobuffer list class using the definingClass which contains the empty list static value.
|
||||
val classType = getInstruction(emptyProtobufListGetIndex).getReference<FieldReference>()!!.definingClass
|
||||
|
||||
val protobufListRemoveFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "remove" && classDef.type == protobufListClassName
|
||||
}
|
||||
classes.find { it.type == classType } ?: throw PatchException("Could not find protobuffer list class.")
|
||||
}
|
||||
|
||||
// Need to allow mutation of the list so the home ads sections can be removed.
|
||||
// Protobuffer list has an 'isMutable' boolean parameter that sets the mutability.
|
||||
// Forcing that always on breaks unrelated code in strange ways.
|
||||
// Instead, remove the method call that checks if the list is unmodifiable.
|
||||
protobufListRemoveFingerprint.method.apply {
|
||||
protobufListRemoveFingerprint.match(protobufListClassDef).method.apply {
|
||||
val invokeThrowUnmodifiableIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
@ -157,8 +156,12 @@ val unlockPremiumPatch = bytecodePatch(
|
||||
removeInstruction(invokeThrowUnmodifiableIndex)
|
||||
}
|
||||
|
||||
|
||||
// Make featureTypeCase_ accessible so we can check the home section type in the extension.
|
||||
homeSectionFingerprint.classDef.publicizeField("featureTypeCase_")
|
||||
|
||||
// Remove ads sections from home.
|
||||
homeStructureFingerprint.method.apply {
|
||||
homeStructureGetSectionsFingerprint.method.apply {
|
||||
val getSectionsIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
|
||||
val sectionsRegister = getInstruction<TwoRegisterInstruction>(getSectionsIndex).registerA
|
||||
|
||||
@ -168,5 +171,56 @@ val unlockPremiumPatch = bytecodePatch(
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->removeHomeSections(Ljava/util/List;)V"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Replace a fetch request that returns and maps Singles with their static onErrorReturn value.
|
||||
fun MutableMethod.replaceFetchRequestSingleWithError(requestClassName: String) {
|
||||
// The index of where the request class is being instantiated.
|
||||
val requestInstantiationIndex = indexOfFirstInstructionOrThrow {
|
||||
getReference<TypeReference>()?.type?.endsWith(requestClassName) == true
|
||||
}
|
||||
|
||||
// The index of where the onErrorReturn method is called with the error static value.
|
||||
val onErrorReturnCallIndex = indexOfFirstInstructionOrThrow(requestInstantiationIndex) {
|
||||
getReference<MethodReference>()?.name == "onErrorReturn"
|
||||
}
|
||||
val onErrorReturnCallInstruction = getInstruction<FiveRegisterInstruction>(onErrorReturnCallIndex)
|
||||
|
||||
// The error static value register.
|
||||
val onErrorReturnValueRegister = onErrorReturnCallInstruction.registerD
|
||||
|
||||
// The index where the error static value starts being constructed.
|
||||
// Because the Singles are mapped, the error static value starts being constructed right after the first
|
||||
// move-result-object of the map call, before the onErrorReturn method call.
|
||||
val onErrorReturnValueConstructionIndex =
|
||||
indexOfFirstInstructionReversedOrThrow(onErrorReturnCallIndex, Opcode.MOVE_RESULT_OBJECT) + 1
|
||||
|
||||
val singleClassName = onErrorReturnCallInstruction.getReference<MethodReference>()!!.definingClass
|
||||
// The index where the request is firstly called, before its result is mapped to other values.
|
||||
val requestCallIndex = indexOfFirstInstructionOrThrow(requestInstantiationIndex) {
|
||||
getReference<MethodReference>()?.returnType == singleClassName
|
||||
}
|
||||
|
||||
// Construct a new single with the error static value and return it.
|
||||
addInstructions(
|
||||
onErrorReturnCallIndex,
|
||||
"invoke-static { v$onErrorReturnValueRegister }, " +
|
||||
"$singleClassName->just(Ljava/lang/Object;)$singleClassName\n" +
|
||||
"move-result-object v$onErrorReturnValueRegister\n" +
|
||||
"return-object v$onErrorReturnValueRegister"
|
||||
)
|
||||
|
||||
// Remove every instruction from the request call to right before the error static value construction.
|
||||
val removeCount = onErrorReturnValueConstructionIndex - requestCallIndex
|
||||
removeInstructions(requestCallIndex, removeCount)
|
||||
}
|
||||
|
||||
// Remove pendragon (pop up ads) requests and return the errors instead.
|
||||
pendragonJsonFetchMessageRequestFingerprint.method.replaceFetchRequestSingleWithError(
|
||||
PENDRAGON_JSON_FETCH_MESSAGE_REQUEST_CLASS_NAME
|
||||
)
|
||||
pendragonProtoFetchMessageListRequestFingerprint.method.replaceFetchRequestSingleWithError(
|
||||
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,14 +43,6 @@ internal val protobufBufferReferenceFingerprint = fingerprint {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* In 19.17 and earlier, this resolves to the same method as [componentContextParserFingerprint].
|
||||
* In 19.18+ this resolves to a different method.
|
||||
*/
|
||||
internal val readComponentIdentifierFingerprint = fingerprint {
|
||||
strings("Number of bits must be positive")
|
||||
}
|
||||
|
||||
internal val emptyComponentFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
|
||||
parameters()
|
||||
|
@ -4,25 +4,25 @@ package app.revanced.patches.youtube.misc.litho.filter
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_18_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_20_05_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.shared.conversionContextFingerprintToString
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.findFreeRegister
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
@ -53,42 +53,33 @@ val lithoFilterPatch = bytecodePatch(
|
||||
* The buffer is a large byte array that represents the component tree.
|
||||
* This byte array is searched for strings that indicate the current component.
|
||||
*
|
||||
* The following pseudocode shows how the patch works:
|
||||
* All modifications done here must allow all the original code to still execute
|
||||
* even when filtering, otherwise memory leaks or poor app performance may occur.
|
||||
*
|
||||
* The following pseudocode shows how this patch works:
|
||||
*
|
||||
* class SomeOtherClass {
|
||||
* // Called before ComponentContextParser.parseBytesToComponentContext method.
|
||||
* // Called before ComponentContextParser.parseComponent() method.
|
||||
* public void someOtherMethod(ByteBuffer byteBuffer) {
|
||||
* ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* When patching 19.17 and earlier:
|
||||
*
|
||||
* class ComponentContextParser {
|
||||
* public ComponentContext ReadComponentIdentifierFingerprint(...) {
|
||||
* public Component parseComponent() {
|
||||
* ...
|
||||
* if (extensionClass.filter(identifier, pathBuilder)); // Inserted by this patch.
|
||||
*
|
||||
* // Checks if the component should be filtered.
|
||||
* // Sets a thread local with the filtering result.
|
||||
* extensionClass.filter(identifier, pathBuilder); // Inserted by this patch.
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* if (extensionClass.shouldFilter()) { // Inserted by this patch.
|
||||
* return emptyComponent;
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* When patching 19.18 and later:
|
||||
*
|
||||
* class ComponentContextParser {
|
||||
* public ComponentContext parseBytesToComponentContext(...) {
|
||||
* ...
|
||||
* if (ReadComponentIdentifierFingerprint() == null); // Inserted by this patch.
|
||||
* return emptyComponent;
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* public ComponentIdentifierObj readComponentIdentifier(...) {
|
||||
* ...
|
||||
* if (extensionClass.filter(identifier, pathBuilder)); // Inserted by this patch.
|
||||
* return null;
|
||||
* ...
|
||||
* }
|
||||
* return originalUnpatchedComponent; // Original code.
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@ -103,7 +94,7 @@ val lithoFilterPatch = bytecodePatch(
|
||||
2,
|
||||
"""
|
||||
new-instance v1, $classDescriptor
|
||||
invoke-direct {v1}, $classDescriptor-><init>()V
|
||||
invoke-direct { v1 }, $classDescriptor-><init>()V
|
||||
const/16 v2, ${filterCount++}
|
||||
aput-object v1, v0, v2
|
||||
""",
|
||||
@ -115,110 +106,105 @@ val lithoFilterPatch = bytecodePatch(
|
||||
|
||||
protobufBufferReferenceFingerprint.method.addInstruction(
|
||||
0,
|
||||
" invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
|
||||
"invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
// region Hook the method that parses bytes into a ComponentContext.
|
||||
|
||||
val readComponentMethod = readComponentIdentifierFingerprint.originalMethod
|
||||
// Get the only static method in the class.
|
||||
val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.first { method ->
|
||||
AccessFlags.STATIC.isSet(method.accessFlags)
|
||||
}
|
||||
// Only one field.
|
||||
val emptyComponentField = classBy { classDef ->
|
||||
builderMethodDescriptor.returnType == classDef.type
|
||||
}!!.immutableClass.fields.single()
|
||||
|
||||
// Returns an empty component instead of the original component.
|
||||
fun createReturnEmptyComponentInstructions(register: Int): String =
|
||||
"""
|
||||
move-object/from16 v$register, p1
|
||||
invoke-static { v$register }, $builderMethodDescriptor
|
||||
move-result-object v$register
|
||||
iget-object v$register, v$register, $emptyComponentField
|
||||
return-object v$register
|
||||
"""
|
||||
|
||||
// Allow the method to run to completion, and override the
|
||||
// return value with an empty component if it should be filtered.
|
||||
// It is important to allow the original code to always run to completion,
|
||||
// otherwise memory leaks and poor app performance can occur.
|
||||
//
|
||||
// The extension filtering result needs to be saved off somewhere, but cannot
|
||||
// save to a class field since the target class is called by multiple threads.
|
||||
// It would be great if there was a way to change the register count of the
|
||||
// method implementation and save the result to a high register to later use
|
||||
// in the method, but there is no simple way to do that.
|
||||
// Instead save the extension filter result to a thread local and check the
|
||||
// filtering result at each method return index.
|
||||
// String field for the litho identifier.
|
||||
componentContextParserFingerprint.method.apply {
|
||||
// 19.18 and later require patching 2 methods instead of one.
|
||||
// Otherwise the modifications done here are the same for all targets.
|
||||
if (is_19_18_or_greater) {
|
||||
// Get the method name of the ReadComponentIdentifierFingerprint call.
|
||||
val readComponentMethodCallIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == readComponentMethod.definingClass &&
|
||||
reference.name == readComponentMethod.name
|
||||
val conversionContextClass = conversionContextFingerprintToString.originalClassDef
|
||||
|
||||
val conversionContextIdentifierField = componentContextSubParserFingerprint.match(
|
||||
componentContextParserFingerprint.originalClassDef
|
||||
).let {
|
||||
// Identifier field is loaded just before the string declaration.
|
||||
val index = it.method.indexOfFirstInstructionReversedOrThrow(
|
||||
it.stringMatches!!.first().index
|
||||
) {
|
||||
val reference = getReference<FieldReference>()
|
||||
reference?.definingClass == conversionContextClass.type
|
||||
&& reference.type == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
// Result of read component, and also a free register.
|
||||
val register = getInstruction<OneRegisterInstruction>(readComponentMethodCallIndex + 1).registerA
|
||||
|
||||
// Insert after 'move-result-object'
|
||||
val insertHookIndex = readComponentMethodCallIndex + 2
|
||||
|
||||
// Return an EmptyComponent instead of the original component if the filterState method returns true.
|
||||
addInstructionsWithLabels(
|
||||
insertHookIndex,
|
||||
"""
|
||||
if-nez v$register, :unfiltered
|
||||
|
||||
# Component was filtered in ReadComponentIdentifierFingerprint hook
|
||||
${createReturnEmptyComponentInstructions(register)}
|
||||
""",
|
||||
ExternalLabel("unfiltered", getInstruction(insertHookIndex)),
|
||||
)
|
||||
it.method.getInstruction<ReferenceInstruction>(index).getReference<FieldReference>()
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
// StringBuilder field for the litho path.
|
||||
val conversionContextPathBuilderField = conversionContextClass.fields
|
||||
.single { field -> field.type == "Ljava/lang/StringBuilder;" }
|
||||
|
||||
// region Read component then store the result.
|
||||
val conversionContextResultIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.returnType == conversionContextClass.type
|
||||
} + 1
|
||||
|
||||
readComponentIdentifierFingerprint.method.apply {
|
||||
val insertHookIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
getReference<FieldReference>()?.type == "Ljava/lang/StringBuilder;"
|
||||
}
|
||||
val stringBuilderRegister = getInstruction<TwoRegisterInstruction>(insertHookIndex).registerA
|
||||
|
||||
// Identifier is saved to a field just before the string builder.
|
||||
val identifierRegister = getInstruction<TwoRegisterInstruction>(
|
||||
indexOfFirstInstructionReversedOrThrow(insertHookIndex) {
|
||||
opcode == Opcode.IPUT_OBJECT &&
|
||||
getReference<FieldReference>()?.type == "Ljava/lang/String;"
|
||||
},
|
||||
val conversionContextResultRegister = getInstruction<OneRegisterInstruction>(
|
||||
conversionContextResultIndex
|
||||
).registerA
|
||||
|
||||
val freeRegister = findFreeRegister(insertHookIndex, identifierRegister, stringBuilderRegister)
|
||||
val invokeFilterInstructions = """
|
||||
invoke-static { v$identifierRegister, v$stringBuilderRegister }, $EXTENSION_CLASS_DESCRIPTOR->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :unfiltered
|
||||
"""
|
||||
|
||||
addInstructionsWithLabels(
|
||||
insertHookIndex,
|
||||
if (is_19_18_or_greater) {
|
||||
"""
|
||||
$invokeFilterInstructions
|
||||
|
||||
# Return null, and the ComponentContextParserFingerprint hook
|
||||
# handles returning an empty component.
|
||||
const/4 v$freeRegister, 0x0
|
||||
return-object v$freeRegister
|
||||
"""
|
||||
} else {
|
||||
"""
|
||||
$invokeFilterInstructions
|
||||
|
||||
${createReturnEmptyComponentInstructions(freeRegister)}
|
||||
"""
|
||||
},
|
||||
ExternalLabel("unfiltered", getInstruction(insertHookIndex)),
|
||||
val identifierRegister = findFreeRegister(
|
||||
conversionContextResultIndex, conversionContextResultRegister
|
||||
)
|
||||
val stringBuilderRegister = findFreeRegister(
|
||||
conversionContextResultIndex, conversionContextResultRegister, identifierRegister
|
||||
)
|
||||
|
||||
// Check if the component should be filtered, and save the result to a thread local.
|
||||
addInstructionsAtControlFlowLabel(
|
||||
conversionContextResultIndex + 1,
|
||||
"""
|
||||
iget-object v$identifierRegister, v$conversionContextResultRegister, $conversionContextIdentifierField
|
||||
iget-object v$stringBuilderRegister, v$conversionContextResultRegister, $conversionContextPathBuilderField
|
||||
invoke-static { v$identifierRegister, v$stringBuilderRegister }, $EXTENSION_CLASS_DESCRIPTOR->filter(Ljava/lang/String;Ljava/lang/StringBuilder;)V
|
||||
"""
|
||||
)
|
||||
|
||||
// Get the only static method in the class.
|
||||
val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.single {
|
||||
method -> AccessFlags.STATIC.isSet(method.accessFlags)
|
||||
}
|
||||
// Only one field.
|
||||
val emptyComponentField = classBy { classDef ->
|
||||
classDef.type == builderMethodDescriptor.returnType
|
||||
}!!.immutableClass.fields.single()
|
||||
|
||||
// Check at each return value if the component is filtered,
|
||||
// and return an empty component if filtering is needed.
|
||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { returnIndex ->
|
||||
val freeRegister = findFreeRegister(returnIndex)
|
||||
|
||||
addInstructionsAtControlFlowLabel(
|
||||
returnIndex,
|
||||
"""
|
||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->shouldFilter()Z
|
||||
move-result v$freeRegister
|
||||
if-eqz v$freeRegister, :unfiltered
|
||||
|
||||
move-object/from16 v$freeRegister, p1
|
||||
invoke-static { v$freeRegister }, $builderMethodDescriptor
|
||||
move-result-object v$freeRegister
|
||||
iget-object v$freeRegister, v$freeRegister, $emptyComponentField
|
||||
return-object v$freeRegister
|
||||
|
||||
:unfiltered
|
||||
nop
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
@ -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")
|
||||
|
@ -11,12 +11,14 @@ import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
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
|
||||
@ -168,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].
|
||||
*
|
||||
@ -207,6 +218,26 @@ fun MutableMethod.injectHideViewCall(
|
||||
"invoke-static { v$viewRegister }, $classDescriptor->$targetMethod(Landroid/view/View;)V",
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Inserts instructions at a given index, using the existing control flow label at that index.
|
||||
* Inserted instructions can have it's own control flow labels as well.
|
||||
*
|
||||
* Effectively this changes the code from:
|
||||
* :label
|
||||
* (original code)
|
||||
*
|
||||
* Into:
|
||||
* :label
|
||||
* (patch code)
|
||||
* (original code)
|
||||
*/
|
||||
// TODO: delete this on next major version bump.
|
||||
fun MutableMethod.addInstructionsAtControlFlowLabel(
|
||||
insertIndex: Int,
|
||||
instructions: String
|
||||
) = addInstructionsAtControlFlowLabel(insertIndex, instructions, *arrayOf<ExternalLabel>())
|
||||
|
||||
/**
|
||||
* Inserts instructions at a given index, using the existing control flow label at that index.
|
||||
* Inserted instructions can have it's own control flow labels as well.
|
||||
@ -223,13 +254,14 @@ fun MutableMethod.injectHideViewCall(
|
||||
fun MutableMethod.addInstructionsAtControlFlowLabel(
|
||||
insertIndex: Int,
|
||||
instructions: String,
|
||||
vararg externalLabels: ExternalLabel
|
||||
) {
|
||||
// Duplicate original instruction and add to +1 index.
|
||||
addInstruction(insertIndex + 1, getInstruction(insertIndex))
|
||||
|
||||
// Add patch code at same index as duplicated instruction,
|
||||
// so it uses the original instruction control flow label.
|
||||
addInstructionsWithLabels(insertIndex + 1, instructions)
|
||||
addInstructionsWithLabels(insertIndex + 1, instructions, *externalLabels)
|
||||
|
||||
// Remove original non duplicated instruction.
|
||||
removeInstruction(insertIndex)
|
||||
@ -472,7 +504,7 @@ fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): I
|
||||
* @see indexOfFirstInstructionOrThrow
|
||||
*/
|
||||
fun Method.indexOfFirstInstruction(startIndex: Int = 0, filter: Instruction.() -> Boolean): Int {
|
||||
var instructions = this.implementation!!.instructions
|
||||
var instructions = this.implementation?.instructions ?: return -1
|
||||
if (startIndex != 0) {
|
||||
instructions = instructions.drop(startIndex)
|
||||
}
|
||||
@ -538,7 +570,7 @@ fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode
|
||||
* @see indexOfFirstInstructionReversedOrThrow
|
||||
*/
|
||||
fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, filter: Instruction.() -> Boolean): Int {
|
||||
var instructions = this.implementation!!.instructions
|
||||
var instructions = this.implementation?.instructions ?: return -1
|
||||
if (startIndex != null) {
|
||||
instructions = instructions.take(startIndex + 1)
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -114,9 +114,9 @@ Brúigh an cnaipe leanúnaí agus ligean athruithe optúimíochta."</string>
|
||||
<string name="revanced_debug_toast_on_error_title">Taispeáin tósta ar earráid ReVanced</string>
|
||||
<string name="revanced_debug_toast_on_error_summary_on">Taispeántar toast má tharlaíonn earráid</string>
|
||||
<string name="revanced_debug_toast_on_error_summary_off">Ní thaispeántar toast má tharlaíonn earráid</string>
|
||||
<string name="revanced_debug_toast_on_error_user_dialog_message">"Díchumasaíonn an rogha toasts earráide fógraí earráide ReVanced go léir.
|
||||
<string name="revanced_debug_toast_on_error_user_dialog_message">"Má mhúchtar tóstaí earráide, folaítear gach fógra earráide ReVanced.
|
||||
|
||||
Ní bheidh a fhios agat faoi aon imeachtaí neamhghnácha."</string>
|
||||
Ní chuirfear ar an eolas thú faoi aon imeachtaí gan choinne."</string>
|
||||
</patch>
|
||||
<patch id="layout.hide.general.hideLayoutComponentsPatch">
|
||||
<string name="revanced_hide_album_cards_title">Folaigh cártaí albam</string>
|
||||
@ -205,11 +205,11 @@ Ní bheidh a fhios agat faoi aon imeachtaí neamhghnácha."</string>
|
||||
<string name="revanced_hide_medical_panels_summary_on">Tá painéil leighis i bhfolach</string>
|
||||
<string name="revanced_hide_medical_panels_summary_off">Taispeántar painéil leighis</string>
|
||||
<string name="revanced_hide_channel_bar_title">Folaigh barra cainéal</string>
|
||||
<string name="revanced_hide_channel_bar_summary_on">Tá barra cainéal i bhfolach</string>
|
||||
<string name="revanced_hide_channel_bar_summary_off">Taispeántar barra cainéal</string>
|
||||
<string name="revanced_hide_playables_title">Folaigh Rudaí Inimeartha</string>
|
||||
<string name="revanced_hide_playables_summary_on">Tá rudaí inimeartha i bhfolach</string>
|
||||
<string name="revanced_hide_playables_summary_off">Taispeántar rudaí inimeartha</string>
|
||||
<string name="revanced_hide_channel_bar_summary_on">Tá an barra Cainéal i bhfolach</string>
|
||||
<string name="revanced_hide_channel_bar_summary_off">Taispeántar barra an chainéil</string>
|
||||
<string name="revanced_hide_playables_title">Folaigh na heilimintí inseinnte</string>
|
||||
<string name="revanced_hide_playables_summary_on">Tá na heilimintí inseinnte i bhfolach</string>
|
||||
<string name="revanced_hide_playables_summary_off">Taispeántar na heilimintí inseinnte</string>
|
||||
<string name="revanced_hide_quick_actions_title">Folaigh gníomhartha gasta i lánscáileán</string>
|
||||
<string name="revanced_hide_quick_actions_summary_on">Tá gníomhartha gasta i bhfolach</string>
|
||||
<string name="revanced_hide_quick_actions_summary_off">Taispeántar gníomhartha tapa</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>
|
||||
@ -333,9 +333,9 @@ MicroG GmsCore に対する電池の最適化を無効にしても、バッテ
|
||||
<string name="revanced_hide_keyword_content_about_summary">"ホーム / 登録チャンネル / 検索結果からキーワードに合致する動画を除外します
|
||||
|
||||
制限事項
|
||||
• ショート動画はチャンネル名で除外できません
|
||||
• 一部の UI コンポーネントが残ってしまう場合があります
|
||||
• キーワードを検索しても、結果が表示されない場合があります"</string>
|
||||
• ショート動画はチャンネル名で除外されない
|
||||
• 一部の UI コンポーネントが残ってしまう場合がある
|
||||
• キーワードを検索したとき、結果が表示されない場合がある"</string>
|
||||
<string name="revanced_hide_keyword_content_about_whole_words_title">単語全体で合致させる</string>
|
||||
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc.) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
|
||||
<string name="revanced_hide_keyword_content_about_whole_words_summary">キーワードを二重引用符で囲むことで、動画のタイトルやチャンネル名の単語の一部とキーワードが合致しないようにできます<br><br>例えば、<br><b>\"ai\"</b>は、次の動画を除外します:<b>How does AI work?</b><br>しかし、次の動画は除外しません:<b>What does fair use mean?</b></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">
|
||||
@ -1099,11 +1099,11 @@ MicroG GmsCore に対する電池の最適化を無効にしても、バッテ
|
||||
<string name="revanced_change_form_factor_user_dialog_message">"変更点は以下のとおりです:
|
||||
|
||||
タブレット レイアウト
|
||||
• コミュニティ投稿が表示されません
|
||||
• コミュニティ投稿が表示されない
|
||||
|
||||
Automotive レイアウト
|
||||
• ショート動画を通常のプレーヤーで開きます
|
||||
• フィードがトピックとチャンネルで分類されます"</string>
|
||||
• ショート動画を通常のプレーヤーで開く
|
||||
• フィードがトピックとチャンネルで分類される"</string>
|
||||
</patch>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<string name="revanced_spoof_app_version_title">アプリのバージョンを偽装する</string>
|
||||
@ -1189,8 +1189,8 @@ Automotive レイアウト
|
||||
<string name="revanced_miniplayer_double_tap_action_title">「ダブルタップとピンチでサイズ変更」を有効にする</string>
|
||||
<string name="revanced_miniplayer_double_tap_action_summary_on">"「ダブルタップとピンチでサイズ変更」は有効です
|
||||
|
||||
• ダブルタップでミニプレーヤーのサイズを大きくします
|
||||
• もう一度ダブルタップすると、元のサイズに戻ります"</string>
|
||||
• ダブルタップすると、ミニプレーヤーのサイズが大きくなる
|
||||
• もう一度ダブルタップすると、元のサイズに戻る"</string>
|
||||
<string name="revanced_miniplayer_double_tap_action_summary_off">「 ダブルタップとピンチでサイズ変更」は無効です</string>
|
||||
<string name="revanced_miniplayer_drag_and_drop_title">ドラッグ&ドロップを有効にする</string>
|
||||
<string name="revanced_miniplayer_drag_and_drop_summary_on">"ドラッグ&ドロップは有効です
|
||||
@ -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>
|
||||
@ -1399,13 +1399,13 @@ Automotive レイアウト
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
|
||||
<string name="revanced_spoof_video_streams_screen_title">動画ストリームを偽装する</string>
|
||||
<string name="revanced_spoof_video_streams_screen_summary">再生不能問題を回避するために、クライアントの動画ストリームを偽装します</string>
|
||||
<string name="revanced_spoof_video_streams_screen_summary">動画の再生に失敗しないために、クライアントの動画ストリームを偽装します</string>
|
||||
<string name="revanced_spoof_video_streams_title">動画ストリームを偽装する</string>
|
||||
<string name="revanced_spoof_video_streams_summary_on">動画ストリームは偽装されます</string>
|
||||
<string name="revanced_spoof_video_streams_summary_off">"動画ストリームは偽装されません
|
||||
|
||||
動画が再生されない可能性があります"</string>
|
||||
<string name="revanced_spoof_video_streams_user_dialog_message">この設定をオフにすると、動画が再生されなくなる可能性があります。</string>
|
||||
動画の再生に失敗する可能性があります"</string>
|
||||
<string name="revanced_spoof_video_streams_user_dialog_message">この設定をオフにすると、動画の再生に失敗するようになる可能性があります。</string>
|
||||
<string name="revanced_spoof_video_streams_client_type_title">デフォルトのクライアント</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_title">iOS クライアントで AVC (H.264) を強制的に使用する</string>
|
||||
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">ビデオ コーデックは強制的に AVC (H.264) が使用されます</string>
|
||||
@ -1414,15 +1414,15 @@ Automotive レイアウト
|
||||
|
||||
AVC は、最大解像度が 1080p であり、Opus オーディオ コーデックが利用できず、動画再生時の通信量が VP9 や AV1 より多くなります。"</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_tv_title">iOS クライアントの副作用</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_tv_summary">"• 映画や有料動画が再生されない可能性があります
|
||||
•「 一定音量」が利用できません
|
||||
• 動画が 1 秒早く終了します"</string>
|
||||
<string name="revanced_spoof_video_streams_about_ios_tv_summary">"• 映画や有料動画が再生されない可能性がある
|
||||
•「 一定音量」が利用できない
|
||||
• 動画が 1 秒早く終了する"</string>
|
||||
<string name="revanced_spoof_video_streams_about_android_title">Android クライアントの副作用</string>
|
||||
<string name="revanced_spoof_video_streams_about_android_summary">"• 「音声トラック」がフライアウト メニューに表示されません
|
||||
• 「一定音量」が利用できません
|
||||
• 「デフォルトの吹き替えを無効にする」が利用できません"</string>
|
||||
<string name="revanced_spoof_video_streams_about_no_av1">• AV1 コーデックが利用できません</string>
|
||||
<string name="revanced_spoof_video_streams_about_kids_videos">• ログアウト時またはシークレット モード時に、子ども向け動画が再生されない可能性があります</string>
|
||||
<string name="revanced_spoof_video_streams_about_android_summary">"• 「音声トラック」がフライアウト メニューに表示されない
|
||||
• 「一定音量」が利用できない
|
||||
• 「デフォルトの吹き替えを無効にする」が利用できない"</string>
|
||||
<string name="revanced_spoof_video_streams_about_no_av1">• AV1 コーデックが利用できない</string>
|
||||
<string name="revanced_spoof_video_streams_about_kids_videos">• ログアウト時またはシークレット モード時に、子ども向け動画が再生されない可能性がある</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">統計情報に表示する</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_on">現在のクライアントが統計情報に表示されます</string>
|
||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_off">現在のクライアントは統計情報に表示されません</string>
|
||||
|
@ -1035,7 +1035,7 @@ MicroG 앱 배터리 최적화를 비활성화(제한 없음)하더라도, 배
|
||||
<string name="revanced_sb_vote_segment_time_to_from">%1$s ~ %2$s</string>
|
||||
<string name="revanced_sb_new_segment_choose_category">구간 카테고리를 선택하세요</string>
|
||||
<string name="revanced_sb_new_segment_disabled_category">이 카테고리는 비활성화되어 있습니다. 제출하려면 설정에서 활성화해야 합니다.</string>
|
||||
<string name="revanced_sb_new_segment_title">새 SponsorBlock 구간</string>
|
||||
<string name="revanced_sb_new_segment_title">새로운 SponsorBlock 구간</string>
|
||||
<string name="revanced_sb_new_segment_mark_time_as_question">%s 을 구간의 시작 또는 끝으로 설정하시겠습니까?</string>
|
||||
<string name="revanced_sb_new_segment_mark_start">시작</string>
|
||||
<string name="revanced_sb_new_segment_mark_end">끝</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>
|
||||
|
@ -495,12 +495,12 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.buttons.action.hideButtonsPatch">
|
||||
<string name="revanced_hide_buttons_screen_title">Кнопки дій</string>
|
||||
<string name="revanced_hide_buttons_screen_summary">Приховати або показувати кнопки дій під відео</string>
|
||||
<string name="revanced_disable_like_subscribe_glow_title">Вимкнути відблиск кнопок \"Подобається\" та \"Підписати\"</string>
|
||||
<string name="revanced_disable_like_subscribe_glow_summary_on">Кнопки \"Подобається\" та \"Підписатися\" не відблискуватимуть при згадуванні</string>
|
||||
<string name="revanced_disable_like_subscribe_glow_summary_off">Кнопки \"Подобається\" та \"Підписатися\" не відблискуватимуть при згадуванні</string>
|
||||
<string name="revanced_hide_like_dislike_button_title">Приховати \"Подобається\" та \"Не подобається\"</string>
|
||||
<string name="revanced_hide_like_dislike_button_summary_on">Кнопки \"Подобається\" та \"Не подобається\" приховано</string>
|
||||
<string name="revanced_hide_like_dislike_button_summary_off">Кнопки \"Подобається\" та \"Не подобається\" показуються</string>
|
||||
<string name="revanced_disable_like_subscribe_glow_title">Вимкнути блимання кнопок \"Лайк\" та \"Підписатися\"</string>
|
||||
<string name="revanced_disable_like_subscribe_glow_summary_on">Кнопки \"Лайк\" та \"Підписатися\" не будуть блимати при згадуванні</string>
|
||||
<string name="revanced_disable_like_subscribe_glow_summary_off">Кнопки \"Лайк\" та \"Підписатися\" не будуть блимати при згадуванні</string>
|
||||
<string name="revanced_hide_like_dislike_button_title">Приховати \"Лайк\" та \"Дизлайк\"</string>
|
||||
<string name="revanced_hide_like_dislike_button_summary_on">Кнопки \"Лайк\" та \"Дизлайк\" приховано</string>
|
||||
<string name="revanced_hide_like_dislike_button_summary_off">Кнопки \"Лайк\" та \"Дизлайк\" показуються</string>
|
||||
<!-- 'Share' should be translated with the same localized wording that YouTube displays. -->
|
||||
<string name="revanced_hide_share_button_title">Приховати \"Поділитися\"</string>
|
||||
<string name="revanced_hide_share_button_summary_on">Кнопку \"Поділитися\" приховано</string>
|
||||
@ -745,14 +745,14 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_hide_shorts_stickers_summary_on">Стікери приховано</string>
|
||||
<string name="revanced_hide_shorts_stickers_summary_off">Стікери показуються</string>
|
||||
<string name="revanced_hide_shorts_like_fountain_title">Приховати анімацію фонтану</string>
|
||||
<string name="revanced_hide_shorts_like_fountain_summary_on">Анімацію фонтану біля кнопки \"Подобається\" приховано</string>
|
||||
<string name="revanced_hide_shorts_like_fountain_summary_off">Анімація фонтану біля кнопки \"Подобається\" показується</string>
|
||||
<string name="revanced_hide_shorts_like_button_title">Приховати \"Подобається\"</string>
|
||||
<string name="revanced_hide_shorts_like_button_summary_on">Кнопку \"Подобається\" приховано</string>
|
||||
<string name="revanced_hide_shorts_like_button_summary_off">Кнопка \"Подобається\" показується</string>
|
||||
<string name="revanced_hide_shorts_dislike_button_title">Приховати \"Не подобається\"</string>
|
||||
<string name="revanced_hide_shorts_dislike_button_summary_on">Кнопку \"Не подобається\" приховано</string>
|
||||
<string name="revanced_hide_shorts_dislike_button_summary_off">Кнопка \"Не подобається\" показується</string>
|
||||
<string name="revanced_hide_shorts_like_fountain_summary_on">Анімацію фонтану біля кнопки \"Лайк\" приховано</string>
|
||||
<string name="revanced_hide_shorts_like_fountain_summary_off">Анімація фонтану біля кнопки \"Лайк\" показується</string>
|
||||
<string name="revanced_hide_shorts_like_button_title">Приховати \"Лайк\"</string>
|
||||
<string name="revanced_hide_shorts_like_button_summary_on">Кнопку \"Лайк\" приховано</string>
|
||||
<string name="revanced_hide_shorts_like_button_summary_off">Кнопка \"Лайк\" показується</string>
|
||||
<string name="revanced_hide_shorts_dislike_button_title">Приховати \"Дизлайк\"</string>
|
||||
<string name="revanced_hide_shorts_dislike_button_summary_on">Кнопку \"Дизлайк\" приховано</string>
|
||||
<string name="revanced_hide_shorts_dislike_button_summary_off">Кнопка \"Дизлайк\" показується</string>
|
||||
<string name="revanced_hide_shorts_comments_button_title">Приховати \"Коментарі\"</string>
|
||||
<string name="revanced_hide_shorts_comments_button_summary_on">Кнопку \"Коментарі\" приховано</string>
|
||||
<string name="revanced_hide_shorts_comments_button_summary_off">Кнопка \"Коментарі\" показується</string>
|
||||
@ -823,43 +823,43 @@ Second \"item\" text"</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. -->
|
||||
<string name="revanced_ryd_failure_connection_timeout">Відмітки \"Не подобається\" тимчасово недоступні (тайм-аут API)</string>
|
||||
<string name="revanced_ryd_failure_connection_status_code">Відмітки \"Не подобається\" недоступні (статус %d)</string>
|
||||
<string name="revanced_ryd_failure_client_rate_limit_requested">Відмітки \"Не подобається\" недоступні (ліміт клієнтів API)</string>
|
||||
<string name="revanced_ryd_failure_generic">Відмітки \"Не подобається\" недоступні (%s)</string>
|
||||
<string name="revanced_ryd_failure_connection_timeout">Дизлайки тимчасово недоступні (тайм-аут API)</string>
|
||||
<string name="revanced_ryd_failure_connection_status_code">Дизлайки недоступні (статус %d)</string>
|
||||
<string name="revanced_ryd_failure_client_rate_limit_requested">Дизлайки недоступні (ліміт клієнтів API)</string>
|
||||
<string name="revanced_ryd_failure_generic">Дизлайки недоступні (%s)</string>
|
||||
<!-- Toast shown if the user enables RYD while a video is opened, and then tries to vote for the video. -->
|
||||
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Оновіть відео, щоб проголосувати за допомогою ReturnYouTubeDislike</string>
|
||||
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Оновіть відео, щоб проголосувати за допомогою Return YouTube Dislike</string>
|
||||
<!-- Video likes have been set to hidden by the video uploader. -->
|
||||
<string name="revanced_ryd_video_likes_hidden_by_video_owner">Приховано власником</string>
|
||||
<string name="revanced_ryd_enable_summary_on">Відмітки \"Не подобається\" показуються</string>
|
||||
<string name="revanced_ryd_enable_summary_off">Відмітки \"Не подобається\" не показуються</string>
|
||||
<string name="revanced_ryd_shorts_title">Відмітки \"Не подобається\" в Shorts</string>
|
||||
<string name="revanced_ryd_shorts_summary_on_disclaimer">"Відмітки \"Не подобається\" в Shorts показуються
|
||||
<string name="revanced_ryd_enable_summary_on">Дизлайки показуються</string>
|
||||
<string name="revanced_ryd_enable_summary_off">Дизлайки не показуються</string>
|
||||
<string name="revanced_ryd_shorts_title">Показувати дизлайки в Shorts</string>
|
||||
<string name="revanced_ryd_shorts_summary_on_disclaimer">"Дизлайки в Shorts показуються
|
||||
|
||||
Обмеження: Відмітки \"Не подобається\" не можуть показуватися в анонімному режимі"</string>
|
||||
<string name="revanced_ryd_shorts_summary_off">Відмітки \"Не подобається\" в Shorts не показуються</string>
|
||||
<string name="revanced_ryd_dislike_percentage_title">Відмітки \"Не подобається\" у відсотках</string>
|
||||
<string name="revanced_ryd_dislike_percentage_summary_on">Відмітки \"Не подобається\" показуються у відсотках</string>
|
||||
<string name="revanced_ryd_dislike_percentage_summary_off">Відмітки \"Не подобається\" показуються як число</string>
|
||||
Обмеження: Дизлайки не можуть показуватися в анонімному режимі"</string>
|
||||
<string name="revanced_ryd_shorts_summary_off">Дизлайки в Shorts не показуються</string>
|
||||
<string name="revanced_ryd_dislike_percentage_title">Дизлайки у відсотках</string>
|
||||
<string name="revanced_ryd_dislike_percentage_summary_on">Дизлайки показуються у відсотках</string>
|
||||
<string name="revanced_ryd_dislike_percentage_summary_off">Дизлайки показуються як число</string>
|
||||
<!-- Translations should use language similar to 'revanced_sb_enable_compact_skip_button' -->
|
||||
<string name="revanced_ryd_compact_layout_title">Компактна кнопка \"Подобається\"</string>
|
||||
<string name="revanced_ryd_compact_layout_summary_on">Кнопку \"Подобається\" стилізовано під мінімальну ширину</string>
|
||||
<string name="revanced_ryd_compact_layout_summary_off">Кнопку \"Подобається\" стилізовано для кращого вигляду</string>
|
||||
<string name="revanced_ryd_estimated_like_title">Показувати приблизну кількість вподобань</string>
|
||||
<string name="revanced_ryd_estimated_like_summary_on">На відео з вимкненими відмітками \"Подобається\" показується приблизна кількість вподобань</string>
|
||||
<string name="revanced_ryd_estimated_like_summary_off">Приблизна кількість вподобань не показується</string>
|
||||
<string name="revanced_ryd_compact_layout_title">Компактна кнопка \"Лайк\"</string>
|
||||
<string name="revanced_ryd_compact_layout_summary_on">Кнопку \"Лайк\" стилізовано під мінімальну ширину</string>
|
||||
<string name="revanced_ryd_compact_layout_summary_off">Кнопку \"Лайк\" стилізовано для кращого вигляду</string>
|
||||
<string name="revanced_ryd_estimated_like_title">Показувати приблизну кількість лайків</string>
|
||||
<string name="revanced_ryd_estimated_like_summary_on">Для відео з вимкненими лайками показується приблизна кількість лайків</string>
|
||||
<string name="revanced_ryd_estimated_like_summary_off">Приблизна кількість лайків не показується</string>
|
||||
<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_attribution_summary">Дані надаються Return YouTube Dislike API. Натисніть тут, щоб дізнатися більше</string>
|
||||
<string name="revanced_ryd_attribution_summary">Дані надаються через API Return YouTube Dislike. Натисніть тут, щоб дізнатися більше</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>
|
||||
<string name="revanced_ryd_statistics_category_title">Статистика API ReturnYouTubeDislike цього пристрою</string>
|
||||
<string name="revanced_ryd_statistics_getFetchCallResponseTimeAverage_title">Час відповіді API, середній</string>
|
||||
<string name="revanced_ryd_statistics_getFetchCallResponseTimeMin_title">Час відповіді API, мінімальний</string>
|
||||
<string name="revanced_ryd_statistics_getFetchCallResponseTimeMax_title">Час відповіді API, максимальний</string>
|
||||
<string name="revanced_ryd_statistics_getFetchCallResponseTimeLast_title">Час відповіді API, останнє відео</string>
|
||||
<string name="revanced_ryd_statistics_getFetchCallResponseTimeLast_rate_limit_summary">Відмітки \"Не подобається\" тимчасово недоступні - діє обмеження швидкості клієнта API</string>
|
||||
<string name="revanced_ryd_statistics_getFetchCallResponseTimeLast_rate_limit_summary">Дизлайки тимчасово недоступні - діє обмеження швидкості для клієнта API</string>
|
||||
<string name="revanced_ryd_statistics_getFetchCallCount_title">Отримання голосів API, кількість запитів</string>
|
||||
<string name="revanced_ryd_statistics_getFetchCallCount_zero_summary">Мережевих запитів не здійснено </string>
|
||||
<string name="revanced_ryd_statistics_getFetchCallCount_non_zero_summary">Здійснено %d мережевих запитів</string>
|
||||
@ -948,13 +948,13 @@ Second \"item\" text"</string>
|
||||
<string name="revanced_sb_settings_ie">Імпорт / Експорт налаштувань</string>
|
||||
<string name="revanced_sb_settings_copy">Копіювати</string>
|
||||
<string name="revanced_sb_settings_ie_sum">Ваші налаштування SponsorBlock, які можуть бути імпортовані чи експортовані у ReVanced та до інших платформ SponsorBlock</string>
|
||||
<string name="revanced_sb_settings_ie_sum_warning">Ваші налаштування SponsorBlock, які можуть бути імпортовані чи експортовані у ReVanced та до інших платформ SponsorBlock. Вони також містять Ваш особистий ідентифікатор користувача. Діліться ним розумно</string>
|
||||
<string name="revanced_sb_settings_ie_sum_warning">Ваші налаштування SponsorBlock, які можуть бути імпортовані чи експортовані у ReVanced та до інших платформ SponsorBlock. Вони також містять Ваш особистий ідентифікатор користувача. Діліться ними розумно</string>
|
||||
<string name="revanced_sb_settings_import_successful">Налаштування успішно імпортовано</string>
|
||||
<string name="revanced_sb_settings_import_failed">Не вдалося імпортувати: %s</string>
|
||||
<string name="revanced_sb_settings_export_failed">Не вдалося експортувати: %s</string>
|
||||
<string name="revanced_sb_settings_revanced_export_user_id_warning">"Ваші налаштування містять особистий ID користувача SponsorBlock.
|
||||
<string name="revanced_sb_settings_revanced_export_user_id_warning">"Ваші налаштування містять особистий ідентифікатор користувача SponsorBlock.
|
||||
|
||||
Ваш ID користувача це як пароль і його не можна поширювати."</string>
|
||||
Ваш ідентифікатор користувача це як пароль і його не можна поширювати."</string>
|
||||
<string name="revanced_sb_settings_revanced_export_user_id_warning_dismiss">Більше не показувати</string>
|
||||
<string name="revanced_sb_diff_segments">Змінити поведінку сегмента</string>
|
||||
<string name="revanced_sb_segments_sponsor">Спонсор</string>
|
||||
@ -1020,10 +1020,10 @@ Second \"item\" text"</string>
|
||||
Він вже існує"</string>
|
||||
<string name="revanced_sb_submit_succeeded">Сегмент успішно надіслано</string>
|
||||
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
|
||||
<string name="revanced_sb_sponsorblock_connection_failure_timeout">SponsorBlock тимчасово недоступний (закінчився час API)</string>
|
||||
<string name="revanced_sb_sponsorblock_connection_failure_timeout">SponsorBlock тимчасово недоступний (тайм-аут API)</string>
|
||||
<string name="revanced_sb_sponsorblock_connection_failure_status">SponsorBlock тимчасово недоступний (статус %d)</string>
|
||||
<string name="revanced_sb_sponsorblock_connection_failure_generic">SponsorBlock тимчасово не доступний</string>
|
||||
<string name="revanced_sb_vote_failed_timeout">Не вдалося проголосувати за сегмент (закінчився час API)</string>
|
||||
<string name="revanced_sb_vote_failed_timeout">Не вдалося проголосувати за сегмент (тайм-аут API)</string>
|
||||
<string name="revanced_sb_vote_failed_unknown_error">Не вдалося проголосувати за сегмент (статус: %1$d %2$s)</string>
|
||||
<string name="revanced_sb_vote_failed_forbidden">Не вдалося проголосувати за сегмент: %s</string>
|
||||
<string name="revanced_sb_vote_upvote">Проголосувати \"за\"</string>
|
||||
@ -1087,7 +1087,7 @@ Second \"item\" text"</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_api_sum">Дані надаються SponsorBlock API. Натисніть тут, щоб дізнатися більше та побачити завантаження для інших платформ</string>
|
||||
<string name="revanced_sb_about_api_sum">Дані надаються через API SponsorBlock. Натисніть тут, щоб дізнатися більше та побачити завантаження для інших платформ</string>
|
||||
</patch>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
<string name="revanced_change_form_factor_title">Макет інтерфейсу</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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user