forked from distok/cutthecord
Dockerise
This commit is contained in:
parent
8ec2dd75c2
commit
55e2e94b63
3921 changed files with 178 additions and 111 deletions
2
resources/scripts/ctcci/.gitignore
vendored
Normal file
2
resources/scripts/ctcci/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
ctcconfig.py
|
||||
__pycache__
|
25
resources/scripts/ctcci/cleanartifacts.py
Normal file
25
resources/scripts/ctcci/cleanartifacts.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import os
|
||||
from ctcconfig import *
|
||||
|
||||
verdata = {}
|
||||
|
||||
# Get all APKs from the dir
|
||||
for file in os.listdir(RESULT_FOLDER):
|
||||
if not file.startswith("cutthecord-"):
|
||||
continue
|
||||
|
||||
filedata = file.replace("cutthecord-", "").replace(".apk", "").split("-")
|
||||
if filedata[1] not in verdata:
|
||||
verdata[filedata[1]] = []
|
||||
|
||||
verdata[filedata[1]].append(int(filedata[0]))
|
||||
|
||||
# Delete the older APKs
|
||||
for branch in verdata:
|
||||
vers = verdata[branch]
|
||||
vers.sort()
|
||||
if len(vers) <= CLEAN_ARTIFACT_KEEP_COUNT:
|
||||
continue
|
||||
for ver in vers[0:-1 * CLEAN_ARTIFACT_KEEP_COUNT]:
|
||||
file = os.path.join(RESULT_FOLDER, f"cutthecord-{ver}-{branch}.apk")
|
||||
os.unlink(file)
|
253
resources/scripts/ctcci/ctcci.py
Normal file
253
resources/scripts/ctcci/ctcci.py
Normal file
|
@ -0,0 +1,253 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import json
|
||||
import subprocess
|
||||
from ctcconfig import *
|
||||
|
||||
print("Welcome to CutTheCord CI :)")
|
||||
|
||||
BRANCH = sys.argv[1]
|
||||
PATCHES = sys.argv[2:]
|
||||
FORCE = False
|
||||
DEFAULT_PATCHES = ["necessary", "branding", "customversion"]
|
||||
OPTIONAL_PATCHES = ["necessary", "blobs"]
|
||||
|
||||
|
||||
def patch(patch_file, workdir, patch_name=""):
|
||||
if not os.path.exists(patch_file):
|
||||
# Allow missing optional patches
|
||||
if patch_name in OPTIONAL_PATCHES:
|
||||
return
|
||||
|
||||
raise FileNotFoundError(f"No patches with name \"{patch_file}\" :(")
|
||||
|
||||
print(f"Applying {patch_file}")
|
||||
|
||||
subprocess.run(f"patch -p1 --no-backup-if-mismatch -i {patch_file}",
|
||||
shell=True, check=True, cwd=workdir)
|
||||
|
||||
|
||||
# Wipe and recreate the working folder
|
||||
if os.path.exists(WORK_APK_PATH):
|
||||
shutil.rmtree(WORK_APK_PATH)
|
||||
|
||||
os.environ["DISTOK_EXTRACTED_DISCORD_PATH"] = WORK_APK_PATH
|
||||
|
||||
os.makedirs(WORK_FOLDER, exist_ok=True)
|
||||
|
||||
with open(os.path.join(REPO_FOLDER, "resources/patchport-state.json")) as f:
|
||||
STATE = json.load(f)
|
||||
VERSION = STATE["versioncode"]
|
||||
|
||||
BASE_APK_PATH = os.path.join(WORK_FOLDER, f"discord-base-{VERSION}")
|
||||
|
||||
# Prepare names of input and output APKs
|
||||
INPUT_FILE = os.path.join(DISTOK_FOLDER, "android",
|
||||
f"{PACKAGE_ID}-{VERSION}.apk")
|
||||
# OUTPUT_FILE = os.path.join(RESULT_FOLDER,
|
||||
# f"cutthecord-{VERSION}-{'_'.join(PATCHES)}.apk")
|
||||
OUTPUT_FILE = os.path.join(RESULT_FOLDER,
|
||||
f"cutthecord-{VERSION}-{BRANCH}.apk")
|
||||
|
||||
# Add necessary patches to the list of patches that will be applied
|
||||
# Important to have this after the output file name generation
|
||||
# otherwise it'll include it, which is not wanted
|
||||
PATCHES = DEFAULT_PATCHES + PATCHES
|
||||
|
||||
print(f"Branch: {BRANCH}, output name: {OUTPUT_FILE}")
|
||||
|
||||
# Check if the version is already patched, if it is exit
|
||||
if not FORCE and os.path.exists(OUTPUT_FILE):
|
||||
print("This version is already patched, bye!")
|
||||
sys.exit(1)
|
||||
|
||||
if DO_GITPULL:
|
||||
# Update cutthecord
|
||||
subprocess.run("git pull", shell=True, cwd=REPO_FOLDER)
|
||||
|
||||
# Extract the APK if it's not already extracted to base cache
|
||||
if not os.path.exists(BASE_APK_PATH):
|
||||
subprocess.run(f"{APKTOOL_BIN} d --no-dummy {INPUT_FILE} -o {BASE_APK_PATH} -f",
|
||||
shell=True,
|
||||
cwd=WORK_FOLDER)
|
||||
|
||||
# Copy the base cache to work on it
|
||||
shutil.copytree(BASE_APK_PATH, WORK_APK_PATH)
|
||||
|
||||
# Go through patches and apply every single one of them
|
||||
for patch_name in PATCHES:
|
||||
pre_script = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, f"{VERSION}-pre.sh")
|
||||
if os.path.exists(pre_script):
|
||||
subprocess.run(f"bash {pre_script}",
|
||||
shell=True,
|
||||
cwd=WORK_APK_PATH)
|
||||
|
||||
# Apply custom emoji patches
|
||||
if patch_name in ["mutant", "blobs"]:
|
||||
print(f"Applying {patch_name} emoji patch")
|
||||
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, "emojireplace.py")
|
||||
subprocess.run(f"{PYTHON_BIN} {patch_script}",
|
||||
shell=True,
|
||||
cwd=WORK_APK_PATH)
|
||||
|
||||
# Apply custom emoji patches
|
||||
elif patch_name == "customtheme":
|
||||
print(f"Applying splash patch")
|
||||
splash = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, "asset_loading.png")
|
||||
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, "fixsplash.sh")
|
||||
|
||||
subprocess.run(f"bash {patch_script} {splash}",
|
||||
shell=True,
|
||||
cwd=WORK_APK_PATH)
|
||||
|
||||
patch_file = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, f"{VERSION}.patch")
|
||||
|
||||
# Apply custom version patches
|
||||
if patch_name == "customversion":
|
||||
print(f"Applying custom version")
|
||||
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, "addpatch.py")
|
||||
|
||||
subprocess.run(f"{PYTHON_BIN} {patch_script} {patch_file} "
|
||||
f"{' '.join(PATCHES)}",
|
||||
shell=True,
|
||||
cwd=WORK_APK_PATH)
|
||||
|
||||
patch(patch_file.replace(".patch", "-custom.patch"), WORK_APK_PATH)
|
||||
# Apply branding patches
|
||||
elif patch_name == "branding":
|
||||
print(f"Applying branding icon patch")
|
||||
if BRANCH in ICONS:
|
||||
shutil.copyfile(ICONS[BRANCH],
|
||||
os.path.join(WORK_APK_PATH, "res",
|
||||
"mipmap-xxxhdpi", "logo_debug.png"))
|
||||
elif "default" in ICONS:
|
||||
shutil.copyfile(ICONS["default"],
|
||||
os.path.join(WORK_APK_PATH, "res",
|
||||
"mipmap-xxxhdpi", "logo_debug.png"))
|
||||
|
||||
if BRANCH in DYN_ICONS:
|
||||
shutil.copyfile(DYN_ICONS[BRANCH]["bg"],
|
||||
os.path.join(WORK_APK_PATH, "res",
|
||||
"mipmap-xxxhdpi",
|
||||
"ic_launcher_background.png"))
|
||||
shutil.copyfile(DYN_ICONS[BRANCH]["fg"],
|
||||
os.path.join(WORK_APK_PATH, "res",
|
||||
"mipmap-xxxhdpi",
|
||||
"ic_launcher_foreground.png"))
|
||||
elif "default" in ICONS:
|
||||
shutil.copyfile(DYN_ICONS["default"]["bg"],
|
||||
os.path.join(WORK_APK_PATH, "res",
|
||||
"mipmap-xxxhdpi",
|
||||
"ic_launcher_background.png"))
|
||||
shutil.copyfile(DYN_ICONS["default"]["fg"],
|
||||
os.path.join(WORK_APK_PATH, "res",
|
||||
"mipmap-xxxhdpi",
|
||||
"ic_launcher_foreground.png"))
|
||||
|
||||
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, "customicon.sh")
|
||||
|
||||
subprocess.run(f"bash {patch_script}", shell=True, cwd=WORK_APK_PATH)
|
||||
|
||||
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, "customdynamicicon.sh")
|
||||
|
||||
subprocess.run(f"bash {patch_script}", shell=True, cwd=WORK_APK_PATH)
|
||||
|
||||
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, "addpatch.py")
|
||||
|
||||
# Hell code
|
||||
app_name = APP_NAMES.get(BRANCH, APP_NAMES.get("default", "CutTheCord"))
|
||||
|
||||
subprocess.run(f"{PYTHON_BIN} {patch_script} "
|
||||
f"{patch_file} {app_name} {BRANCH}",
|
||||
shell=True,
|
||||
cwd=WORK_APK_PATH)
|
||||
|
||||
patch(patch_file.replace(".patch", "-custom.patch"), WORK_APK_PATH)
|
||||
elif patch_name in ["bettertm", "bettertmlight"]:
|
||||
print(f"Applying bettertm emoji patch")
|
||||
patch_dir = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name)
|
||||
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, "bettertm.sh")
|
||||
subprocess.run(f"bash {patch_script} {patch_dir}",
|
||||
shell=True,
|
||||
cwd=WORK_APK_PATH)
|
||||
# Apply custom ringtone
|
||||
elif patch_name == "customring":
|
||||
print(f"Applying custom ringtone")
|
||||
if BRANCH in RINGTONES:
|
||||
CUSTOM_RINGTONE = RINGTONES[BRANCH]
|
||||
else:
|
||||
CUSTOM_RINGTONE = RINGTONES["default"]
|
||||
|
||||
shutil.copyfile(CUSTOM_RINGTONE,
|
||||
os.path.join(WORK_APK_PATH, "res",
|
||||
"raw", "call_ringing.mp3"))
|
||||
# Apply custom fonts
|
||||
elif patch_name == "customfont":
|
||||
print(f"Applying custom font")
|
||||
if BRANCH in FONTS:
|
||||
fonts = FONTS[BRANCH]
|
||||
else:
|
||||
fonts = FONTS["default"]
|
||||
|
||||
for font in fonts:
|
||||
shutil.copyfile(fonts[font],
|
||||
os.path.join(WORK_APK_PATH, "res",
|
||||
"font", font))
|
||||
# Apply any other patches
|
||||
else:
|
||||
patch(patch_file, WORK_APK_PATH, patch_name)
|
||||
|
||||
post_script = os.path.join(REPO_FOLDER, "resources/patches",
|
||||
patch_name, f"{VERSION}-post.sh")
|
||||
if os.path.exists(post_script):
|
||||
subprocess.run(f"bash {post_script}",
|
||||
shell=True,
|
||||
cwd=WORK_APK_PATH)
|
||||
|
||||
# Pack the APK
|
||||
subprocess.run(f"{APKTOOL_BIN} b discord",
|
||||
shell=True,
|
||||
cwd=WORK_FOLDER)
|
||||
|
||||
APK_PATH = os.path.join(WORK_FOLDER, "discord",
|
||||
"dist", f"{PACKAGE_ID}-{VERSION}.apk")
|
||||
|
||||
# Sign the APK
|
||||
if DO_APKSIGNER:
|
||||
subprocess.run(f"apksigner sign --ks {KEYSTORE_FILE} "
|
||||
f"--ks-key-alias {KEYSTORE_ALIAS} "
|
||||
f"--ks-pass pass:{KEYSTORE_PASS} "
|
||||
f"{APK_PATH}",
|
||||
shell=True,
|
||||
cwd=WORK_FOLDER)
|
||||
else:
|
||||
subprocess.run(f"jarsigner -storepass {KEYSTORE_PASS} -keystore "
|
||||
f"{KEYSTORE_FILE} {APK_PATH} {KEYSTORE_ALIAS}",
|
||||
shell=True,
|
||||
cwd=WORK_FOLDER)
|
||||
|
||||
# Copy the result file
|
||||
shutil.copyfile(APK_PATH, OUTPUT_FILE)
|
||||
|
||||
if DO_FDROID:
|
||||
# Do fdroid magic
|
||||
subprocess.run(f"fdroid update -c",
|
||||
shell=True,
|
||||
cwd=FDROID_FOLDER)
|
||||
|
||||
# Wipe caches
|
||||
shutil.rmtree(WORK_APK_PATH)
|
||||
print("CutTheCord CI has fulfilled its purpose.")
|
79
resources/scripts/ctcci/ctcconfig.example.py
Normal file
79
resources/scripts/ctcci/ctcconfig.example.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
import os
|
||||
|
||||
APKTOOL_BIN = "java -jar /opt/ctc/tools/apktool.jar" # use latest
|
||||
|
||||
# APKs must be placed under $DISTOK_FOLDER/android/$PACKAGE_ID-$VERSION_NUMBER.apk
|
||||
# Example: /home/ave/distok/android/com.discord-909.apk
|
||||
DISTOK_FOLDER = "/opt/ctc/gitrepo/resources/distok"
|
||||
|
||||
# Set if F-Droid repo should be automatically updated or not
|
||||
# If set to False, you can leave FDROID_FOLDER empty
|
||||
DO_FDROID = True
|
||||
FDROID_FOLDER = "/opt/ctc/gitrepo/resources/fdroid"
|
||||
|
||||
# This is where APKs will be placed
|
||||
RESULT_FOLDER = FDROID_FOLDER + "/repo"
|
||||
|
||||
# Repo for CutTheCord (https://gitdab.com/distok/cutthecord)
|
||||
DO_GITPULL = True
|
||||
REPO_FOLDER = "/opt/ctc/gitrepo"
|
||||
|
||||
# If false, jarsigner will be used for signing the apk.
|
||||
DO_APKSIGNER = True
|
||||
|
||||
# Keystore file, alias and pass. Required.
|
||||
KEYSTORE_FILE = "/opt/ctc/gitrepo/resources/keystore.jks"
|
||||
KEYSTORE_ALIAS = "test"
|
||||
KEYSTORE_PASS = "password"
|
||||
|
||||
# Set this to the python version you want to use. Needs to be 3.6+.
|
||||
PYTHON_BIN = "python3"
|
||||
|
||||
# Folder where the apk will be extracted to, patched in, and packed back on
|
||||
# You're not recommended to touch WORK_APK_PATH.
|
||||
WORK_FOLDER = "/tmp/cutthecord"
|
||||
WORK_APK_PATH = os.path.join(WORK_FOLDER, "discord")
|
||||
|
||||
# Package ID for discord. You should probably not touch this.
|
||||
PACKAGE_ID = "com.discord"
|
||||
|
||||
# Set to force if you want builds to be done
|
||||
# even if there's already a build for this version
|
||||
FORCE = False
|
||||
|
||||
# Custom ringtones, default = applied to all the ones not explicitly stated
|
||||
RINGTONES = {"ave": "/opt/ctc/gitrepo/resources/ringtones/sans.mp3",
|
||||
"default": "/opt/ctc/gitrepo/resources/ringtones/removeskype.mp3"}
|
||||
|
||||
# Custom icons, default = applied to all the ones not explicitly stated
|
||||
ICONS = {"ave": "/opt/ctc/gitrepo/resources/icons/ctclogoave.png",
|
||||
"default": "/opt/ctc/gitrepo/resources/icons/ctclogo.png"}
|
||||
|
||||
# Custom dynamic icons, default = applied to all the ones not explicitly stated
|
||||
DYN_ICONS = {"ave": {"bg": "/opt/ctc/gitrepo/resources/icons/bg.png",
|
||||
"fg": "/opt/ctc/gitrepo/resources/icons/fg.png"},
|
||||
"default": {"bg": "/opt/ctc/gitrepo/resources/icons/dcbg.png",
|
||||
"fg": "/opt/ctc/gitrepo/resources/icons/dcfg.png"}}
|
||||
|
||||
# Custom app names, default = applied to all the ones not explicitly stated
|
||||
# Needs to be one word, __ gets replaced with space
|
||||
APP_NAMES = {"ave": "CutTheCord__Ave",
|
||||
"litecord": "Litecord",
|
||||
"test": "CTC__Testing",
|
||||
"default": "CutTheCord"}
|
||||
|
||||
# Custom fonts, default = applied to all the ones not explicitly stated
|
||||
FONTS = {"ellie": {"whitney_bold.ttf": "/opt/ctc/gitrepo/resources/fonts/GoogleSans-Bold.ttf",
|
||||
"whitney_semibold.ttf": "/opt/ctc/gitrepo/resources/fonts/GoogleSans-Medium.ttf",
|
||||
"whitney_medium.ttf": "/opt/ctc/gitrepo/resources/fonts/GoogleSans-Regular.ttf"},
|
||||
"dyslexic": {"whitney_bold.ttf": "/opt/ctc/gitrepo/resources/fonts/OpenDyslexic3-Bold.ttf",
|
||||
"whitney_semibold.ttf": "/opt/ctc/gitrepo/resources/fonts/OpenDyslexic3-Bold.ttf",
|
||||
"whitney_medium.ttf": "/opt/ctc/gitrepo/resources/fonts/OpenDyslexic3-Regular.ttf"},
|
||||
"murm": {"whitney_bold.ttf": "/opt/ctc/gitrepo/resources/fonts/comicbd.ttf",
|
||||
"whitney_semibold.ttf": "/opt/ctc/gitrepo/resources/fonts/comicbd.ttf",
|
||||
"whitney_medium.ttf": "/opt/ctc/gitrepo/resources/fonts/comic.ttf"},
|
||||
"ave": {"sourcecodepro_semibold.ttf": "/opt/ctc/gitrepo/resources/fonts/comic.ttf"},
|
||||
"default": {}}
|
||||
|
||||
# Amount of last builds cleanartifacts.py should keep
|
||||
CLEAN_ARTIFACT_KEEP_COUNT = 2
|
107
resources/scripts/patchbisect.py
Normal file
107
resources/scripts/patchbisect.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
#!/bin/env python3
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
# Example invocation:
|
||||
# python3 patchbisect.py /home/ave/workbench/ctc/com.discord-968
|
||||
|
||||
# This is really bad code that I wrote between 3am and 6am
|
||||
# Please do not criticize or judge my programming skills by this.
|
||||
|
||||
apk_folder = sys.argv[1]
|
||||
cutthecord_folder = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
def apply_patch(patch, reverse=False):
|
||||
patch_path = os.path.join(cutthecord_folder, "resources/patches", patch,
|
||||
f"{to_versioncode}.patch")
|
||||
with open(patch_path) as f:
|
||||
patch_contents = f.read()
|
||||
cmd = f"patch -p1 {'-R' if reverse else ''} --no-backup-if-mismatch --force"
|
||||
subprocess.run(cmd,
|
||||
shell=True, input=patch_contents, text=True,
|
||||
cwd=apk_folder, capture_output=True)
|
||||
|
||||
|
||||
re_versioncode_yml = re.compile(r'versionCode: \'([0-9]+)\'')
|
||||
re_versionname_yml = re.compile(r'versionName: (.+)$')
|
||||
|
||||
|
||||
# Get version code and name
|
||||
with open(os.path.join(apk_folder, "apktool.yml")) as f:
|
||||
file_contents = f.read()
|
||||
to_versioncode = re_versioncode_yml.findall(file_contents)[0]
|
||||
to_versionname = re_versionname_yml.findall(file_contents)[0]
|
||||
|
||||
|
||||
unsure = []
|
||||
|
||||
# Load list of patches
|
||||
for patch in os.listdir(os.path.join(cutthecord_folder, "resources/patches")):
|
||||
# Ignore non-dirs
|
||||
if not os.path.isdir(os.path.join(cutthecord_folder, "resources/patches", patch)):
|
||||
continue
|
||||
|
||||
patch_path = os.path.join(cutthecord_folder, "resources/patches", patch,
|
||||
f"{to_versioncode}.patch")
|
||||
|
||||
# Check if patch exists for from_version, if it doesn't, warn user
|
||||
if not os.path.isfile(patch_path) and patch not in ["necessary"]:
|
||||
# Don't warn on instructional patches
|
||||
if patch not in ["customfont", "customring",
|
||||
"bettertm", "bettertmlight",
|
||||
"blobs"]:
|
||||
print(f"SKIPPED: No {to_versionname} version found for {patch}.")
|
||||
continue
|
||||
|
||||
# Append patch name to the list
|
||||
unsure.append(patch)
|
||||
|
||||
|
||||
failcount = 1
|
||||
applied = []
|
||||
good = []
|
||||
bad = []
|
||||
|
||||
while unsure:
|
||||
print("So far...")
|
||||
print(f"Unsure patches: {', '.join(unsure)}")
|
||||
print(f"Good patches: {', '.join(good)}")
|
||||
print(f"Bad patches: {', '.join(bad)}")
|
||||
count_this_round = int(len(unsure) / failcount)
|
||||
for i in range(0, count_this_round):
|
||||
patch_name = unsure[i]
|
||||
print(f"Applying: {patch_name}")
|
||||
apply_patch(patch_name)
|
||||
applied.append(patch_name)
|
||||
|
||||
# Very cursed lines of code.
|
||||
is_working = ""
|
||||
while is_working not in ["y", "n"]:
|
||||
is_working = input("Is the current patchset working? (y/n) ")
|
||||
|
||||
# <3 3.8
|
||||
# while (is_working := input("Is the current patchset working? (y/n) ")) not in ["y", "n"]:
|
||||
# continue
|
||||
|
||||
if is_working == "y":
|
||||
good.extend(applied)
|
||||
unsure = list(set(unsure) - set(applied))
|
||||
failcount = 1
|
||||
elif count_this_round > 1:
|
||||
failcount += 1
|
||||
else:
|
||||
bad.extend(applied)
|
||||
unsure = list(set(unsure) - set(applied))
|
||||
failcount = 1
|
||||
|
||||
for patch_name in applied:
|
||||
apply_patch(patch_name, True)
|
||||
|
||||
applied.clear()
|
||||
|
||||
print("Done, all patches identified as good or bad.")
|
||||
print(f"Good: {', '.join(good)}")
|
||||
print(f"Bad: {', '.join(bad)}")
|
262
resources/scripts/patchport.py
Normal file
262
resources/scripts/patchport.py
Normal file
|
@ -0,0 +1,262 @@
|
|||
#!/bin/env python3
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import datetime
|
||||
import shutil
|
||||
|
||||
# Example invocation:
|
||||
# python3 patchport.py /home/ave/apks/com.discord-900/
|
||||
|
||||
with open("/opt/ctc/gitrepo/resources/patchport-state.json", "r") as f:
|
||||
jin = json.load(f)
|
||||
from_versioncode = jin["versioncode"]
|
||||
from_versionname = jin["versionname"]
|
||||
|
||||
apk_folder = sys.argv[1]
|
||||
cutthecord_folder = os.path.dirname(os.path.realpath(__file__))
|
||||
debug = False
|
||||
tmp_folder = "/tmp/patchport"
|
||||
|
||||
if debug:
|
||||
print(f"ctc folder: {cutthecord_folder}")
|
||||
|
||||
|
||||
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,
|
||||
to_crashlytics_id)
|
||||
return patch_content
|
||||
|
||||
|
||||
def apply_patch(patch_contents):
|
||||
subprocess.run("patch -p1 --no-backup-if-mismatch --force",
|
||||
shell=True, input=patch_contents, text=True,
|
||||
cwd=apk_folder, capture_output=True)
|
||||
|
||||
|
||||
def fix_offset(patch_contents):
|
||||
# OH GOD OH FUCK
|
||||
shutil.rmtree(tmp_folder, ignore_errors=True)
|
||||
os.makedirs(tmp_folder)
|
||||
patch_lines = patch_contents.splitlines()
|
||||
for line in patch_lines:
|
||||
if "diff -crB" in line:
|
||||
patch_target = line.split(" ")[2].replace("from/", "")
|
||||
if not os.path.exists(os.path.join(apk_folder, patch_target)):
|
||||
return False
|
||||
os.makedirs(os.path.dirname(os.path.join(tmp_folder, patch_target)), exist_ok=True)
|
||||
shutil.copy(os.path.join(apk_folder, patch_target), os.path.join(tmp_folder, patch_target))
|
||||
# shutil.copytree(apk_folder, tmp_folder)
|
||||
subprocess.run("patch -p1 --no-backup-if-mismatch --force",
|
||||
shell=True, input=patch_contents, text=True,
|
||||
cwd=tmp_folder, capture_output=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)
|
||||
actual_difflines = []
|
||||
for line in out.stdout.splitlines():
|
||||
if line[0:4] != "Only":
|
||||
actual_difflines.append(line)
|
||||
patch_out = ("\n".join(actual_difflines)+"\n").replace(apk_folder, "from").replace(tmp_folder, "to")
|
||||
|
||||
return patch_out
|
||||
|
||||
|
||||
def make_necessary(version_name, version_code):
|
||||
# OH GOD OH FUCK
|
||||
shutil.rmtree(tmp_folder, ignore_errors=True)
|
||||
os.makedirs(tmp_folder)
|
||||
shutil.copy(os.path.join(apk_folder, "AndroidManifest.xml"), os.path.join(tmp_folder, "AndroidManifest.xml"))
|
||||
|
||||
# Set version code and name
|
||||
# Due to https://github.com/iBotPeaches/Apktool/issues/2046
|
||||
# Code based on https://stackoverflow.com/a/4128192/3286892
|
||||
with open(os.path.join(tmp_folder, "AndroidManifest.xml")) as fin:
|
||||
filec = fin.read()
|
||||
incorrect_versioncode = re_versioncode_xml.findall(filec)[0]
|
||||
filec = filec.replace(incorrect_versioncode,
|
||||
f'platformBuildVersionCode="{to_versioncode}"')
|
||||
incorrect_versionname = re_versionname_xml.findall(filec)[0]
|
||||
filec = filec.replace(incorrect_versionname,
|
||||
f'platformBuildVersionName="{to_versionname}"')
|
||||
with open(os.path.join(tmp_folder, "AndroidManifest.xml"), "w") as fout:
|
||||
fout.write(filec)
|
||||
|
||||
out = subprocess.run(f"diff -crB {apk_folder} {tmp_folder}",
|
||||
shell=True, text=True,
|
||||
cwd=tmp_folder, capture_output=True)
|
||||
shutil.rmtree(tmp_folder, ignore_errors=True)
|
||||
actual_difflines = []
|
||||
for line in out.stdout.splitlines():
|
||||
if line[0:4] != "Only":
|
||||
actual_difflines.append(line)
|
||||
patch_out = ("\n".join(actual_difflines)+"\n").replace(apk_folder, "from").replace(tmp_folder, "to")
|
||||
return patch_out
|
||||
|
||||
|
||||
re_versioncode_xml = re.compile(r'(platformBuildVersionCode="[0-9]+")')
|
||||
re_versionname_xml = re.compile(r'(platformBuildVersionName="[0-9a-z.]+")')
|
||||
|
||||
re_versioncode_yml = re.compile(r'versionCode: \'([0-9]+)\'')
|
||||
re_versionname_yml = re.compile(r'versionName: \'?(.+?)\'?$')
|
||||
|
||||
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]'
|
||||
r'{8}-?[a-z0-9]{4}-?[a-z0-9]{4}-?[a-z0-9]{4}-?'
|
||||
r'[a-z0-9]{12})</string>')
|
||||
|
||||
|
||||
# Get version code and name
|
||||
with open(os.path.join(apk_folder, "apktool.yml")) as f:
|
||||
file_contents = f.read()
|
||||
to_versioncode = re_versioncode_yml.findall(file_contents)[0]
|
||||
to_versionname = re_versionname_yml.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, "resources/patches")):
|
||||
if debug:
|
||||
print(f"going over patch: {patch}")
|
||||
|
||||
# Ignore non-dirs
|
||||
if not os.path.isdir(os.path.join(cutthecord_folder, "resources/patches", patch)):
|
||||
if debug:
|
||||
print(f"patch is not a folder, skipping: {patch}")
|
||||
continue
|
||||
|
||||
pre_in_path = os.path.join(cutthecord_folder, "resources/patches", patch,
|
||||
f"{from_versioncode}-pre.sh")
|
||||
post_in_path = os.path.join(cutthecord_folder, "resources/patches", patch,
|
||||
f"{from_versioncode}-post.sh")
|
||||
pre_out_path = os.path.join(cutthecord_folder, "resources/patches", patch,
|
||||
f"{to_versioncode}-pre.sh")
|
||||
post_out_path = os.path.join(cutthecord_folder, "resources/patches", patch,
|
||||
f"{to_versioncode}-post.sh")
|
||||
|
||||
patch_path = os.path.join(cutthecord_folder, "resources/patches", patch,
|
||||
f"{from_versioncode}.patch")
|
||||
out_path = os.path.join(cutthecord_folder, "resources/patches", patch,
|
||||
f"{to_versioncode}.patch")
|
||||
readme_path = os.path.join(cutthecord_folder, "resources/patches", patch, "README.md")
|
||||
|
||||
# Handle copying of versioned scripts, untested and dirty!
|
||||
script_path = os.path.join(cutthecord_folder, "resources/patches", patch,
|
||||
f"{from_versioncode}.sh")
|
||||
if os.path.exists(script_path):
|
||||
script_out_path = os.path.join(cutthecord_folder, "resources/patches", patch,
|
||||
f"{to_versioncode}.sh")
|
||||
with open(script_path) as f:
|
||||
with open(script_out_path, "w") as f2:
|
||||
f2.write(f.read())
|
||||
|
||||
# Check if patch exists for from_version, if it doesn't, warn user
|
||||
if not os.path.isfile(patch_path) and patch not in ["necessary"]:
|
||||
# Don't warn on instructional patches
|
||||
if patch not in ["customfont", "customring",
|
||||
"bettertm", "bettertmlight",
|
||||
"blobs"]:
|
||||
print(f"SKIPPED: No {from_versionname} version found for {patch}.")
|
||||
continue
|
||||
|
||||
# Check if pre-script exists, if it does copy it
|
||||
if os.path.isfile(pre_in_path):
|
||||
shutil.copyfile(pre_in_path, pre_out_path)
|
||||
print(f"PRE COPIED: {patch}'s pre script was copied.")
|
||||
|
||||
# Check if post-script exists, if it does copy it
|
||||
if os.path.isfile(post_in_path):
|
||||
shutil.copyfile(post_in_path, post_out_path)
|
||||
print(f"POST COPIED: {patch}'s post script was copied.")
|
||||
|
||||
# Create necessary instead of porting it.
|
||||
if patch == "necessary":
|
||||
patch_contents = make_necessary(to_versioncode, to_versionname)
|
||||
else:
|
||||
# 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,
|
||||
capture_output=True)
|
||||
|
||||
# Check for issues
|
||||
if "FAILED" in out.stdout or "can't find file to patch" in out.stdout:
|
||||
print(f"FAILED: {patch} failed, please fix by hand.")
|
||||
failures.append(patch)
|
||||
out_path += "-failed"
|
||||
elif "offset" in out.stdout:
|
||||
temp_patch_contents = fix_offset(patch_contents)
|
||||
if temp_patch_contents:
|
||||
patch_contents = temp_patch_contents
|
||||
print(f"WARNING: {patch} has offsets which were auto corrected.")
|
||||
else:
|
||||
print(f"FAILED: {patch} is missing files, please fix by hand.")
|
||||
failures.append(patch)
|
||||
out_path += "-failed"
|
||||
|
||||
if debug:
|
||||
print(out.stdout)
|
||||
|
||||
# Apply patch to main APK folder too
|
||||
if patch in ["necessary"]:
|
||||
apply_patch(patch_contents)
|
||||
|
||||
if from_versionname != to_versionname:
|
||||
# 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}\n',
|
||||
f'- {from_versionname}\n'
|
||||
f'- {to_versionname}\n')
|
||||
with open(readme_path, "w") as f:
|
||||
f.write(readme_text)
|
||||
|
||||
# Save ported patch
|
||||
with open(out_path, "w") as f:
|
||||
f.write(patch_contents)
|
||||
|
||||
if not out_path.endswith("-failed"):
|
||||
print(f"PORTED: {patch} was successfully ported.")
|
||||
|
||||
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:
|
||||
f.write(ctcr_text)
|
||||
|
||||
with open("patchport-state.json", "w") as f:
|
||||
jout = {"versionname": to_versionname, "versioncode": to_versioncode}
|
||||
json.dump(jout, f)
|
||||
|
||||
if failures:
|
||||
print(f"Port complete. Following patches failed: {', '.join(failures)}")
|
||||
else:
|
||||
print("Port complete. All patches completed successfully.")
|
Loading…
Add table
Add a link
Reference in a new issue