Compare commits
	
		
			4 commits
		
	
	
		
			85923ac1a5
			...
			8a7b95beee
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8a7b95beee | |||
|   | f2ada0e2fe | ||
|   | e8ffddf93f | ||
| 9e065cd638 | 
					 23 changed files with 324 additions and 17899 deletions
				
			
		
							
								
								
									
										9
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,11 +1,8 @@ | |||
| build/ | ||||
| source-apk/*.apk | ||||
| keystores/*.keystore | ||||
| tools/* | ||||
| !tools/smali-2.5.2.jar | ||||
| !tools/apktool_2.9.3.jar | ||||
| !tools/apksigner.jar | ||||
| !tools/d8.jar | ||||
| 
 | ||||
| .idea | ||||
| *.iml | ||||
| *.iml | ||||
| 
 | ||||
| __pycache__ | ||||
							
								
								
									
										15
									
								
								.vscode/launch.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								.vscode/launch.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| { | ||||
|     // Use IntelliSense to learn about possible attributes. | ||||
|     // Hover to view descriptions of existing attributes. | ||||
|     // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||||
|     "version": "0.2.0", | ||||
|     "configurations": [ | ||||
|         { | ||||
|             "name": "Python Debugger: Current File", | ||||
|             "type": "debugpy", | ||||
|             "request": "launch", | ||||
|             "program": "test.py", | ||||
|             "console": "integratedTerminal" | ||||
|         } | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										2
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| { | ||||
| } | ||||
							
								
								
									
										234
									
								
								build.py
									
										
									
									
									
								
							
							
						
						
									
										234
									
								
								build.py
									
										
									
									
									
								
							|  | @ -1,234 +0,0 @@ | |||
| import sys | ||||
| import shutil | ||||
| import os | ||||
| from pathlib import Path | ||||
| import subprocess | ||||
| 
 | ||||
| class BuildError(Exception): | ||||
|     def __init__(self): | ||||
|         super.__init__() | ||||
| 
 | ||||
| def getDexName(smaliName): | ||||
|     if smaliName == 'smali': | ||||
|         return 'classes.dex' | ||||
|     elif smaliName.startswith('smali_classes'): | ||||
|         return 'classes' + smaliName[13:] + '.dex' | ||||
|     print("Not a smali name") | ||||
|     raise BuildError() | ||||
| 
 | ||||
| class Task: | ||||
|     def clean(): | ||||
|         shutil.rmtree('build') | ||||
|      | ||||
|     def extractApk(): | ||||
|         if Path('build/extracted').exists(): | ||||
|             return | ||||
|         Path('build').mkdir(parents=True, exist_ok=True) | ||||
| 
 | ||||
|         apk = list(filter(lambda f: f.endswith('.apk'), os.listdir('source-apk'))) | ||||
|         if len(apk) == 0: | ||||
|             print('Please place the APK inside the source-apk directory.') | ||||
|             raise BuildError() | ||||
|         elif len(apk) > 1: | ||||
|             print('More than one apk found in source-apk. Please remove all but one.') | ||||
|             raise BuildError() | ||||
| 
 | ||||
|         # Extract the apk | ||||
|         print("Extracting APK...") | ||||
|         subprocess.run(['java', '-jar', './tools/apktool_2.9.3.jar', 'd', 'source-apk/' + apk[0], '-o', 'build/extracted']) | ||||
| 
 | ||||
|     def copySmali(): | ||||
|         Task.extractApk() | ||||
| 
 | ||||
|         Path('build/smali').mkdir(parents=True, exist_ok=True) | ||||
|         # Copy smali classes that haven't been yet | ||||
|         for file in os.listdir('build/extracted'): | ||||
|             if file != 'smali' and not file.startswith('smali_classes'): | ||||
|                 continue | ||||
|              | ||||
|             # Skip if already exists | ||||
|             if Path(f'build/smali/{file}').exists(): | ||||
|                 continue | ||||
| 
 | ||||
|             print(f'Copying {file}...') | ||||
|             shutil.copytree(f'build/extracted/{file}', f'build/smali/{file}') | ||||
| 
 | ||||
|         # Copy from src | ||||
|         print('Copying ykit smali...') | ||||
|         shutil.copytree('src/smali', 'build/smali', dirs_exist_ok=True) | ||||
| 
 | ||||
|     def compileSmali(): | ||||
|         Task.copySmali() | ||||
| 
 | ||||
|         Path('build/dex').mkdir(parents=True, exist_ok=True) | ||||
| 
 | ||||
|         def shouldCompileSmali(file, dex): | ||||
|             src = f'build/smali/{file}' | ||||
|             ykitsrc = f'src/smali/{file}' | ||||
|             dst = f'build/dex/{dex}' | ||||
| 
 | ||||
|             # If dest doesn't exist, always build | ||||
|             if not Path(dst).exists(): | ||||
|                 return True | ||||
| 
 | ||||
|             # Check if ykit src dir exists for it, if not then don't recompile | ||||
|             if not Path(ykitsrc).exists(): | ||||
|                 return False | ||||
| 
 | ||||
|             # Get latest file timestamp in ykitsrc | ||||
|             files = list(Path(ykitsrc).rglob('*.smali')) | ||||
|             files = [os.path.getmtime(x) for x in files] | ||||
|             latest = max(files) | ||||
| 
 | ||||
|             dsttime = os.path.getmtime(dst) | ||||
| 
 | ||||
|             # If it is newer than the dest, then do recompile | ||||
|             if latest > dsttime: | ||||
|                 return True | ||||
| 
 | ||||
|             # Otherwise, don't | ||||
|             return False | ||||
| 
 | ||||
|         for file in os.listdir('build/smali'): | ||||
|             dex = getDexName(file) | ||||
| 
 | ||||
|             if not shouldCompileSmali(file, dex): | ||||
|                 print(f"Skipping {file}...") | ||||
|                 continue | ||||
| 
 | ||||
|             print(f"Compiling {file}...") | ||||
|             subprocess.run(['java', '-jar', 'tools/smali-2.5.2.jar', 'a', f'build/smali/{file}', '-o', f'build/dex/{dex}']) | ||||
| 
 | ||||
|     def genClassPath(): | ||||
|         print("Generating java classpath from dex files...") | ||||
|         for file in os.listdir('build/dex'): | ||||
|             src = f'build/dex/{file}' | ||||
|             dst = f'build/jar/{file[:-4]}.jar' | ||||
|             srctime = os.path.getmtime(src) | ||||
|             dsttime = 0 if not Path(dst).exists() else os.path.getmtime(dst) | ||||
| 
 | ||||
|             if srctime < dsttime: | ||||
|                 print(f"Skipping {file}...") | ||||
|                 continue | ||||
| 
 | ||||
|             print(f"Dex2jar {file}...") | ||||
|             subprocess.run(['java', '-cp', 'tools/d2j/*', 'com.googlecode.dex2jar.tools.Dex2jarCmd', '-f', src, '-o', dst]) | ||||
| 
 | ||||
|     def compileJava(): | ||||
|         Task.genClassPath() | ||||
| 
 | ||||
|         sep = ';' if os.name == 'nt' else ':' | ||||
|         javaFiles = [str(x) for x in Path('src/java').rglob('*.java')] | ||||
|         classPath = sep.join([str(x) for x in Path('build/jar').rglob('*.jar')]) | ||||
| 
 | ||||
|         # Get latest java file timestamp | ||||
|         files = list(Path('src/java').rglob('*.java')) | ||||
|         files = [os.path.getmtime(x) for x in files] | ||||
|         latest = max(files) | ||||
| 
 | ||||
|         # Get latest dex time or 0 | ||||
|         latest2 = 0 if not Path('build/java_dex/classes.dex').exists() else os.path.getmtime('build/java_dex/classes.dex') | ||||
| 
 | ||||
|         # If dex file is newer than source, no need to do anything | ||||
|         if latest2 > latest: | ||||
|             print("Skipping java...") | ||||
|             return | ||||
| 
 | ||||
|         print("Compiling java...") | ||||
|         subprocess.run(['javac', '-cp', classPath, '-d', 'build/java_class', *javaFiles]) | ||||
| 
 | ||||
|         classFiles = [str(x) for x in Path('build/java_class').rglob('*.class')] | ||||
| 
 | ||||
|         Path('build/java_dex').mkdir(parents=True, exist_ok=True) | ||||
|         subprocess.run(['java', '-cp', 'tools/d8.jar', 'com.android.tools.r8.D8', *classFiles, '--output', 'build/java_dex']) | ||||
| 
 | ||||
|     def mergeResources(): | ||||
|         Task.compileSmali() | ||||
|         Task.compileJava() | ||||
| 
 | ||||
|         # Copy original resources | ||||
|         # on first run only | ||||
|         if not Path('build/merged').exists(): | ||||
|             print('Copying extracted resources...') | ||||
|             shutil.copytree('build/extracted', 'build/merged', dirs_exist_ok=True, ignore=shutil.ignore_patterns('smali', 'smali_classes*')) | ||||
| 
 | ||||
|         print("Copying ykit resources...") | ||||
|         shutil.copytree('src/resources', 'build/merged', dirs_exist_ok=True) | ||||
| 
 | ||||
|         print("Copying compiled dex files...") | ||||
|         shutil.copytree('build/dex', 'build/merged', dirs_exist_ok=True) | ||||
|         shutil.copy('build/java_dex/classes.dex', 'build/merged/classes7.dex') # TODO: Unhardcode this | ||||
| 
 | ||||
|     def buildApk(): | ||||
|         Task.mergeResources() | ||||
| 
 | ||||
|         debugFlag = ('-d',) if ('d' in ''.join(sys.argv[2:])) else () | ||||
| 
 | ||||
|         print("Building apk...") | ||||
|         subprocess.run(['java', '-jar', './tools/apktool_2.9.3.jar', 'b', *debugFlag, 'build/merged', '-o', 'build/apk/tumblr-ykit-unsigned.apk']) | ||||
| 
 | ||||
|     def alignApk(): | ||||
|         Task.buildApk() | ||||
| 
 | ||||
|         print('Aligning apk...') | ||||
|         subprocess.run(['tools/zipalign', '-p', '-f', '-v', '4', 'build/apk/tumblr-ykit-unsigned.apk', 'build/apk/tumblr-ykit.apk']) | ||||
| 
 | ||||
|     def debugKey(): | ||||
|         if Path('keystores/debug.keystore').exists(): | ||||
|             return | ||||
| 
 | ||||
|         print("Debug keystore does not exist, generating...") | ||||
|         subprocess.run(['keytool', '-genkey', '-v', '-keystore', 'keystores/debug.keystore', '-alias', 'alias_name', '-keyalg', 'RSA', '-keysize', '2048', '-validity', '10000', '-storepass', '123456', '-keypass', '123456', '-dname', 'CN=, OU=, O=, L=, ST=, C=']) | ||||
| 
 | ||||
|     def signApk(): | ||||
|         Task.alignApk() | ||||
|         Task.debugKey() | ||||
| 
 | ||||
|         debugFlag = 'd' in ''.join(sys.argv[2:]) | ||||
|         releaseFlag = 'r' in ''.join(sys.argv[2:]) | ||||
| 
 | ||||
|         if not (debugFlag or releaseFlag): | ||||
|             print("Neither debug nor release flag was specified so the apk will not be signed.") | ||||
|             return | ||||
| 
 | ||||
|         if releaseFlag: | ||||
|             if not Path('keystores/release.keystore').exists(): | ||||
|                 print("release.keystore is missing from keystores directory") | ||||
|                 return | ||||
|              | ||||
|             keystoreOpts = ('--ks', 'keystores/release.keystore',) | ||||
|         else: | ||||
|             Task.debugKey() | ||||
|             keystoreOpts = ('--ks', 'keystores/debug.keystore', '--ks-pass', 'pass:123456',) | ||||
| 
 | ||||
|         print('Signing apk...') | ||||
|         subprocess.run(['java', '-jar', './tools/apksigner.jar', 'sign', *keystoreOpts, 'build/apk/tumblr-ykit.apk']) | ||||
|         print("Successfully signed apk in 'build/apk/tumblr-ykit.apk' using " + ('release' if releaseFlag else 'debug') + ' keystore') | ||||
| 
 | ||||
|     def deployToAndroidStudio(): | ||||
|         projDir = Path.home() / 'ApkProjects/tumblr-ykit' | ||||
|         if not projDir.exists(): | ||||
|             print("No project named 'tumblr-ykit' in ~/ApkProjects/") | ||||
| 
 | ||||
|         shutil.copy('build/apk/tumblr-ykit.apk', projDir / 'tumblr-ykit.apk') | ||||
|         print("Deployed to Android Studio") | ||||
| 
 | ||||
| def main(): | ||||
| 
 | ||||
|     match sys.argv[1]: | ||||
|         case 'clean': | ||||
|             Task.clean() | ||||
|         case 'extract': | ||||
|             Task.extractApk() | ||||
|         case 'assemble': | ||||
|             Task.buildApk() | ||||
|         case 'build': | ||||
|             Task.signApk() | ||||
|              | ||||
|             if ('a' in ''.join(sys.argv[2:])): | ||||
|                 Task.deployToAndroidStudio() | ||||
|         case 'genclasspath': | ||||
|             Task.genClassPath() | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										2
									
								
								build.sh
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								build.sh
									
										
									
									
									
								
							|  | @ -1 +1 @@ | |||
| python build.py "$@" | ||||
| python buildtool/build.py "$@" | ||||
							
								
								
									
										5
									
								
								buildtool/build.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								buildtool/build.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| # from task.extract import extract | ||||
| from task.patch_resources import patch_resources | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     _ = patch_resources() | ||||
							
								
								
									
										44
									
								
								buildtool/polly/grammar.lark
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								buildtool/polly/grammar.lark
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | |||
| start: (patch)* | ||||
| patch: file_declaration instruction* | ||||
| 
 | ||||
| file_declaration: "file" string | ||||
| 
 | ||||
| ?string: STRING | RAW_STRING | LONG_STRING | ||||
| 
 | ||||
| ?instruction: insert | ||||
|             | preg_replace | ||||
|             | define_anchor | ||||
| 
 | ||||
| insert: "insert" location string | ||||
| 
 | ||||
| preg_replace: "preg_replace" range PATTERN string | ||||
| 
 | ||||
| define_anchor: "define_anchor" anchor affix PATTERN | ||||
|              | "define_anchor" range_anchor "range" PATTERN | ||||
| 
 | ||||
| ?affix: "before" | "after" | ||||
| 
 | ||||
| ?location: LINE_NUMBER | ||||
|          | CHAR_INDEX | ||||
|          | LINE_COLUMN | ||||
|          | anchor | ||||
| 
 | ||||
| range: "(" location "-" location ")" | ||||
|      | range_anchor | ||||
| 
 | ||||
| anchor: "@" IDENTIFIER | ||||
| range_anchor: "@$" IDENTIFIER | ||||
| 
 | ||||
| STRING: /"([^"\\]|\\.)*"/ | ||||
| RAW_STRING: /'[^']*'/ | ||||
| LONG_STRING: /<<\s*(?P<terminator>[^\n]+)\n.*\n(?P=terminator)/s | ||||
| LINE_NUMBER: /ln\d+/ | ||||
| CHAR_INDEX: /ch\d+/ | ||||
| LINE_COLUMN: /ln\d+c\d+/ | ||||
| IDENTIFIER: /[a-zA-Z_][a-zA-Z0-9_]*/ | ||||
| PATTERN: /\/([^\/\\]|\\.)*\// | ||||
| 
 | ||||
