Skip to content

Instantly share code, notes, and snippets.

@zb3
Created December 23, 2022 17:24
Show Gist options
  • Save zb3/2f0cb7f0029d34322be4b7b6e0565906 to your computer and use it in GitHub Desktop.
Save zb3/2f0cb7f0029d34322be4b7b6e0565906 to your computer and use it in GitHub Desktop.
Icy Tower Classic Mobile Patcher

Icy Tower Classic Mobile Patcher

If you suck at this game as much as I do (assuming that's even possible) and want to practice playing at the higher floors without constantly having to start from the first one, the idea of using the revive functionality probably came to your head. However, I'm certain the idea of paying real money for it most probably didn't (hopefully...).

Here's the brilliant, free solution: Icy Tower Classic Mobile Patcher. It will give you 2147483647 coins so that you can revive yourself 31 times in one game (but don't ask me why)...

Normally the solution would be to patch the APK to allow an infinite number of revives... But that would be easy, universal, reliable and extensible. You gotta admit that doesn't sound so "zb3"...

Therefore this solution is:

  • not easy - you need to execute the patcher on your PC while the device is connected each time you want to increase your coins amount
  • not universal - not only does it require a rooted device, it even requires "adb root" to be working (well, at least those commands below)... oh, and of course, your PC needs to run Linux.
  • not reliable - encryption keys are tied to a single game version, it might not work on other versions
  • not extensible - don't even think about trying to patch game logic this way - it's just not possible, we can only patch shared pref values.

Now, this indeed sounds like something made by zb3! Let's go!

Usage

  1. Ensure your device is rooted and adb root works, you have a linux PC with Python 3 installed.
  2. Ensure you have the game installed (package name com.freelunchdesign.icytowerclassic)
  3. Ensure the game is not running.
  4. Save the icytower.py file somewhere and cd into that directory.
  5. Run this on your PC:
adb shell cat /data/data/com.freelunchdesign.icytowerclassic/shared_prefs/com.freelunchdesign.icytowerclassic.xml | python icytower.py >/tmp/patched.xml
adb push /tmp/patched.xml /data/data/com.freelunchdesign.icytowerclassic/shared_prefs/com.freelunchdesign.icytowerclassic.xml
  1. Enjoy! (for a moment lol, then you need to run step 4 again)

FAQ

Does this crap actually work?

If the MD5 hash of your Icy Tower Classic apk file is 53c2619abffc325eb0fa17de8c9b9ba2, then yes, it actually does work. Not that it helps me play the game much coz I still crash almost immediately after the revive, but still - it actually works.

Are you crazy?

Yes.

Why did you write this useless crap?

My primary goal was to waste time so I could avoid doing things that I actually needed to do. While I don't usually accomplish any goals, this time I succeeded. In fact, the amount of time wasted even exceeded my wildest expectations!

I didn't read anything. TLDR?

Ensure the game is not running (Force Stop might be needed) and run this with root privileges:

sed -i -E 's/<string name="uFuga2.PStore.HCLocal">[^<]+<\/string>/<string name="uFuga2.PStore.HCLocal">ePuuLEFq3TrostMIvyvKjvN1BRCKoCB8RNjNm3Qs0qOUGREGY7bFrg==<\/string>/' /data/data/com.freelunchdesign.icytowerclassic/shared_prefs/com.freelunchdesign.icytowerclassic.xml
import io
import sys
import base64
import struct
import hashlib
import xml.etree.ElementTree as ET
SCRAMBLE_KEY = bytes([89, 238, 113, 21, 154, 42, 140, 20])
MAGIC_BYTES = bytes([36, 104])
NEW_CURRENCY = 2147483647
def unscramble(data):
data = bytearray(data)
for x in range(len(data)-1, 0, -1):
data[x] ^= data[x-1] ^ SCRAMBLE_KEY[x % len(SCRAMBLE_KEY)]
data[0] ^= (len(data) & 0xff) ^ SCRAMBLE_KEY[0]
return bytes(data)
def scramble(data):
data = bytearray(data)
data[0] ^= (len(data) & 0xff) ^ SCRAMBLE_KEY[0]
for x in range(1, len(data)):
data[x] ^= data[x-1] ^ SCRAMBLE_KEY[x % len(SCRAMBLE_KEY)]
return bytes(data)
def encode7_io(bio, value):
while value >= 0x80:
bio.write(bytes([(value | 0x80) & 0xff]))
value >>= 7
bio.write(bytes([value & 0xff]))
def decode7_io(bio):
ret = 0
shift = 0
while True:
value = bio.read(1)[0]
ret |= (value & 0x7f) << shift
shift += 7
if not (value & 0x80):
break
return ret
def encode_str_io(bio, value):
value = value.encode()
encode7_io(bio, len(value))
bio.write(value)
def decode_str_io(bio):
vl = decode7_io(bio)
value = bio.read(vl)
return value.decode()
def serialize(kv):
writer = io.BytesIO()
writer.write(struct.pack("<i", len(kv)))
for key, value in kv.items():
encode_str_io(writer, key)
if isinstance(value, str):
writer.write(struct.pack("<B", 1))
encode_str_io(writer, value)
elif isinstance(value, int):
writer.write(struct.pack("<B", 2))
writer.write(struct.pack("<i", value))
elif isinstance(value, float):
writer.write(struct.pack("<B", 3))
writer.write(struct.pack("<f", value))
elif isinstance(value, bool):
writer.write(struct.pack("<B", 4))
writer.write(struct.pack("<?", value))
writer.seek(0)
return writer.read()
def unserialize(data):
ret = {}
reader = io.BytesIO(data)
num = struct.unpack("<i", reader.read(4))[0]
while num > 0:
num -= 1
key = decode_str_io(reader)
value = None
switch = struct.unpack("<B", reader.read(1))[0]
if switch == 1:
value = decode_str_io(reader)
elif switch == 2:
value = struct.unpack("<i", reader.read(4))[0]
elif switch == 3:
value = struct.unpack("<f", reader.read(4))[0]
elif switch == 4:
value = struct.unpack("<?", reader.read(1))[0]
ret[key] = value
return ret
def save(data):
data = serialize(data)
ret = MAGIC_BYTES + data
return base64.b64encode(scramble(hashlib.md5(ret).digest() + ret)).decode()
def load(data):
data = base64.b64decode(data)
data = unscramble(data)
return unserialize(data[16+len(MAGIC_BYTES):])
def find_pref(xml, name):
for string in xml.findall('string'):
if string.get('name') == name:
return string
def main():
tree = ET.fromstring(sys.stdin.buffer.read())
el = find_pref(tree, 'uFuga2.PStore.HCLocal')
kv = load(el.text)
print(kv, file=sys.stderr)
kv['HardCurrency'] = NEW_CURRENCY
el.text = save(kv)
sys.stdout.buffer.write(ET.tostring(tree, encoding='utf8', xml_declaration=True))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment