Compare commits
4 commits
85923ac1a5
...
8a7b95beee
Author | SHA1 | Date | |
---|---|---|---|
|
8a7b95beee | ||
![]() |
f2ada0e2fe | ||
![]() |
e8ffddf93f | ||
|
9e065cd638 |
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()
|
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…
Reference in a new issue