| COMMENT: /#[^\r\n]+/ | ||||
| 
 | ||||
| %ignore /[\t \f\n]+/ | ||||
| %ignore COMMENT | ||||
							
								
								
									
										63
									
								
								buildtool/polly/parser.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								buildtool/polly/parser.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | |||
| from lark import Lark | ||||
| from lark.lexer import Token | ||||
| from lark.tree import Branch | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| import re | ||||
| 
 | ||||
| def parse_string(token: Token): | ||||
|     if token.type == 'STRING': | ||||
|         string = token.value[1:-1] | ||||
|         string = re.sub(r"\\n","\n", string) | ||||
|         string = re.sub(r"\\t","\t", string) | ||||
|         string = re.sub(r"\\r","\r", string) | ||||
|         string = re.sub(r"\\(.)",r"\1", string) | ||||
|         return string | ||||
|     elif token.type == 'RAW_STRING': | ||||
|         string = token.value[1:-1] | ||||
|         return string | ||||
|     elif token.type == 'LONG_STRING': | ||||
|         string = re.match(re.compile(r"<<\s*(?P<terminator>[^\n]+)\n(.*)\n(?P=terminator)", re.MULTILINE + re.DOTALL),token.value) | ||||
|         assert string is not None | ||||
|         string = string.group(2) | ||||
|         return string | ||||
|      | ||||
| def parse_location(location: Token): | ||||
|     assert isinstance(location.value, str) | ||||
|     m = re.match(r"ln([0-9]+)(?:c([0-9]+))?", location.value) | ||||
|     if m: | ||||
|         ln, col = m.groups() | ||||
|         return {"type": "lncol", "line": int(ln), "column": int(col) if col else 0} | ||||
|      | ||||
|     m = re.match(r"ch([0-9]+)", location.value) | ||||
|     if m: | ||||
|         ch = m.groups()[0] | ||||
|         return {"type": "char", "index": int(ch)} | ||||
| 
 | ||||
