Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
.gadgetCache/
__pycache__/
102 changes: 99 additions & 3 deletions APK.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}")
Expand Down Expand Up @@ -495,7 +499,7 @@ def _find_smali_file(root: str, rel: str):
r'(?m)^(\s*\.method\s+static\s+constructor\s+<clinit>\(\)V\s*$)',
r'\1\n .registers 1',
clinit_block,
count=1,
count=1,
)

# Insert the load instructions after the (possibly updated) .registers line.
Expand Down Expand Up @@ -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<path>.*?):\d+:\s+error:\s+attribute\s+android:(?P<attr>[^\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<path>.*?):\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<path>.*?):\d+:\s*error:\s*'(?P<value>[^']+)'\s*is incompatible with attribute\s*(?P<attr>\w+).*?\[(?P<allowed>[^\]]+)\]")
matches = list(incompatible_flags_error.finditer(stderr))
if matches:
self._fix_apktool_incompatible_flags(matches)
return True

return False