mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-26 12:11:02 +00:00
Compare commits
4 Commits
v5.33.0-de
...
v5.33.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a8146dbc8 | ||
|
|
178eed7fcd | ||
|
|
621292644c | ||
|
|
1dd01cf54a |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
|||||||
|
# [5.33.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.9...v5.33.0-dev.10) (2025-08-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Video quality:** Fix wrong qualities sometimes shown in player button dialog ([7378ae3](https://github.com/ReVanced/revanced-patches/commit/7378ae3c5fc88f91bf5cd6db47c6cd170a8c5a4f))
|
||||||
|
|
||||||
|
# [5.33.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.8...v5.33.0-dev.9) (2025-08-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Force original audio:** Disable a/b feature flag that forces localized audio ([#5582](https://github.com/ReVanced/revanced-patches/issues/5582)) ([9fe13ee](https://github.com/ReVanced/revanced-patches/commit/9fe13ee1af104c009efd19b826adef375e48e191))
|
||||||
|
|
||||||
# [5.33.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.7...v5.33.0-dev.8) (2025-08-03)
|
# [5.33.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.7...v5.33.0-dev.8) (2025-08-03)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ public class ForceOriginalAudioPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean ignoreDefaultAudioStream(boolean original) {
|
||||||
|
if (Settings.FORCE_ORIGINAL_AUDIO.get()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@@ -50,7 +60,6 @@ public class ForceOriginalAudioPatch {
|
|||||||
return isOriginal;
|
return isOriginal;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "isDefaultAudioStream failure", ex);
|
Logger.printException(() -> "isDefaultAudioStream failure", ex);
|
||||||
|
|
||||||
return isDefault;
|
return isDefault;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality;
|
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
@@ -45,13 +44,11 @@ public class RememberVideoQualityPatch {
|
|||||||
private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI;
|
private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI;
|
||||||
private static final IntegerSetting shortsQualityMobile = Settings.SHORTS_QUALITY_DEFAULT_MOBILE;
|
private static final IntegerSetting shortsQualityMobile = Settings.SHORTS_QUALITY_DEFAULT_MOBILE;
|
||||||
|
|
||||||
private static boolean qualityNeedsUpdating;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The available qualities of the current video.
|
* The available qualities of the current video.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static List<VideoQuality> currentQualities;
|
private static VideoQuality[] currentQualities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current quality of the video playing.
|
* The current quality of the video playing.
|
||||||
@@ -67,7 +64,7 @@ public class RememberVideoQualityPatch {
|
|||||||
private static VideoQualityMenuInterface currentMenuInterface;
|
private static VideoQualityMenuInterface currentMenuInterface;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static List<VideoQuality> getCurrentQualities() {
|
public static VideoQuality[] getCurrentQualities() {
|
||||||
return currentQualities;
|
return currentQualities;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,11 +135,11 @@ public class RememberVideoQualityPatch {
|
|||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
currentMenuInterface = menu;
|
currentMenuInterface = menu;
|
||||||
|
|
||||||
final boolean availableQualitiesChanged = currentQualities == null
|
final boolean availableQualitiesChanged = (currentQualities == null)
|
||||||
|| currentQualities.size() != qualities.length;
|
|| !Arrays.equals(currentQualities, qualities);
|
||||||
if (availableQualitiesChanged) {
|
if (availableQualitiesChanged) {
|
||||||
currentQualities = Arrays.asList(qualities);
|
currentQualities = qualities;
|
||||||
Logger.printDebug(() -> "VideoQualities: " + currentQualities);
|
Logger.printDebug(() -> "VideoQualities: " + Arrays.toString(currentQualities));
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoQuality updatedCurrentQuality = qualities[originalQualityIndex];
|
VideoQuality updatedCurrentQuality = qualities[originalQualityIndex];
|
||||||
@@ -155,17 +152,12 @@ public class RememberVideoQualityPatch {
|
|||||||
VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality);
|
VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int preferredQuality = getDefaultQualityResolution();
|
|
||||||
if (preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) {
|
|
||||||
return originalQualityIndex; // Nothing to do.
|
|
||||||
}
|
|
||||||
|
|
||||||
// After changing videos the qualities can initially be for the prior video.
|
// After changing videos the qualities can initially be for the prior video.
|
||||||
// If the qualities have changed and the default is not auto then an update is needed.
|
// If the qualities have changed and the default is not auto then an update is needed.
|
||||||
if (!qualityNeedsUpdating && !availableQualitiesChanged) {
|
final int preferredQuality = getDefaultQualityResolution();
|
||||||
return originalQualityIndex;
|
if (preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE || !availableQualitiesChanged) {
|
||||||
|
return originalQualityIndex; // Nothing to do.
|
||||||
}
|
}
|
||||||
qualityNeedsUpdating = false;
|
|
||||||
|
|
||||||
// Find the highest quality that is equal to or less than the preferred.
|
// Find the highest quality that is equal to or less than the preferred.
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@@ -213,7 +205,7 @@ public class RememberVideoQualityPatch {
|
|||||||
Logger.printDebug(() -> "Cannot save default quality, qualities is null");
|
Logger.printDebug(() -> "Cannot save default quality, qualities is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
VideoQuality quality = currentQualities.get(userSelectedQualityIndex);
|
VideoQuality quality = currentQualities[userSelectedQualityIndex];
|
||||||
saveDefaultQuality(quality.patch_getResolution());
|
saveDefaultQuality(quality.patch_getResolution());
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -243,7 +235,6 @@ public class RememberVideoQualityPatch {
|
|||||||
currentQualities = null;
|
currentQualities = null;
|
||||||
currentQuality = null;
|
currentQuality = null;
|
||||||
currentMenuInterface = null;
|
currentMenuInterface = null;
|
||||||
qualityNeedsUpdating = true;
|
|
||||||
|
|
||||||
// Hide the quality button until playback starts and the qualities are available.
|
// Hide the quality button until playback starts and the qualities are available.
|
||||||
VideoQualityDialogButton.updateButtonIcon(null);
|
VideoQualityDialogButton.updateButtonIcon(null);
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ public class VideoQualityDialogButton {
|
|||||||
},
|
},
|
||||||
view -> {
|
view -> {
|
||||||
try {
|
try {
|
||||||
List<VideoQuality> qualities = RememberVideoQualityPatch.getCurrentQualities();
|
VideoQuality[] qualities = RememberVideoQualityPatch.getCurrentQualities();
|
||||||
VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface();
|
VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface();
|
||||||
if (qualities == null || menu == null) {
|
if (qualities == null || menu == null) {
|
||||||
Logger.printDebug(() -> "Cannot reset quality, videoQualities is null");
|
Logger.printDebug(() -> "Cannot reset quality, videoQualities is null");
|
||||||
@@ -186,13 +186,13 @@ public class VideoQualityDialogButton {
|
|||||||
*/
|
*/
|
||||||
private static void showVideoQualityDialog(Context context) {
|
private static void showVideoQualityDialog(Context context) {
|
||||||
try {
|
try {
|
||||||
List<VideoQuality> currentQualities = RememberVideoQualityPatch.getCurrentQualities();
|
VideoQuality[] currentQualities = RememberVideoQualityPatch.getCurrentQualities();
|
||||||
VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality();
|
VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality();
|
||||||
if (currentQualities == null || currentQuality == null) {
|
if (currentQualities == null || currentQuality == null) {
|
||||||
Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null");
|
Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentQualities.size() < 2) {
|
if (currentQualities.length < 2) {
|
||||||
// Should never happen.
|
// Should never happen.
|
||||||
Logger.printException(() -> "Cannot show qualities dialog, no qualities available");
|
Logger.printException(() -> "Cannot show qualities dialog, no qualities available");
|
||||||
return;
|
return;
|
||||||
@@ -205,9 +205,17 @@ public class VideoQualityDialogButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -1 adjustment for automatic quality at first index.
|
// -1 adjustment for automatic quality at first index.
|
||||||
final int listViewSelectedIndex = currentQualities.indexOf(currentQuality) - 1;
|
int listViewSelectedIndex = 0;
|
||||||
|
for (VideoQuality quality : currentQualities) {
|
||||||
|
if (quality == currentQuality) {
|
||||||
|
// -1 adjustment for the missing automatic quality in the dialog list.
|
||||||
|
listViewSelectedIndex--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
listViewSelectedIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
List<String> qualityLabels = new ArrayList<>(currentQualities.size() - 1);
|
List<String> qualityLabels = new ArrayList<>(currentQualities.length - 1);
|
||||||
for (VideoQuality availableQuality : currentQualities) {
|
for (VideoQuality availableQuality : currentQualities) {
|
||||||
if (availableQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) {
|
if (availableQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) {
|
||||||
qualityLabels.add(availableQuality.patch_getQualityName());
|
qualityLabels.add(availableQuality.patch_getQualityName());
|
||||||
@@ -310,7 +318,7 @@ public class VideoQualityDialogButton {
|
|||||||
listView.setOnItemClickListener((parent, view, which, id) -> {
|
listView.setOnItemClickListener((parent, view, which, id) -> {
|
||||||
try {
|
try {
|
||||||
final int originalIndex = which + 1; // Adjust for automatic.
|
final int originalIndex = which + 1; // Adjust for automatic.
|
||||||
VideoQuality selectedQuality = currentQualities.get(originalIndex);
|
VideoQuality selectedQuality = currentQualities[originalIndex];
|
||||||
Logger.printDebug(() -> "User clicked on quality: " + selectedQuality);
|
Logger.printDebug(() -> "User clicked on quality: " + selectedQuality);
|
||||||
|
|
||||||
if (RememberVideoQualityPatch.shouldRememberVideoQuality()) {
|
if (RememberVideoQualityPatch.shouldRememberVideoQuality()) {
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.33.0-dev.8
|
version = 5.33.0-dev.10
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
package app.revanced.patches.youtube.video.audio
|
package app.revanced.patches.youtube.video.audio
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.util.containsLiteralInstruction
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
|
||||||
internal val streamingModelBuilderFingerprint = fingerprint {
|
internal val formatStreamModelToStringFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
returns("L")
|
returns("Ljava/lang/String;")
|
||||||
strings("vprng")
|
custom { method, classDef ->
|
||||||
|
method.name == "toString" && classDef.type ==
|
||||||
|
"Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val menuItemAudioTrackFingerprint = fingerprint {
|
internal const val AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG = 45666189L
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
|
||||||
parameters("L")
|
internal val selectAudioStreamFingerprint = fingerprint {
|
||||||
returns("V")
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||||
strings("menu_item_audio_track")
|
returns("L")
|
||||||
|
custom { method, _ ->
|
||||||
|
method.parameters.size > 2 // Method has a large number of parameters and may change.
|
||||||
|
&& method.parameters[1].type == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;"
|
||||||
|
&& method.containsLiteralInstruction(AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val audioStreamingTypeSelector = fingerprint {
|
|
||||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
|
|
||||||
returns("L")
|
|
||||||
strings("raw") // String is not unique
|
|
||||||
}
|
|
||||||
@@ -5,22 +5,22 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
|
|||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
|
||||||
|
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.findMethodFromToString
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import app.revanced.util.insertLiteralOverride
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
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.builder.MutableMethodImplementation
|
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||||
import com.android.tools.smali.dexlib2.iface.Method
|
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||||
@@ -37,6 +37,7 @@ val forceOriginalAudioPatch = bytecodePatch(
|
|||||||
sharedExtensionPatch,
|
sharedExtensionPatch,
|
||||||
settingsPatch,
|
settingsPatch,
|
||||||
addResourcesPatch,
|
addResourcesPatch,
|
||||||
|
versionCheckPatch
|
||||||
)
|
)
|
||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
@@ -60,29 +61,25 @@ val forceOriginalAudioPatch = bytecodePatch(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Method.firstFormatStreamingModelCall(
|
// Disable feature flag that ignores the default track flag
|
||||||
returnType: String = "Ljava/lang/String;"
|
// and instead overrides to the user region language.
|
||||||
): MutableMethod {
|
if (is_20_07_or_greater) {
|
||||||
val audioTrackIdIndex = indexOfFirstInstructionOrThrow {
|
selectAudioStreamFingerprint.method.insertLiteralOverride(
|
||||||
val reference = getReference<MethodReference>()
|
AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG,
|
||||||
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
|
"$EXTENSION_CLASS_DESCRIPTOR->ignoreDefaultAudioStream(Z)Z"
|
||||||
&& reference.returnType == returnType
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return navigate(this).to(audioTrackIdIndex).stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accessor methods of FormatStreamModel have no string constants and
|
val isDefaultAudioTrackMethod = formatStreamModelToStringFingerprint.originalMethod
|
||||||
// opcodes are identical to other methods in the same class,
|
.findMethodFromToString("isDefaultAudioTrack=")
|
||||||
// so must walk from another class that use the methods.
|
val audioTrackDisplayNameMethod = formatStreamModelToStringFingerprint.originalMethod
|
||||||
val isDefaultMethod = streamingModelBuilderFingerprint.originalMethod.firstFormatStreamingModelCall("Z")
|
.findMethodFromToString("audioTrackDisplayName=")
|
||||||
val audioTrackIdMethod = menuItemAudioTrackFingerprint.originalMethod.firstFormatStreamingModelCall()
|
val audioTrackIdMethod = formatStreamModelToStringFingerprint.originalMethod
|
||||||
val audioTrackDisplayNameMethod = audioStreamingTypeSelector.originalMethod.firstFormatStreamingModelCall()
|
.findMethodFromToString("audioTrackId=")
|
||||||
val formatStreamModelClass = proxy(classes.first {
|
|
||||||
it.type == audioTrackIdMethod.definingClass
|
|
||||||
}).mutableClass
|
|
||||||
|
|
||||||
formatStreamModelClass.apply {
|
proxy(classes.first {
|
||||||
|
it.type == audioTrackIdMethod.definingClass
|
||||||
|
}).mutableClass.apply {
|
||||||
// Add a new field to store the override.
|
// Add a new field to store the override.
|
||||||
val helperFieldName = "isDefaultAudioTrackOverride"
|
val helperFieldName = "isDefaultAudioTrackOverride"
|
||||||
fields.add(
|
fields.add(
|
||||||
@@ -103,7 +100,7 @@ val forceOriginalAudioPatch = bytecodePatch(
|
|||||||
|
|
||||||
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
|
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
|
||||||
val helperMethodClass = type
|
val helperMethodClass = type
|
||||||
val helperMethodName = "extension_isDefaultAudioTrack"
|
val helperMethodName = "patch_isDefaultAudioTrack"
|
||||||
val helperMethod = ImmutableMethod(
|
val helperMethod = ImmutableMethod(
|
||||||
helperMethodClass,
|
helperMethodClass,
|
||||||
helperMethodName,
|
helperMethodName,
|
||||||
@@ -143,7 +140,7 @@ val forceOriginalAudioPatch = bytecodePatch(
|
|||||||
methods.add(helperMethod)
|
methods.add(helperMethod)
|
||||||
|
|
||||||
// Modify isDefaultAudioTrack() to call extension helper method.
|
// Modify isDefaultAudioTrack() to call extension helper method.
|
||||||
isDefaultMethod.apply {
|
isDefaultAudioTrackMethod.apply {
|
||||||
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
|
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
|
||||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstructio
|
|||||||
import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
|
||||||
|
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.Reference
|
import com.android.tools.smali.dexlib2.iface.reference.Reference
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
@@ -171,6 +174,79 @@ internal val Instruction.isBranchInstruction: Boolean
|
|||||||
internal val Instruction.isReturnInstruction: Boolean
|
internal val Instruction.isReturnInstruction: Boolean
|
||||||
get() = this.opcode in returnOpcodes
|
get() = this.opcode in returnOpcodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the instruction index used for a toString() StringBuilder write of a given String name.
|
||||||
|
*
|
||||||
|
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||||
|
*/
|
||||||
|
private fun Method.findInstructionIndexFromToString(fieldName: String) : Int {
|
||||||
|
val stringIndex = indexOfFirstInstruction {
|
||||||
|
val reference = getReference<StringReference>()
|
||||||
|
reference?.string?.contains(fieldName) == true
|
||||||
|
}
|
||||||
|
if (stringIndex < 0) {
|
||||||
|
throw IllegalArgumentException("Could not find usage of string: '$fieldName'")
|
||||||
|
}
|
||||||
|
val stringRegister = getInstruction<OneRegisterInstruction>(stringIndex).registerA
|
||||||
|
|
||||||
|
// Find use of the string with a StringBuilder.
|
||||||
|
val stringUsageIndex = indexOfFirstInstruction(stringIndex) {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.definingClass == "Ljava/lang/StringBuilder;" &&
|
||||||
|
(this as? FiveRegisterInstruction)?.registerD == stringRegister
|
||||||
|
}
|
||||||
|
if (stringUsageIndex < 0) {
|
||||||
|
throw IllegalArgumentException("Could not find StringBuilder usage in: $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the next usage of StringBuilder, which should be the desired field.
|
||||||
|
val fieldUsageIndex = indexOfFirstInstruction(stringUsageIndex + 1) {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.definingClass == "Ljava/lang/StringBuilder;" && reference.name == "append"
|
||||||
|
}
|
||||||
|
if (fieldUsageIndex < 0) {
|
||||||
|
// Should never happen.
|
||||||
|
throw IllegalArgumentException("Could not find StringBuilder append usage in: $this")
|
||||||
|
}
|
||||||
|
val fieldUsageRegister = getInstruction<FiveRegisterInstruction>(fieldUsageIndex).registerD
|
||||||
|
|
||||||
|
// Look backwards up the method to find the instruction that sets the register.
|
||||||
|
var fieldSetIndex = indexOfFirstInstructionReversedOrThrow(fieldUsageIndex - 1) {
|
||||||
|
fieldUsageRegister == writeRegister
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the field is a method call, then adjust from MOVE_RESULT to the method call.
|
||||||
|
val fieldSetOpcode = getInstruction(fieldSetIndex).opcode
|
||||||
|
if (fieldSetOpcode == MOVE_RESULT ||
|
||||||
|
fieldSetOpcode == MOVE_RESULT_WIDE ||
|
||||||
|
fieldSetOpcode == MOVE_RESULT_OBJECT) {
|
||||||
|
fieldSetIndex--
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldSetIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the method used for a toString() StringBuilder write of a given String name.
|
||||||
|
*
|
||||||
|
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||||
|
*/
|
||||||
|
context(BytecodePatchContext)
|
||||||
|
internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod {
|
||||||
|
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
|
||||||
|
return navigate(this).to(methodUsageIndex).stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the field used for a toString() StringBuilder write of a given String name.
|
||||||
|
*
|
||||||
|
* @param fieldName The name of the field to find. Partial matches are allowed.
|
||||||
|
*/
|
||||||
|
internal fun Method.findFieldFromToString(fieldName: String) : FieldReference {
|
||||||
|
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
|
||||||
|
return getInstruction<ReferenceInstruction>(methodUsageIndex).getReference<FieldReference>()!!
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds public [AccessFlags] and removes private and protected flags (if present).
|
* Adds public [AccessFlags] and removes private and protected flags (if present).
|
||||||
*/
|
*/
|
||||||
@@ -594,7 +670,7 @@ fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int = indexOfF
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the index of matching instruction,
|
* Get the index of matching instruction,
|
||||||
* starting from and [startIndex] and searching down.
|
* starting from [startIndex] and searching down.
|
||||||
*
|
*
|
||||||
* @param startIndex Optional starting index to search down from. Searching includes the start index.
|
* @param startIndex Optional starting index to search down from. Searching includes the start index.
|
||||||
* @return The index of the instruction.
|
* @return The index of the instruction.
|
||||||
@@ -617,7 +693,7 @@ fun Method.indexOfFirstInstructionReversedOrThrow(targetOpcode: Opcode): Int = i
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the index of matching instruction,
|
* Get the index of matching instruction,
|
||||||
* starting from and [startIndex] and searching down.
|
* starting from [startIndex] and searching down.
|
||||||
*
|
*
|
||||||
* @param startIndex Optional starting index to search down from. Searching includes the start index.
|
* @param startIndex Optional starting index to search down from. Searching includes the start index.
|
||||||
* @return The index of the instruction.
|
* @return The index of the instruction.
|
||||||
|
|||||||
Reference in New Issue
Block a user