|     raise RuntimeError("Cannot parse location") | ||||
| 
 | ||||
| def parse_patch(branch: Branch[Token], mtime: float): | ||||
|     # First instruction is always file declaration | ||||
|     target_file = parse_string(branch.children[0].children[0]) # pyright: ignore[reportUnknownMemberType, reportArgumentType] | ||||
| 
 | ||||
|     actions = [] | ||||
| 
 | ||||
|     for inst in branch.children[1:]: | ||||
|         match inst.data: | ||||
|             case "insert": | ||||
|                 actions.append({"type": "insert", "at": parse_location(inst.children[0]), "content": parse_string(inst.children[1])}) | ||||
|                 # print(f"Inserting {parse_string(inst.children[1])} at {inst.children[0]} in {target_file}") | ||||
| 
 | ||||
|     return {"target": target_file, "actions": actions, "timestamp": mtime} | ||||
| 
 | ||||
| def parse_patch_file(file: str): | ||||
|     lark = Lark.open('grammar.lark', rel_to=__file__) | ||||
| 
 | ||||
|     mtime = os.path.getmtime(file) | ||||
| 
 | ||||
|     with open(file, 'r') as f: | ||||
|         result = lark.parse(f.read()) | ||||
|         patches = [parse_patch(patch, mtime) for patch in result.children] | ||||
|          | ||||
|     return patches | ||||
							
								
								
									
										71
									
								
								buildtool/polly/patcher.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								buildtool/polly/patcher.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| from .parser import parse_patch_file | ||||
