This commit is contained in:
Andy Bao 2025-05-10 16:39:06 +02:00 committed by GitHub
commit 638c8c5116
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 225 additions and 0 deletions

View file

@ -505,6 +505,14 @@ public final class app/revanced/patches/reddit/customclients/joeyforreddit/detec
public static final fun getDisablePiracyDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/redditisfun/ads/FakePremiumKt {
public static final fun getFakePremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/redditisfun/api/ImgurAlbumsPatchKt {
public static final fun getImgurAlbumsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/redditisfun/api/SpoofClientPatchKt {
public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}

View file

@ -0,0 +1,18 @@
package app.revanced.patches.reddit.customclients.redditisfun.ads
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
// Based on: https://github.com/ReVanced/revanced-patches/issues/661#issuecomment-2549674017
val fakePremiumPatch = bytecodePatch(
name = "Fake reddit premium",
description = "Allows using pro features without ads."
) {
compatibleWith(
"com.andrewshu.android.reddit",
)
execute {
userPremiumFingerprint.method.returnEarly(true)
}
}

View file

@ -0,0 +1,18 @@
package app.revanced.patches.reddit.customclients.redditisfun.ads
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
// Should usually match: o5/i0.a()
internal val userPremiumFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Z")
parameters()
custom { _, classDef ->
// Expect the class to have exactly one static field of type HashSet.
val sfIter = classDef.staticFields.iterator()
sfIter.hasNext()
&& sfIter.next().type == "Ljava/util/HashSet;"
&& !sfIter.hasNext()
}
}

View file

@ -29,3 +29,11 @@ internal val getUserAgentFingerprint = fingerprint {
Opcode.CONST,
)
}
// Should usually match: g2/c.x()
internal val imgurApiFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Landroid/net/Uri;")
parameters("Ljava/lang/String;", "Z")
strings("https", "api", "imgur", "3", "gallery", "album")
}

View file

@ -0,0 +1,173 @@
package app.revanced.patches.reddit.customclients.redditisfun.api
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.stringOption
val imgurAlbumsPatch = bytecodePatch(
name = "Use public imgur API",
description = "Fix imgur albums not loading."
) {
compatibleWith(
"com.andrewshu.android.reddit",
"com.andrewshu.android.redditdonation",
)
val clientId by stringOption(
key = "imgur-client-id",
// Obtained from: https://s.imgur.com/desktop-assets/js/main.[snip].js | grep apiClientId
default = "546c25a59c58ad7",
title = "Imgur client ID",
description = "The default value should work for most users",
required = true,
)
execute {
val m = imgurApiFingerprint.method
m.removeInstructions(m.instructions.size)
val androidNetUriBuilder = "android/net/Uri\$Builder"
m.addInstructions(0, """
new-instance v0, L$androidNetUriBuilder;
invoke-direct {v0}, L$androidNetUriBuilder;-><init>()V
const-string v1, "https"
invoke-virtual {v0, v1}, L$androidNetUriBuilder;->scheme(Ljava/lang/String;)L$androidNetUriBuilder;
move-result-object v0
const-string v1, "api.imgur.com"
invoke-virtual {v0, v1}, L$androidNetUriBuilder;->authority(Ljava/lang/String;)L$androidNetUriBuilder;
move-result-object v0
const-string v1, "3"
invoke-virtual {v0, v1}, L$androidNetUriBuilder;->appendPath(Ljava/lang/String;)L$androidNetUriBuilder;
move-result-object v0
if-eqz p1, :cond_0
const-string p1, "gallery"
invoke-virtual {v0, p1}, L$androidNetUriBuilder;->appendPath(Ljava/lang/String;)L$androidNetUriBuilder;
:cond_0
const-string p1, "album"
invoke-virtual {v0, p1}, L$androidNetUriBuilder;->appendPath(Ljava/lang/String;)L$androidNetUriBuilder;
move-result-object p1
invoke-virtual {p1, p0}, L$androidNetUriBuilder;->appendPath(Ljava/lang/String;)L$androidNetUriBuilder;
move-result-object p0
const-string v0, "client_id"
const-string v1, "$clientId"
invoke-virtual {p0, v0, v1}, L$androidNetUriBuilder;->appendQueryParameter(Ljava/lang/String;Ljava/lang/String;)L$androidNetUriBuilder;
move-result-object p0
invoke-virtual {p0}, L$androidNetUriBuilder;->build()Landroid/net/Uri;
move-result-object p0
return-object p0
""".trimIndent())
}
}
/*
The original contents of the method decodes an obfuscated string, you can use the following script
to decode the value:
import kotlin.math.pow
object StringDecryptor {
/**
* Decrypts a string using a position-dependent Caesar-like cipher.
* Handles lowercase, uppercase, and other characters differently.
* Replaces '>' with '_' at the end.
*/
fun w(input: String): String {
val builder = StringBuilder()
val length = input.length
// Note: Smali loop starts from 1, accesses charAt(i-1), uses 'i' for shift
for (i in 1..length) {
val charIndex = i - 1 // Index of the character to process
var charCode = input[charIndex].code // Get character's Unicode code point
var base: Int
val mod: Int
when (input[charIndex]) {
in 'a'..'z' -> {
base = 'a'.code
mod = 26
charCode -= base // Normalize to 0-25
}
in 'A'..'Z' -> {
base = 'A'.code
mod = 26
charCode -= base // Normalize to 0-25
}
else -> {
// Handles other characters based on space ' '
// Corresponds to the Smali block L3
base = ' '.code
mod = 33
charCode -= base // Normalize relative to space
}
}
// Apply the position-dependent shift (subtracting loop counter 'i')
// The formula (val - shift + mod) % mod handles potential negative results correctly
val shiftedCode = (charCode - i + mod) % mod
// Add the base back to get the final character code
val finalCode = shiftedCode + base
builder.append(finalCode.toChar())
}
// Final replacement as seen in the Smali code
return builder.toString().replace('>', '_')
}
/**
* Unscrambles a string based on an index mapping array.
* If input is "cba" and map is [2, 0, 1], output is "abc".
*/
fun z(scrambledString: String, indexMap: IntArray): String {
val length = indexMap.size
// Create an inverse map: targetIndices[original_index] = current_index
val targetIndices = IntArray(length)
for (i in 0 until length) {
targetIndices[indexMap[i]] = i
}
val builder = StringBuilder(length)
// Build the unscrambled string using the inverse map
for (i in 0 until length) {
builder.append(scrambledString[targetIndices[i]])
}
return builder.toString()
}
fun decode(s: String, offsets: IntArray): String {
// Values produced by decompiler are offset
val decompilerMult = 2.0.pow(24).toInt()
// If you extract the values from the smali directly no offset is needed:
//val decompilerMult = 1
// 1. Unscramble the string using z()
val unscrambledString = z(s, offsets.map { it / decompilerMult }.toIntArray())
// 2. Decrypt the unscrambled string using w()
return w(unscrambledString)
}
}
StringDecryptor.decode("krltdwtl2eikrcbfsg>", intArrayOf(
100663296,
16777216,
117440512,
167772160,
150994944,
67108864,
268435456,
33554432,
50331648,
184549376,
218103808,
83886080,
134217728,
234881024,
0,
301989888,
201326592,
285212672,
251658240,
)) // Output: api.redditisfun.com
*/