Distok CutTheCord: Modular Discord Android client mod
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

140 lines
5.3 KiB

#!/bin/env python3
import re
import sys
import os
import subprocess
import datetime
import shutil
# Example invocation:
# python3 patchport.py 839 8.3.9g
# /home/ave/apks/com.discord-841/ /home/ave/cutthecordrepo/
from_versioncode = sys.argv[1]
from_versionname = sys.argv[2]
apk_folder = sys.argv[3]
cutthecord_folder = sys.argv[4]
debug = False
tmp_folder = "/tmp/patchport"
def modify_patch(patch_name, patch_path):
with open(patch_path) as f:
patch_content = f.read()
if patch_name in ["branding", "customversion"]:
patch_content = patch_content.replace(from_versioncode, to_versioncode)
patch_content = patch_content.replace(from_versionname, to_versionname)
if patch_name == "notrack":
# TODO: There's a risk here that we'll replace the nulled value
from_crashlytics_id = re_crashlytics.findall(patch_content)[0]
patch_content = patch_content.replace(from_crashlytics_id,
return patch_content
def fix_offset(patch_contents):
shutil.rmtree(tmp_folder, ignore_errors=True)
shutil.copytree(apk_folder, tmp_folder)
subprocess.run("patch -p1 --no-backup-if-mismatch --force",
shell=True, input=patch_contents, text=True,
out = subprocess.run(f"diff -crB {apk_folder} {tmp_folder}",
shell=True, input=patch_contents, text=True,
cwd=tmp_folder, capture_output=True)
shutil.rmtree(tmp_folder, ignore_errors=True)
return out.stdout
re_versioncode = re.compile(r'platformBuildVersionCode="([0-9]+)"')
re_versionname = re.compile(r'platformBuildVersionName="([0-9a-z.]+)"')
re_releasedate = re.compile(r'released on ([0-9]{4}-[0-9]{2}-[0-9]{2})')
re_crashlytics = re.compile(r'com\.crashlytics\.android\.build_id">([a-z0-9]'
# Get version code and name
with open(os.path.join(apk_folder, "AndroidManifest.xml")) as f:
file_contents = f.read()
to_versioncode = re_versioncode.findall(file_contents)[0]
to_versionname = re_versionname.findall(file_contents)[0]
# Get crashlytics build ID
with open(os.path.join(apk_folder, "res", "values", "strings.xml")) as f:
file_contents = f.read()
to_crashlytics_id = re_crashlytics.findall(file_contents)[0]
failures = []
for patch in os.listdir(os.path.join(cutthecord_folder, "patches")):
patch_path = os.path.join(cutthecord_folder, "patches", patch,
out_path = os.path.join(cutthecord_folder, "patches", patch,
readme_path = os.path.join(cutthecord_folder, "patches", patch, "README.md")
# Check if patch exists for from_version, if it doesn't, warn user
if not os.path.isfile(patch_path):
# Don't warn on instructional patches
if patch not in ["customfont", "customring",
"bettertm", "bettertmlight"]:
print(f"SKIPPED: No {from_versionname} version found for {patch}.")
# Get a modified version of the patch
patch_contents = modify_patch(patch, patch_path)
# Pass the new patch to patch command and get it to attempt to patch
out = subprocess.run("patch -p1 --dry-run --force", shell=True,
cwd=apk_folder, input=patch_contents, text=True,
# Check for issues
if "FAILED" in out.stdout:
print(f"FAILED: {patch} failed, please fix by hand.")
out_path += "-failed"
elif "offset" in out.stdout:
patch_contents = fix_offset(patch_contents)
print(f"WARNING: {patch} has offsets which were auto corrected.")
if debug:
# Add supported version to readme of that patch, hacky
# https://stackoverflow.com/a/35130508/3286892
with open(readme_path, 'r') as f:
readme_text = f.read().replace(f'- {from_versionname}',
f'- {from_versionname}\n'
f'- {to_versionname}')
with open(readme_path, "w") as f:
# Save ported patch
with open(out_path, "w") as f:
print(f"PORTED: {patch} was successfully ported.")
if failures:
print(f"Port complete. Following patches failed: {', '.join(failures)}")
ctcreadme_path = os.path.join(cutthecord_folder, "README.md")
# TODO: can we pull the correct date from distok?
out_datestamp = datetime.datetime.utcnow().strftime("%Y-%m-%d")
# Update readme with latest version, hacky
# https://stackoverflow.com/a/35130508/3286892
with open(ctcreadme_path, 'r') as f:
ctcr_text = f.read().replace(f'{from_versionname} ({from_versioncode})',
f'{to_versionname} ({to_versioncode})')
in_datestamp = re_releasedate.findall(ctcr_text)[0]
ctcr_text = ctcr_text.replace(in_datestamp, out_datestamp)
with open(ctcreadme_path, "w") as f:
print("Port complete. All patches completed successfully. "
"README.md was updated.")