| 
 | ||||
| from pathlib import Path | ||||
| 
 | ||||
| from task.util import * | ||||
| 
 | ||||
| def resolve_position(location, src: str): | ||||
|     if location["type"] == "char": | ||||
|         return location["index"] | ||||
|     elif location["type"] == "lncol": | ||||
|         j = 0 | ||||
|         for i, x in enumerate(src.split('\n')): | ||||
|             if (i+1) == location["line"]: | ||||
|                 if location["column"] > len(x): | ||||
|                     print(f"Cannot insert at line {location['line']} column {location['column']}. The line is not that long.") | ||||
|                     exit(-1) | ||||
|                 return j + location["column"] | ||||
|             j += len(x) + 1 | ||||
|              | ||||
|         print(f"Cannot insert at line {location['line']}. The file is not long enough.") | ||||
|         exit(-1) | ||||
| 
 | ||||
| def read_insertion(action, src): | ||||
|     position = resolve_position(action["at"], src) | ||||
|     return {"content": action["content"], "pos": position} | ||||
| 
 | ||||
| def process_patch_file(patch_file: str, check_date: bool = True): | ||||
|     patches = parse_patch_file(patch_file) | ||||
|      | ||||
|     for patch in patches: | ||||
|         target_src = EXTRACTED_DIR / patch["target"] | ||||
|         target_dest = PATCHED_RSC_DIR / patch["target"] | ||||
|          | ||||
|         # Skip old patches | ||||
|         if check_date and target_dest.exists() and target_dest.stat().st_mtime > patch["timestamp"]: | ||||
|             print(f"Skipping patch {patch['target']}...") | ||||
|             continue | ||||
|          | ||||
|         print(f"Patching {patch['target']} with {len(patch['actions'])} actions") | ||||
|          | ||||
|         # Read the source of the file | ||||
|         with open(target_src, 'r') as f: | ||||
|             src = f.read() | ||||
|              | ||||
|         # Resolve the actual char locations in the patches | ||||
|         insertions = [read_insertion(action, src) for action in patch["actions"]] | ||||
|         insertions = [x for x in insertions if x is not None] | ||||
|          | ||||
|         for insertion in insertions: | ||||
|             src = src[:insertion["pos"]] + insertion["content"] + src[insertion["pos"]:] | ||||
| 
 | ||||
