Friday, July 3, 2015

increase soundcloud cache

tl;dr: i can't link to the apk because i'm not getting banned for you. you can repeat these steps yourself if you're interested. i can't stop anyone from posting a link in the comments. sorry!


background

soundcloud used to let you cache a lot of songs for offline listening. then they took it away. i'm half sure they have some reason for doing it that has vaguely to do with money dollars. but, hey, when you have a nice feature and you take it away, peeps get mad. the mad peeps are here:
https://www.soundcloudcommunity.com/soundcloud/topics/how-do-i-change-the-size-of-the-mobile-audio-cache-on-android (warning, internet drama)

many users recommend downloading 2.8.3, which you can find in the crybaby fest linked above. personally, i'm not content rolling with an ancient version like some bucolic hillbilly. nah. i must break the latest version and get all those sweet sweet features.


compare old and new

i reckoned the best way to find where the cache was controlled would be to compare 2.8.3 and 2.8.4, where it changed. maybe there was just a setting somewhere they changed, or a number was reduced from "big" to "tiny". here's how i did it:

  1. acquire 2.8.3 and 2.8.4. former was in link above, latter was from play store. i used this extension to download from play
  2. convert apks to jars with enjarify
  3. use latest jd-gui to get java for whole app (file -> save all sources)
  4. use diff -rq <dir1> <dir2> to get a sense of what changed
  5. give up on diff inspection because delta was huge
  6. randomly search for "cache" in 2.8.3
thank you soundcloud, for not obfuscating your app at all. it was much easier to find what i was looking for with class and variable names. it takes about an hour to setup proguard such that you still get debugging symbols, but hey, whatever, right?

found some likely places for cache control in 2.8.3, but it looked like there was an api rewrite between 2.8.3 and 2.8.4, so even if i found it, it wouldn't help me, because none of that code was used in later versions.

enjarify is supposed to be this snazzy new dex -> java class conversion tool. the results were "meh". i'm sure it's going to be very good eventually, and i have more faith in the professionalism of the devs of enjarify than, say, other devs.


educated guess searching

started searching around for the word "cache". there's quite a lot of caching going on. i poked around a few files that looked like they were just setting up LRU caches for something, and those looked promising, but then i found: Lcom/soundcloud/android/playback/CacheConfig;.


results

public class CacheConfig
{

  // ~524 megs
  static final long MAX_SIZE_BYTES = 524288000L;

  // ~63 megs, aka "fuck you lol"
  static final long MIN_SIZE_BYTES = 62914560L;

  public static long getCacheSize(@Nullable String country) {
    if ((ScTextUtils.isBlank(country)) || (Locale.US.getCountry().equalsIgnoreCase(country))
        || (Locale.GERMANY.getCountry().equalsIgnoreCase(country))
        || (Locale.FRANCE.getCountry().equalsIgnoreCase(country))
        || (Locale.UK.getCountry().equalsIgnoreCase(country))) {
      return MIN_SIZE_BYTES;
    }

    return MAX_SIZE_BYTES;
  }

}

boom sha ka la ka. this must be it. if you're in the US, Germany, France, or UK, you get burned with a tiny, micro cache. here's what the fix looks like:
.method public static getCacheSize(Ljava/lang/String;)J
    .registers 3
    .param p0    # Ljava/lang/String;
        .annotation build Landroid/support/annotation/Nullable;
        .end annotation
    .end param

    .prologue
    .line 15
    # return "totally random" number, 555555
    const-wide/32 v0, 0x4fb10040
    return-wide v0

    invoke-static {p0}, Lcom/soundcloud/android/utils/ScTextUtils;->isBlank(Ljava/lang/String;)Z
    # ...

unfortunately, this creates new problems. as soon as you modify the code, you have to update the dex. if you update the dex, you break the signature and have to resign. if you resign, it breaks facebook logins, and probably some other stuff, because they uses signatures to generate a token that identifies which app is trying to do the login. luckily, antilvl knows how to handle spoofing signatures.