From 1fbc7f14a08fa9dadb4afb64304c61c196856819 Mon Sep 17 00:00:00 2001 From: Sleeeee Date: Mon, 4 May 2026 04:50:49 -0400 Subject: [PATCH 1/3] gitignore pycache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0a7f648..986faec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store .gadgetCache/ +__pycache__/ From ff0fcdc84e6a7e3d0e7fe28827337d8a69fa80da Mon Sep 17 00:00:00 2001 From: Sleeeee Date: Mon, 4 May 2026 04:55:17 -0400 Subject: [PATCH 2/3] Fix Apktool namespace mismatching bug --- APK.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/APK.py b/APK.py index 1fefdbc..d98a15d 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,30 @@ 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_error(self, stderr: str) -> bool: + pattern = re.compile(r"W:\s+(?P.*?):\d+:\s+error:\s+attribute\s+android:(?P[^\s]+)\s+not found\.") + matches = list(pattern.finditer(stderr)) + + if not matches: + return False + + 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") + return True From 499873f3520853b25ff0420515089506e620945d Mon Sep 17 00:00:00 2001 From: Sleeeee Date: Mon, 4 May 2026 07:41:09 -0400 Subject: [PATCH 3/3] Fix Apktool duplicate attributes and incompatible flags errors --- APK.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/APK.py b/APK.py index d98a15d..b39b66a 100644 --- a/APK.py +++ b/APK.py @@ -550,13 +550,7 @@ def _fix_private_resources(self, base: str): count += 1 Log.verbose(f" Forced {count} private resource refs to public") - def _fix_apktool_error(self, stderr: str) -> bool: - pattern = re.compile(r"W:\s+(?P.*?):\d+:\s+error:\s+attribute\s+android:(?P[^\s]+)\s+not found\.") - matches = list(pattern.finditer(stderr)) - - if not matches: - return False - + def _fix_apktool_namespaces(self, matches): Log.info("Detected resource namespace mismatch, trying to resolve automatically") count = 0 @@ -575,4 +569,75 @@ def _fix_apktool_error(self, stderr: str) -> bool: except Exception as e: Log.abort(e) Log.info(f"Fixed {count} broken namespaces, restarting build") - return True + + 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 +