|             # Correct insertions that follow this one | ||||
|             for i2 in insertions: | ||||
|                 # Only update insertions that come *after* this one | ||||
|                 if i2 == insertion or i2["pos"] < insertion["pos"]: | ||||
|                     continue | ||||
|                  | ||||
|                 i2["pos"] += len(insertion["content"]) | ||||
|                  | ||||
|         # Write the changed output | ||||
|         with open(target_dest, 'w') as f: | ||||
|             f.write(src) | ||||
|              | ||||
| def process_all_patches(dir: Path | str, check_date: bool = True): | ||||
|     dir = Path(dir) | ||||
|      | ||||
|     for (cd, _, files) in dir.walk(): | ||||
|         for f in files: | ||||
|             if f.endswith(".patch"): | ||||
|                 print("Processing patch {}".format((cd / f).relative_to(dir))) | ||||
|                 process_patch_file(str(cd / f), check_date=check_date) | ||||
							
								
								
									
										25
									
								
								buildtool/task/extract.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								buildtool/task/extract.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| from .util import * | ||||
| 
 | ||||
| import subprocess | ||||
| 
 | ||||
| def find_apk(): | ||||
|     apks = list(SOURCE_APK_DIR.glob("*.apk")) | ||||
|     if len(apks) == 0: | ||||
|         print("Source APK is missing from 'source-apk' directory. Please make sure to copy it to the right directory.") | ||||
|         exit(-1) | ||||
|     elif len(apks) > 1: | ||||
|         print("Multiple APKs were found in the 'source-apk' directory. Please only place the correct APK there.") | ||||
|         exit(-1) | ||||
| 
 | ||||
|     return apks[0] | ||||
| 
 | ||||
| def extract(): | ||||
|     target_apk = find_apk() | ||||
| 
 | ||||
|     # Check if this task is necessary | ||||
|     if EXTRACTED_DIR.exists() and EXTRACTED_DIR.stat().st_mtime > target_apk.stat().st_mtime: | ||||
|         return "pass" | ||||
|      | ||||
|     # Extract the apk | ||||
|     print("Extracting APK...") | ||||
|     _ = subprocess.run(['java', '-jar', './tools/apktool_2.9.3.jar', 'd', str(target_apk.absolute()), '-o', EXTRACTED_DIR]) | ||||
							
								
								
									
										23
									
								
								buildtool/task/fileutil.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								buildtool/task/fileutil.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| from pathlib import Path | ||||
| import shutil | ||||
| 
 | ||||
| def merge_into(src: Path | str, dest: Path | str, check_date: bool = True): | ||||
|     src = Path(src) | ||||
|     dest = Path(dest) | ||||
|      | ||||
|     changed = False | ||||
|      | ||||
|     for (cd, _, files) in src.walk(): | ||||
|         for file in files: | ||||
|             src_file = cd / file | ||||
|             dest_file = dest / src_file.relative_to(src) | ||||
|              | ||||
|             # Don't update if dest is newer than source | ||||
|             if check_date and dest_file.exists() and src_file.stat().st_mtime < dest_file.stat().st_mtime: | ||||
|                 continue | ||||
|              | ||||
|             dest_file.parent.mkdir(parents=True, exist_ok=True) | ||||
|             _ = shutil.copyfile(src_file, dest_file) | ||||
|             changed = True | ||||
|              | ||||
|     return changed | ||||
							
								
								
									
										29
									
								
								buildtool/task/patch_resources.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								buildtool/task/patch_resources.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| from .util import * | ||||
