diff --git a/.gitignore b/.gitignore index 0a7f648..986faec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store .gadgetCache/ +__pycache__/ diff --git a/APK.py b/APK.py index 1fefdbc..b39b66a 100644 --- a/APK.py +++ b/APK.py @@ -231,8 +231,12 @@ def _apktool(self, args: List[str], ok_required: bool = False): Log.verbose(f"[apktool] {exe} {' '.join(args)}\n{cp.stdout}") if cp.returncode != 0: Log.verbose(cp.stderr) - if ok_required and cp.returncode != 0: - Log.abort(f"apktool failed: \n\n{exe}{' '.join(args)}\n\n" + cp.stdout +"\n\n---\n\n"+ cp.stderr) + while ok_required and cp.returncode != 0: + fixed = self._fix_apktool_error(cp.stderr) + if fixed: + cp = subprocess.run([exe, *args], input="\r\n", text=True, capture_output=True) + else: + Log.abort(f"apktool failed: \n\n{exe}{' '.join(args)}\n\n" + cp.stdout +"\n\n---\n\n"+ cp.stderr) def _run(self, args: List[str], ok_required: bool = False): Log.verbose(f"[{args[0]}] {' '.join(args)}") @@ -495,7 +499,7 @@ def _find_smali_file(root: str, rel: str): r'(?m)^(\s*\.method\s+static\s+constructor\s+\(\)V\s*$)', r'\1\n .registers 1', clinit_block, - count=1, + count=1, ) # Insert the load instructions after the (possibly updated) .registers line. @@ -545,3 +549,95 @@ def _fix_private_resources(self, base: str): fh.write(ns) count += 1 Log.verbose(f" Forced {count} private resource refs to public") + + def _fix_apktool_namespaces(self, matches): + Log.info("Detected resource namespace mismatch, trying to resolve automatically") + + count = 0 + for m in matches: + path, attr = m.group("path"), m.group("attr") + data = "" + try: + with open(path, "r", encoding="utf-8") as f: + data = f.read() + if data: + data = data.replace(f"android:{attr}", attr) + with open(path, "w", encoding="utf-8") as f: + f.write(data) + count += 1 + + except Exception as e: + Log.abort(e) + Log.info(f"Fixed {count} broken namespaces, restarting build") + + def _fix_apktool_duplicates(self, matches): + Log.info("Detected duplicate attribute error, trying to resolve automatically") + + # Matches duplicates attributes, and groups the first attribute occurence (\1), the duplicate (\2) and anything in between (\3) + dup_pattern = re.compile(r'(\b([a-zA-Z0-9_:]+)="[^"]*")(.*?)\b\2="[^"]*"') + + count = 0 + for m in matches: + path = m.group("path") + data = "" + try: + with open(path, "r", encoding="utf-8") as f: + data = f.read() + if data and dup_pattern.search(data): + while dup_pattern.search(data): + # Rewrites contents without the duplicate attribute in \2 + data = dup_pattern.sub(r"\1\3", data) + + with open(path, "w", encoding="utf-8") as f: + f.write(data) + count += 1 + + except Exception as e: + Log.abort(e) + Log.info(f"Removed {count} duplicates, restarting build") + + def _fix_apktool_incompatible_flags(self, matches): + Log.info("Detected incompatible flags error, trying to resolve automatically") + + count = 0 + for m in matches: + path, attr, value = m.group("path"), m.group("attr"), m.group("value") + incomp_pattern = re.compile(rf'\s+(?:[a-zA-Z0-9_]+:)?{attr}="0x0"') + data = "" + try: + with open(path, "r", encoding="utf-8") as f: + data = f.read() + if data: + data = incomp_pattern.sub("", data) + with open(path, "w", encoding="utf-8") as f: + f.write(data) + count += 1 + + except Exception as e: + Log.abort(e) + Log.info(f"Removed {count} incompatible flags, restarting build") + + def _fix_apktool_error(self, stderr: str) -> bool: + # Matches "attribue android:example not found" error + namespace_error = re.compile(r"W:\s+(?P.*?):\d+:\s+error:\s+attribute\s+android:(?P[^\s]+)\s+not found\.") + matches = list(namespace_error.finditer(stderr)) + if matches: + self._fix_apktool_namespaces(matches) + return True + + # Matches "duplicate attribute" error + duplicates_error = re.compile(r"W:\s+(?P.*?):\d+:\s+error:\s+duplicate\s+attribute\.") + matches = list(duplicates_error.finditer(stderr)) + if matches: + self._fix_apktool_duplicates(matches) + return True + + # Matches "incompatible with attribute" error + incompatible_flags_error = re.compile(r"W:\s+(?P.*?):\d+:\s*error:\s*'(?P[^']+)'\s*is incompatible with attribute\s*(?P\w+).*?\[(?P[^\]]+)\]") + matches = list(incompatible_flags_error.finditer(stderr)) + if matches: + self._fix_apktool_incompatible_flags(matches) + return True + + return False +