Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(SoundCloud): Add Enable offline sync patch #3407

Merged
merged 11 commits into from
Jul 13, 2024
6 changes: 6 additions & 0 deletions api/revanced-patches.api
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,12 @@ public final class app/revanced/patches/soundcloud/analytics/DisableTelemetryPat
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}

public final class app/revanced/patches/soundcloud/offlinesync/EnableOfflineSyncPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/soundcloud/offlinesync/EnableOfflineSyncPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}

public final class app/revanced/patches/spotify/layout/theme/CustomThemePatch : app/revanced/patcher/patch/ResourcePatch {
public static final field INSTANCE Lapp/revanced/patches/spotify/layout/theme/CustomThemePatch;
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.soundcloud.ad.fingerprints.InterceptFingerprint
import app.revanced.patches.soundcloud.ad.fingerprints.FeatureConstructorFingerprint
import app.revanced.patches.soundcloud.shared.fingerprints.FeatureConstructorFingerprint
import app.revanced.patches.soundcloud.ad.fingerprints.UserConsumerPlanConstructorFingerprint
import app.revanced.util.resultOrThrow

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package app.revanced.patches.soundcloud.offlinesync

import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.soundcloud.offlinesync.fingerprints.DownloadOperationsHeaderVerificationFingerprint
import app.revanced.patches.soundcloud.offlinesync.fingerprints.DownloadOperationsURLBuilderFingerprint
import app.revanced.patches.soundcloud.shared.fingerprints.FeatureConstructorFingerprint
import app.revanced.util.getReference
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference

@Patch(
name = "Enable offline sync",
compatiblePackages = [CompatiblePackage("com.soundcloud.android")],
)
@Suppress("unused")
object EnableOfflineSyncPatch : BytecodePatch(
setOf(
FeatureConstructorFingerprint, DownloadOperationsURLBuilderFingerprint,
DownloadOperationsHeaderVerificationFingerprint
),
) {
override fun execute(context: BytecodeContext) {
// Enable the feature to allow offline track syncing by modifying the JSON server response.
// This method is the constructor of a class representing a "Feature" object parsed from JSON data.
// p1 is the name of the feature.
// p2 is true if the feature is enabled, false otherwise.
FeatureConstructorFingerprint.resultOrThrow().mutableMethod.apply {
val afterCheckNotNullIndex = 2

addInstructionsWithLabels(
afterCheckNotNullIndex,
"""
const-string v0, "offline_sync"
invoke-virtual { p1, v0 }, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v0
if-eqz v0, :skip
const/4 p2, 0x1
""",
ExternalLabel("skip", getInstruction(afterCheckNotNullIndex)),
)
}

// Patch the URL builder to use the HTTPS_STREAM endpoint
// instead of the offline sync endpoint to downloading the track.
DownloadOperationsURLBuilderFingerprint.resultOrThrow().mutableMethod.apply {
val getEndpointsEnumFieldIndex = 1
val getEndpointsEnumFieldInstruction = getInstruction<OneRegisterInstruction>(getEndpointsEnumFieldIndex)

val targetRegister = getEndpointsEnumFieldInstruction.registerA
val endpointsType = getEndpointsEnumFieldInstruction.getReference<FieldReference>()!!.type

replaceInstruction(
getEndpointsEnumFieldIndex,
"sget-object v$targetRegister, $endpointsType->HTTPS_STREAM:$endpointsType"
)
}

// The HTTPS_STREAM endpoint does not return the necessary headers for offline sync.
// Mock the headers to prevent the app from crashing by setting them to empty strings.
// The headers are all cosmetic and do not affect the functionality of the app.
DownloadOperationsHeaderVerificationFingerprint.resultOrThrow().mutableMethod.apply {
// The first three null checks need to be patched.
getInstructions().asSequence().filter {
it.opcode == Opcode.IF_EQZ
}.take(3).map { it.location.index }.forEach { nullCheckIndex ->
val headerStringRegister = getInstruction<OneRegisterInstruction>(nullCheckIndex).registerA

addInstruction(nullCheckIndex, "const-string v$headerStringRegister, \"\"")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package app.revanced.patches.soundcloud.offlinesync.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode

internal object DownloadOperationsHeaderVerificationFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L","L"),
opcodes = listOf(
Opcode.CONST_STRING,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CONST_STRING
),
customFingerprint = { _, classDef ->
classDef.sourceFile == "DownloadOperations.kt"
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package app.revanced.patches.soundcloud.offlinesync.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode

internal object DownloadOperationsURLBuilderFingerprint : MethodFingerprint(
returnType = "Ljava/lang/String",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L","L"),
opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.SGET_OBJECT,
Opcode.FILLED_NEW_ARRAY
),
customFingerprint = { _, classDef ->
classDef.sourceFile == "DownloadOperations.kt"
}
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package app.revanced.patches.soundcloud.ad.fingerprints
package app.revanced.patches.soundcloud.shared.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
Expand Down
Loading