| from . import fileutil | ||||
| 
 | ||||
| import shutil | ||||
| 
 | ||||
| from polly.patcher import process_all_patches | ||||
| 
 | ||||
| # Task dependencies | ||||
| from .extract import extract | ||||
| 
 | ||||
| def patch_resources(): | ||||
|     _ = extract() | ||||
|      | ||||
|     first_time = False | ||||
|      | ||||
|     # Copy original resources only the first time | ||||
|     if not PATCHED_RSC_DIR.exists(): | ||||
|         print("Copying original resources...") | ||||
|         shutil.copytree(EXTRACTED_DIR, PATCHED_RSC_DIR, ignore=shutil.ignore_patterns("smali*")) | ||||
|         first_time = True | ||||
|      | ||||
|     s = fileutil.merge_into(SRC_RESOURCES_DIR, PATCHED_RSC_DIR, check_date=not first_time) | ||||
|     if s: | ||||
|         print("Copied custom resources from src/resources") | ||||
|     else: | ||||
|         print("Skipped copying resources from src/resources") | ||||
|          | ||||
|     # Patch other resources | ||||
|     process_all_patches(SRC_PATCHES_DIR, check_date=not first_time) | ||||
							
								
								
									
										11
									
								
								buildtool/task/util.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								buildtool/task/util.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| from pathlib import Path | ||||
| 
 | ||||
| SOURCE_APK_DIR = Path("source-apk") | ||||
| 
 | ||||
| BUILD_DIR = Path("build") | ||||
| EXTRACTED_DIR = BUILD_DIR / "extracted" | ||||
| PATCHED_RSC_DIR = BUILD_DIR / "patched_resources" | ||||
| 
 | ||||
| SRC_DIR = Path("src") | ||||
| SRC_RESOURCES_DIR = SRC_DIR / "resources" | ||||
| SRC_PATCHES_DIR = SRC_DIR / "patches" | ||||
							
								
								
									
										1
									
								
								msys.bat
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								msys.bat
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| C:\msys64\msys2_shell.cmd -here -defterm -no-start -full-path | ||||
							
								
								
									
										16
									
								
								src/patches/public.patch
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/patches/public.patch
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| file 'res/values/public.xml' | ||||
| 
 | ||||
| insert ln17595 << @end | ||||
| 
 | ||||
|     <!-- Ykit --> | ||||
| 
 | ||||
|     <public type="drawable" name="oval_yellow" id="0x7f08f001" /> <!-- YKit-BringBackPhoebe --> | ||||
|     <public type="id" name="ykit_settings" id="0x7f0bf001" /> <!-- YKit-Settings --> | ||||
|     <public type="id" name="ykit_version" id="0x7f0bf003" /> <!-- YKit-Settings --> | ||||
|     <public type="id" name="text_color_yellow" id="0x7f0bf002" /> <!-- YKit-BringBackPhoebe --> | ||||
|     <public type="layout" name="activity_ykit_settings" id="0x7f0ef001" /> <!-- YKit-Settings --> | ||||
|     <public type="raw" name="ykit_meow" id="0x7f12f001" /> <!-- YKit-Settings --> | ||||
| 
 | ||||
| @end
 | ||||
| 
 | ||||
| #17595 | ||||
							
								
								
									
										6
									
								
								src/patches/yellow.patch
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/patches/yellow.patch
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| file 'res/layout/color_options_toolbar.xml' | ||||
| 
 | ||||
| insert ln8 << @end | ||||
|         <androidx.appcompat.widget.AppCompatImageButton android:id="@id/text_color_yellow" android:background="?selectableItemBackgroundBorderless" android:layout_width="48.0dip" android:layout_height="48.0dip" android:layout_marginLeft="2.0dip" android:layout_marginRight="2.0dip" android:layout_weight="0.5" app:srcCompat="@drawable/oval_yellow" /> | ||||
| 
 | ||||
| @end
 | ||||
							
								
								
									
										6
									
								
								src/patches/ykit_settings.patch
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/patches/ykit_settings.patch
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| file 'res/layout/fragment_blog_settings.xml' | ||||
| 
 | ||||
| insert ln10 << @end | ||||
|             <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/ykit_settings" tumblr:listItemDetail="Change settings for YKit" tumblr:listItemTitle="YKit Settings" style="@style/DetailedListItem" /> | ||||
| 
 | ||||
| @end
 | ||||
|  | @ -1,14 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <merge android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="48.0dip" | ||||
|   xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|     <LinearLayout android:gravity="center" android:orientation="horizontal" android:id="@id/color_editing_controls" android:background="?selectableItemBackgroundBorderless" android:layout_width="fill_parent" android:layout_height="48.0dip"> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton android:id="@id/text_color_default" android:background="?selectableItemBackgroundBorderless" android:layout_width="48.0dip" android:layout_height="48.0dip" android:layout_marginLeft="2.0dip" android:layout_marginRight="2.0dip" android:layout_weight="0.5" app:srcCompat="@drawable/oval_main_text_color" /> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton android:id="@id/text_color_red" android:background="?selectableItemBackgroundBorderless" android:layout_width="48.0dip" android:layout_height="48.0dip" android:layout_marginLeft="2.0dip" android:layout_marginRight="2.0dip" android:layout_weight="0.5" app:srcCompat="@drawable/oval_red" /> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton android:id="@id/text_color_orange" android:background="?selectableItemBackgroundBorderless" android:layout_width="48.0dip" android:layout_height="48.0dip" android:layout_marginLeft="2.0dip" android:layout_marginRight="2.0dip" android:layout_weight="0.5" app:srcCompat="@drawable/oval_orange" /> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton android:id="@id/text_color_yellow" android:background="?selectableItemBackgroundBorderless" android:layout_width="48.0dip" android:layout_height="48.0dip" android:layout_marginLeft="2.0dip" android:layout_marginRight="2.0dip" android:layout_weight="0.5" app:srcCompat="@drawable/oval_yellow" /> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton android:id="@id/text_color_green" android:background="?selectableItemBackgroundBorderless" android:layout_width="48.0dip" android:layout_height="48.0dip" android:layout_marginLeft="2.0dip" android:layout_marginRight="2.0dip" android:layout_weight="0.5" app:srcCompat="@drawable/oval_green" /> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton android:id="@id/text_color_blue" android:background="?selectableItemBackgroundBorderless" android:layout_width="48.0dip" android:layout_height="48.0dip" android:layout_marginLeft="2.0dip" android:layout_marginRight="2.0dip" android:layout_weight="0.5" app:srcCompat="@drawable/oval_blue" /> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton android:id="@id/text_color_purple" android:background="?selectableItemBackgroundBorderless" android:layout_width="48.0dip" android:layout_height="48.0dip" android:layout_marginLeft="2.0dip" android:layout_marginRight="2.0dip" android:layout_weight="0.5" app:srcCompat="@drawable/oval_purple" /> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton android:id="@id/text_color_pink" android:background="?selectableItemBackgroundBorderless" android:layout_width="48.0dip" android:layout_height="48.0dip" android:layout_marginLeft="2.0dip" android:layout_marginRight="2.0dip" android:layout_weight="0.5" app:srcCompat="@drawable/oval_pink" /> | ||||
|     </LinearLayout> | ||||
| </merge> | ||||
|  | @ -1,43 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <com.tumblr.ui.widget.fab.ObservableScrollView android:id="@id/blog_settings_scroll_view" android:background="?themeHighlightedContentBackgroundColor" android:paddingTop="@dimen/action_bar_base_height" android:layout_width="fill_parent" android:layout_height="fill_parent" | ||||
|   xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tumblr="http://schemas.android.com/apk/res-auto"> | ||||
|     <LinearLayout android:background="@null" style="@style/BlogSettingsContainer"> | ||||
|         <LinearLayout android:id="@id/user_blog_default_options" style="@style/BlogSettingsUserBlogOptionsContainer"> | ||||
|             <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/account_settings" tumblr:listItemDetail="@string/global_settings_subtitle" tumblr:listItemTitle="@string/account_settings" style="@style/DetailedListItem" /> | ||||
|             <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_ad_free_management" android:visibility="gone" tumblr:listItemDetail="@string/tumblr_premium_subtitle" tumblr:listItemTitle="@string/tumblr_premium" style="@style/DetailedListItem" /> | ||||
|             <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_payment_and_purchases" tumblr:listItemDetail="@string/payment_and_purchases_desc" tumblr:listItemTitle="@string/payment_and_purchases" style="@style/DetailedListItem" /> | ||||
|             <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_subscriptions_and_purchases" tumblr:listItemDetail="@string/subscriptions_and_purchases_desc" tumblr:listItemTitle="@string/subscriptions_and_purchases" style="@style/DetailedListItem" /> | ||||
|             <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/ykit_settings" tumblr:listItemDetail="Change settings for YKit" tumblr:listItemTitle="YKit Settings" style="@style/DetailedListItem" /> | ||||
|         </LinearLayout> | ||||
|         <LinearLayout style="@style/BlogSettingsOptionsSection"> | ||||
|             <TextView android:id="@id/blog_settings_header" android:background="?themeHighlightedContentBackgroundColor" style="@style/SettingsSectionHeader" /> | ||||
|             <com.tumblr.ui.widget.UserBlogOptionsLayout android:id="@id/user_blog_options_container" style="@style/BlogSettingsUserBlogOptionsContainer"> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_change_username_row" android:visibility="gone" tumblr:listItemTitle="@string/change_name_title" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_pages_row" android:visibility="gone" tumblr:listItemDetail="@string/pages_description" tumblr:listItemTitle="@string/pages" style="@style/DetailedListItem.White" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_featured_tags" android:visibility="gone" tumblr:listItemTitle="@string/featured_tags" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_followers_row" tumblr:listItemTitle="@string/followers_title" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_drafts_row" tumblr:listItemTitle="@string/drafts_title" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_queue_row" tumblr:listItemTitle="@string/queue_title" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_inbox_row" tumblr:listItemDetail="@string/inbox_detail_new" tumblr:listItemTitle="@string/inbox_title" style="@style/DetailedListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_community_label_row" android:visibility="gone" tumblr:listItemTitle="@string/blog_community_label_settings" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_posts_review_row" android:visibility="gone" tumblr:listItemTitle="@string/posts_review_setting_label" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blaze_info_page" android:visibility="gone" tumblr:listItemTitle="@string/tumblr_blaze" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/manage_gifts" android:visibility="gone" tumblr:listItemTitle="@string/gifts" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_payouts" android:visibility="gone" tumblr:listItemTitle="@string/payouts_settings" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_privacy_row" tumblr:listItemTitle="@string/setting_label_blog_privacy_title" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_delete_row" tumblr:listItemTitle="@string/delete_blog" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMToggleWithWarningRow android:id="@id/blog_allow_tipping" android:paddingStart="6.0dip" tumblr:rowText="@string/allow_tipping" tumblr:toggleDefault="false" style="@style/SettingsToggleSettingRow" /> | ||||
|                 <com.tumblr.ui.widget.TMToggleWithWarningRow android:id="@id/blog_allow_blaze_by_others" android:paddingStart="6.0dip" tumblr:rowText="@string/allow_blaze_by_others_v2" tumblr:toggleDefault="false" style="@style/SettingsToggleSettingRow" /> | ||||
|                 <com.tumblr.ui.widget.TMToggleRow android:id="@id/blog_show_tip_button" android:paddingStart="6.0dip" tumblr:rowText="@string/tipping_tip_blog" tumblr:toggleDefault="false" style="@style/SettingsToggleSettingRow" /> | ||||
|                 <com.tumblr.ui.widget.TMToggleRow android:id="@id/blog_post_tipping_default" android:paddingStart="6.0dip" tumblr:rowText="@string/tipping_post_default" tumblr:toggleDefault="false" style="@style/SettingsToggleSettingRow" /> | ||||
|                 <com.tumblr.ui.widget.TMToggleRow android:id="@id/blog_allow_submissions" android:paddingStart="6.0dip" tumblr:rowText="@string/blog_allow_submissions" tumblr:toggleDefault="true" style="@style/SettingsToggleSettingRow" /> | ||||
|                 <com.tumblr.ui.widget.TMToggleRow android:id="@id/blog_allow_asks" android:paddingStart="6.0dip" tumblr:rowText="@string/blog_allow_asks" tumblr:toggleDefault="true" style="@style/SettingsToggleSettingRow" /> | ||||
|                 <com.tumblr.ui.widget.TMToggleRow android:id="@id/blog_ask_allow_anonymous" android:paddingStart="6.0dip" tumblr:rowText="@string/blog_ask_allow_anonymous" tumblr:toggleDefault="true" style="@style/SettingsToggleSettingRow" /> | ||||
|                 <com.tumblr.ui.widget.TMToggleRow android:id="@id/blog_ask_allow_media" android:paddingStart="6.0dip" tumblr:rowText="@string/blog_ask_allow_with_media" tumblr:toggleDefault="true" style="@style/SettingsToggleSettingRow" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_ask_page_title" android:minHeight="0.0dip" tumblr:listItemTitle="@string/blog_ask_page_title" style="@style/BlogSettingsListItem" /> | ||||
|                 <com.tumblr.ui.widget.TMToggleRow android:id="@id/blog_show_top_posts" android:paddingStart="6.0dip" tumblr:rowText="@string/blog_show_top_posts" tumblr:toggleDefault="true" style="@style/SettingsToggleSettingRow" /> | ||||
|                 <com.tumblr.ui.widget.TMBlogSettingsTextRow android:id="@id/blog_blocked_tumblrs_row" tumblr:listItemTitle="@string/blocked_tumblrs_title" style="@style/BlogSettingsListItem" /> | ||||
|             </com.tumblr.ui.widget.UserBlogOptionsLayout> | ||||
|         </LinearLayout> | ||||
|     </LinearLayout> | ||||
| </com.tumblr.ui.widget.fab.ObservableScrollView> | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										3
									
								
								test.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| from buildtool.polly.patcher import process_patch_file | ||||
| 
 | ||||
| process_patch_file('src/patches/public.patch', check_date=True) | ||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								tools/d8.jar
									
										
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								tools/d8.jar
									
										
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	Add table
		
		Reference in a new issue