Import GingerCal 1.0 source
This commit is contained in:
commit
01f49852ab
116 files changed
+5079
No files matched your search
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Checks every Android / Apache HttpClient method and field the dex references against
|
||||
published API declarations (NativeScript's android-platform-*.d.ts). Catches stub mistakes
|
||||
(wrong name, arity, parameter or return kind) that would otherwise surface as
|
||||
NoSuchMethodError on the phone. usage: check_api.py classes.dex android-platform-17.d.ts"""
|
||||
import re
|
||||
import sys
|
||||
from loguru import logger
|
||||
|
||||
logger.remove()
|
||||
from androguard.core.dex import DEX
|
||||
|
||||
dex_path, dts_path = sys.argv[1], sys.argv[2]
|
||||
|
||||
# ----------------------------------------------------------- parse the .d.ts
|
||||
classes = {} # fqcn -> {"supers": [...], "methods": {name: [(params, ret)]}, "fields": set()}
|
||||
stack = [] # (kind, name, depth_at_open)
|
||||
depth = 0
|
||||
cur = None
|
||||
cls_re = re.compile(r"export (?:abstract )?class (\w+)(?:<[^{]*?>)?(?:\s+extends ([\w.]+)(?:<.*?>)?)?(?:\s+implements (.+?))?\s*\{")
|
||||
mod_re = re.compile(r"(?:declare|export) module ([\w.]+) \{")
|
||||
meth_re = re.compile(r"^\s*(?:public |protected )?(?:static )?(\w+)(?:<[^(]*>)?\((.*)\): (.+);\s*$")
|
||||
field_re = re.compile(r"^\s*public (?:static )?(\w+): (.+);\s*$")
|
||||
|
||||
|
||||
def strip_generics(t):
|
||||
out, d = "", 0
|
||||
for ch in t:
|
||||
if ch == "<":
|
||||
d += 1
|
||||
elif ch == ">":
|
||||
d -= 1
|
||||
elif d == 0:
|
||||
out += ch
|
||||
return out.strip()
|
||||
|
||||
|
||||
for line in open(dts_path, encoding="utf-8"):
|
||||
m = mod_re.search(line)
|
||||
c = cls_re.search(line)
|
||||
if c:
|
||||
name = ".".join([s[1] for s in stack if s[0] in ("mod", "cls")] + [c.group(1)])
|
||||
supers = []
|
||||
if c.group(2):
|
||||
supers.append(c.group(2))
|
||||
if c.group(3):
|
||||
supers += [strip_generics(s) for s in strip_generics(c.group(3)).split(",")]
|
||||
classes.setdefault(name, {"supers": [], "methods": {}, "fields": set()})["supers"] += supers
|
||||
stack.append(("cls", c.group(1), depth))
|
||||
cur = classes[name]
|
||||
elif m:
|
||||
for part in m.group(1).split("."):
|
||||
stack.append(("mod", part, depth))
|
||||
elif cur is not None and stack and stack[-1][0] == "cls":
|
||||
mm = meth_re.match(line)
|
||||
if mm and mm.group(1) not in ("constructor",):
|
||||
params = [p for p in mm.group(2).split(", ") if p] if mm.group(2).strip() else []
|
||||
ptypes = [strip_generics(p.split(": ", 1)[1]) if ": " in p else "any" for p in params]
|
||||
cur["methods"].setdefault(mm.group(1), []).append((ptypes, strip_generics(mm.group(3))))
|
||||
elif "public constructor(" in line and "implementation" not in line:
|
||||
inner = line.split("public constructor(", 1)[1].rsplit(");", 1)[0]
|
||||
params = [p for p in inner.split(", ") if p] if inner.strip() else []
|
||||
cur["methods"].setdefault("<init>", []).append(
|
||||
([strip_generics(p.split(": ", 1)[1]) for p in params], "void"))
|
||||
else:
|
||||
fm = field_re.match(line)
|
||||
if fm:
|
||||
cur["fields"].add(fm.group(1))
|
||||
depth += line.count("{") - line.count("}")
|
||||
while stack and depth <= stack[-1][2]:
|
||||
stack.pop()
|
||||
cur = classes.get(".".join(s[1] for s in stack if s[0] in ("mod", "cls"))) if stack and stack[-1][0] == "cls" else None
|
||||
|
||||
# ------------------------------------------------------------- type mapping
|
||||
NUM = {"I", "J", "F", "D", "S", "B", "C"}
|
||||
|
||||
|
||||
def ts_of(desc):
|
||||
if desc in NUM:
|
||||
return "number"
|
||||
if desc == "Z":
|
||||
return "boolean"
|
||||
if desc == "V":
|
||||
return "void"
|
||||
if desc.startswith("["):
|
||||
return "native.Array<%s>" % ts_of(desc[1:])
|
||||
n = desc[1:-1].replace("/", ".").replace("$", ".")
|
||||
if n in ("java.lang.String", "java.lang.CharSequence"):
|
||||
return "string"
|
||||
return n
|
||||
|
||||
|
||||
def compatible(java, ts):
|
||||
ts = ts.replace(" ", "")
|
||||
if ts in ("any", "any[]"):
|
||||
return True
|
||||
if java.startswith("native.Array"):
|
||||
return ts.startswith("native.Array") or ts.endswith("[]")
|
||||
if java == "java.lang.Object":
|
||||
return True
|
||||
return java == ts or (java == "string" and ts == "string")
|
||||
|
||||
|
||||
def lookup(cls, name, params, ret, seen=None):
|
||||
seen = seen or set()
|
||||
if cls in seen or cls not in classes:
|
||||
return False
|
||||
seen.add(cls)
|
||||
for ptypes, r in classes[cls]["methods"].get(name, []):
|
||||
if len(ptypes) == len(params) and all(compatible(j, t) for j, t in zip(params, ptypes)):
|
||||
if name == "<init>" or compatible(ret, r) or r == "any":
|
||||
return True
|
||||
return any(lookup(s, name, params, ret, seen) for s in classes[cls]["supers"])
|
||||
|
||||
|
||||
def has_field(cls, name, seen=None):
|
||||
seen = seen or set()
|
||||
if cls in seen or cls not in classes:
|
||||
return False
|
||||
seen.add(cls)
|
||||
return name in classes[cls]["fields"] or any(has_field(s, name, seen) for s in classes[cls]["supers"])
|
||||
|
||||
|
||||
# ------------------------------------------------------------- check the dex
|
||||
raw = open(dex_path, "rb").read()
|
||||
if raw[:2] == b"PK":
|
||||
import zipfile
|
||||
raw = zipfile.ZipFile(dex_path).read("classes.dex")
|
||||
d = DEX(raw)
|
||||
own = {c.get_name() for c in d.get_classes()}
|
||||
bad, checked, seen = [], 0, set()
|
||||
for mid in d.get_methods_id_item().get_obj():
|
||||
cname = mid.get_class_name()
|
||||
if not (cname.startswith("Landroid/") or cname.startswith("Lorg/apache/http/")) or cname in own:
|
||||
continue
|
||||
desc = mid.get_descriptor().replace(" ", "")
|
||||
key = (cname, mid.get_name(), desc)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
plist = re.findall(r"\[*(?:L[^;]+;|[ZBCSIJFD])", desc[1:desc.index(")")])
|
||||
ret = desc[desc.index(")") + 1:]
|
||||
cls = cname[1:-1].replace("/", ".").replace("$", ".")
|
||||
checked += 1
|
||||
if not lookup(cls, mid.get_name(), [ts_of(p) for p in plist], ts_of(ret)):
|
||||
bad.append("%s.%s%s" % (cls, mid.get_name(), desc))
|
||||
for fid in d.get_fields_id_item().get_obj():
|
||||
cname = fid.get_class_name()
|
||||
if not (cname.startswith("Landroid/") or cname.startswith("Lorg/apache/http/")) or cname in own:
|
||||
continue
|
||||
cls = cname[1:-1].replace("/", ".").replace("$", ".")
|
||||
checked += 1
|
||||
if not has_field(cls, fid.get_name()):
|
||||
bad.append("field %s.%s" % (cls, fid.get_name()))
|
||||
print("checked %d framework references against %d declared classes" % (checked, len(classes)))
|
||||
for b in bad:
|
||||
print(" NOT FOUND:", b)
|
||||
sys.exit(1 if bad else 0)
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Packs an APK without aapt: binary AndroidManifest.xml, a tiny resources.arsc
|
||||
(icon + app name), classes.dex. Output is unsigned; build.sh signs and aligns it.
|
||||
|
||||
usage: mkapk.py AndroidManifest.xml classes.dex icon.png out.apk
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
ANDROID_NS = "http://schemas.android.com/apk/res/android"
|
||||
|
||||
# android.R.attr ids (stable since API 1-4)
|
||||
ATTR_IDS = {
|
||||
"theme": 0x01010000, "label": 0x01010001, "icon": 0x01010002, "name": 0x01010003,
|
||||
"permission": 0x01010006, "enabled": 0x0101000e, "exported": 0x01010010,
|
||||
"configChanges": 0x0101001f, "minSdkVersion": 0x0101020c, "versionCode": 0x0101021b,
|
||||
"versionName": 0x0101021c, "windowSoftInputMode": 0x0101022b,
|
||||
"targetSdkVersion": 0x01010270, "installLocation": 0x010102b7,
|
||||
}
|
||||
INT_ATTRS = {"versionCode", "minSdkVersion", "targetSdkVersion"}
|
||||
BOOL_ATTRS = {"exported", "enabled"}
|
||||
FLAGS = {
|
||||
"configChanges": {"mcc": 1, "mnc": 2, "locale": 4, "touchscreen": 8, "keyboard": 0x10,
|
||||
"keyboardHidden": 0x20, "navigation": 0x40, "orientation": 0x80,
|
||||
"screenLayout": 0x100, "uiMode": 0x200, "fontScale": 0x40000000},
|
||||
"windowSoftInputMode": {"stateUnspecified": 0, "stateHidden": 2, "stateAlwaysHidden": 3,
|
||||
"adjustResize": 0x10, "adjustPan": 0x20},
|
||||
"installLocation": {"auto": 0, "internalOnly": 1, "preferExternal": 2},
|
||||
}
|
||||
|
||||
T_REF, T_STRING, T_INT_DEC, T_INT_HEX, T_BOOL = 0x01, 0x03, 0x10, 0x11, 0x12
|
||||
|
||||
# Our own resources: type id -> (type name, [(key, value)])
|
||||
RES_TYPES = [("drawable", [("icon", "res/drawable/icon.png")]),
|
||||
("string", [("app_name", "GingerCal")])]
|
||||
|
||||
|
||||
def res_id(ref):
|
||||
typ, key = ref.lstrip("@").split("/")
|
||||
for ti, (tname, entries) in enumerate(RES_TYPES):
|
||||
for ei, (k, _) in enumerate(entries):
|
||||
if tname == typ and k == key:
|
||||
return 0x7F000000 | ((ti + 1) << 16) | ei
|
||||
raise SystemExit("unknown resource " + ref)
|
||||
|
||||
|
||||
# ------------------------------------------------------------- string pool
|
||||
|
||||
def string_pool(strings):
|
||||
"""UTF-16 ResStringPool (understood by every Android version)."""
|
||||
offsets, data = [], b""
|
||||
for s in strings:
|
||||
offsets.append(len(data))
|
||||
u = s.encode("utf-16-le")
|
||||
n = len(u) // 2
|
||||
if n > 0x7FFF:
|
||||
raise SystemExit("string too long")
|
||||
data += struct.pack("<H", n) + u + b"\0\0"
|
||||
while len(data) % 4:
|
||||
data += b"\0"
|
||||
header = 28
|
||||
strings_start = header + 4 * len(strings)
|
||||
size = strings_start + len(data)
|
||||
return (struct.pack("<HHIIIIII", 0x0001, header, size, len(strings), 0, 0, strings_start, 0)
|
||||
+ b"".join(struct.pack("<I", o) for o in offsets) + data)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- AXML
|
||||
|
||||
def axml(manifest_path):
|
||||
tree = ET.parse(manifest_path)
|
||||
root = tree.getroot()
|
||||
|
||||
# collect strings: attribute names with resource ids first (resource map order)
|
||||
attr_names, other = [], []
|
||||
|
||||
def walk(e):
|
||||
for k in e.attrib:
|
||||
ns, local = split(k)
|
||||
if ns == ANDROID_NS:
|
||||
if local not in ATTR_IDS:
|
||||
raise SystemExit("add resource id for android:" + local)
|
||||
if local not in attr_names:
|
||||
attr_names.append(local)
|
||||
for c in e:
|
||||
walk(c)
|
||||
|
||||
def split(k):
|
||||
if k.startswith("{"):
|
||||
ns, local = k[1:].split("}")
|
||||
return ns, local
|
||||
return None, k
|
||||
|
||||
walk(root)
|
||||
attr_names.sort(key=lambda n: ATTR_IDS[n])
|
||||
strings = list(attr_names)
|
||||
|
||||
def idx(s):
|
||||
if s not in strings:
|
||||
strings.append(s)
|
||||
return strings.index(s)
|
||||
|
||||
idx("android")
|
||||
idx(ANDROID_NS)
|
||||
|
||||
events = [] # built after strings are known; strings may grow while encoding
|
||||
|
||||
def encode_value(local, ns, v):
|
||||
if v.startswith("@") and "/" in v and not v.startswith("@android:"):
|
||||
return 0xFFFFFFFF, T_REF, res_id(v)
|
||||
if ns == ANDROID_NS and local in INT_ATTRS:
|
||||
return 0xFFFFFFFF, T_INT_DEC, int(v)
|
||||
if ns == ANDROID_NS and local in BOOL_ATTRS:
|
||||
return 0xFFFFFFFF, T_BOOL, 0xFFFFFFFF if v == "true" else 0
|
||||
if ns == ANDROID_NS and local in FLAGS:
|
||||
n = 0
|
||||
for part in v.split("|"):
|
||||
n |= FLAGS[local][part.strip()]
|
||||
return 0xFFFFFFFF, T_INT_HEX, n
|
||||
si = idx(v)
|
||||
return si, T_STRING, si
|
||||
|
||||
def element(e, line):
|
||||
ns, local = split(e.tag)
|
||||
attrs = []
|
||||
for k, v in e.attrib.items():
|
||||
ans, alocal = split(k)
|
||||
raw, typ, data = encode_value(alocal, ans, v)
|
||||
attrs.append((ATTR_IDS.get(alocal, 0) if ans == ANDROID_NS else 0,
|
||||
idx(ANDROID_NS) if ans == ANDROID_NS else 0xFFFFFFFF, idx(alocal), raw, typ, data))
|
||||
attrs.sort(key=lambda a: a[0]) # Android looks attributes up in resource-id order
|
||||
name = idx(local)
|
||||
body = struct.pack("<IIHHHHHH", 0xFFFFFFFF, name, 20, 20, len(attrs), 0, 0, 0)
|
||||
for _, ans, an, raw, typ, data in attrs:
|
||||
body += struct.pack("<IIIHBBI", ans, an, raw, 8, 0, typ, data & 0xFFFFFFFF)
|
||||
events.append(struct.pack("<HHIII", 0x0102, 16, 16 + len(body), line, 0xFFFFFFFF) + body)
|
||||
for c in e:
|
||||
line = element(c, line + 1)
|
||||
events.append(struct.pack("<HHIIIII", 0x0103, 16, 24, line, 0xFFFFFFFF, 0xFFFFFFFF, name))
|
||||
return line + 1
|
||||
|
||||
ns_chunk = lambda t: struct.pack("<HHIIIII", t, 16, 24, 1, 0xFFFFFFFF, idx("android"), idx(ANDROID_NS))
|
||||
events.append(ns_chunk(0x0100))
|
||||
element(root, 2)
|
||||
events.append(ns_chunk(0x0101))
|
||||
|
||||
pool = string_pool(strings)
|
||||
resmap = struct.pack("<HHI", 0x0180, 8, 8 + 4 * len(attr_names)) + b"".join(
|
||||
struct.pack("<I", ATTR_IDS[n]) for n in attr_names)
|
||||
body = pool + resmap + b"".join(events)
|
||||
return struct.pack("<HHI", 0x0003, 8, 8 + len(body)) + body
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- ARSC
|
||||
|
||||
def arsc(package):
|
||||
values = [v for _, entries in RES_TYPES for _, v in entries]
|
||||
global_pool = string_pool(values)
|
||||
type_pool = string_pool([t for t, _ in RES_TYPES])
|
||||
key_pool = string_pool([k for _, entries in RES_TYPES for k, _ in entries])
|
||||
|
||||
chunks = b""
|
||||
vi = 0
|
||||
ki = 0
|
||||
for ti, (tname, entries) in enumerate(RES_TYPES):
|
||||
tid = ti + 1
|
||||
n = len(entries)
|
||||
spec = struct.pack("<HHIBBHI", 0x0202, 16, 16 + 4 * n, tid, 0, 0, n) + b"\0\0\0\0" * n
|
||||
config = struct.pack("<I", 32) + b"\0" * 28 # default configuration
|
||||
header = 20 + len(config)
|
||||
entry_data = b""
|
||||
offsets = b""
|
||||
for _ in entries:
|
||||
offsets += struct.pack("<I", len(entry_data))
|
||||
entry_data += struct.pack("<HHI", 8, 0, ki) + struct.pack("<HBBI", 8, 0, T_STRING, vi)
|
||||
ki += 1
|
||||
vi += 1
|
||||
entries_start = header + 4 * n
|
||||
typ = struct.pack("<HHIBBHII", 0x0201, header, entries_start + len(entry_data), tid, 0, 0, n,
|
||||
entries_start) + config + offsets + entry_data
|
||||
chunks += spec + typ
|
||||
|
||||
pkg_header = 284 # pre-Lollipop ResTable_package (no typeIdOffset)
|
||||
name = package.encode("utf-16-le")[:254].ljust(256, b"\0")
|
||||
type_off = pkg_header
|
||||
key_off = type_off + len(type_pool)
|
||||
pkg_size = key_off + len(key_pool) + len(chunks)
|
||||
pkg = struct.pack("<HHII", 0x0200, pkg_header, pkg_size, 0x7F) + name + struct.pack(
|
||||
"<IIII", type_off, len(RES_TYPES), key_off, ki) + type_pool + key_pool + chunks
|
||||
assert len(struct.pack("<HHII", 0, 0, 0, 0) + name + struct.pack("<IIII", 0, 0, 0, 0)) == pkg_header
|
||||
body = global_pool + pkg
|
||||
return struct.pack("<HHII", 0x0002, 12, 12 + len(body), 1) + body
|
||||
|
||||
|
||||
def main():
|
||||
manifest, dex, icon, out = sys.argv[1:5]
|
||||
package = ET.parse(manifest).getroot().attrib["package"]
|
||||
with zipfile.ZipFile(out, "w") as z:
|
||||
z.writestr(zipfile.ZipInfo("AndroidManifest.xml", (2026, 1, 1, 0, 0, 0)), axml(manifest),
|
||||
compress_type=zipfile.ZIP_DEFLATED)
|
||||
z.writestr(zipfile.ZipInfo("classes.dex", (2026, 1, 1, 0, 0, 0)), open(dex, "rb").read(),
|
||||
compress_type=zipfile.ZIP_DEFLATED)
|
||||
z.writestr(zipfile.ZipInfo("resources.arsc", (2026, 1, 1, 0, 0, 0)), arsc(package),
|
||||
compress_type=zipfile.ZIP_STORED)
|
||||
z.writestr(zipfile.ZipInfo("res/drawable/icon.png", (2026, 1, 1, 0, 0, 0)), open(icon, "rb").read(),
|
||||
compress_type=zipfile.ZIP_STORED)
|
||||
print("wrote", out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Draws the 48x48 (mdpi) launcher icon: a little calendar page in ginger tones."""
|
||||
import sys
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
S = 4 # supersample then shrink for smooth edges
|
||||
W = 48 * S
|
||||
img = Image.new("RGBA", (W, W), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(img)
|
||||
# shadow
|
||||
d.rounded_rectangle((5 * S, 7 * S, 45 * S, 46 * S), radius=6 * S, fill=(0, 0, 0, 90))
|
||||
# page
|
||||
d.rounded_rectangle((3 * S, 5 * S, 43 * S, 44 * S), radius=6 * S, fill=(250, 246, 238, 255))
|
||||
# ginger header band
|
||||
d.rounded_rectangle((3 * S, 5 * S, 43 * S, 17 * S), radius=6 * S, fill=(214, 110, 42, 255))
|
||||
d.rectangle((3 * S, 11 * S, 43 * S, 17 * S), fill=(214, 110, 42, 255))
|
||||
# binder rings
|
||||
for x in (13, 33):
|
||||
d.rounded_rectangle(((x - 2) * S, 2 * S, (x + 2) * S, 10 * S), radius=2 * S, fill=(90, 60, 40, 255))
|
||||
# day grid, one day highlighted
|
||||
for row in range(3):
|
||||
for col in range(4):
|
||||
x0 = (8 + col * 8.5) * S
|
||||
y0 = (21 + row * 7.5) * S
|
||||
box = (x0, y0, x0 + 5.5 * S, y0 + 5 * S)
|
||||
if (row, col) == (1, 2):
|
||||
d.rounded_rectangle(box, radius=1.5 * S, fill=(0, 130, 201, 255)) # Nextcloud blue
|
||||
else:
|
||||
d.rounded_rectangle(box, radius=1.5 * S, fill=(200, 188, 172, 255))
|
||||
img = img.resize((48, 48), Image.LANCZOS)
|
||||
img.save(sys.argv[1] if len(sys.argv) > 1 else "icon.png", optimize=True)
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Removes MethodParameters attributes from .class files (in place).
|
||||
|
||||
JDK 21+ javac always emits them for inner-class constructors, and older d8 releases
|
||||
crash on the nameless ones. They are only reflection metadata, so dropping them is safe.
|
||||
usage: strip_method_params.py file.class...
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def strip(path):
|
||||
d = open(path, "rb").read()
|
||||
n = struct.unpack(">H", d[8:10])[0]
|
||||
p, i, utf8 = 10, 1, {}
|
||||
while i < n:
|
||||
t = d[p]
|
||||
if t == 1:
|
||||
ln = struct.unpack(">H", d[p + 1:p + 3])[0]
|
||||
utf8[i] = d[p + 3:p + 3 + ln]
|
||||
p += 3 + ln
|
||||
elif t in (3, 4, 9, 10, 11, 12, 17, 18):
|
||||
p += 5
|
||||
elif t in (5, 6):
|
||||
p += 9
|
||||
i += 1
|
||||
elif t in (7, 8, 16, 19, 20):
|
||||
p += 3
|
||||
elif t == 15:
|
||||
p += 4
|
||||
else:
|
||||
raise ValueError("bad constant tag %d in %s" % (t, path))
|
||||
i += 1
|
||||
out = bytearray(d[:p])
|
||||
p_access = p
|
||||
ifaces = struct.unpack(">H", d[p + 6:p + 8])[0]
|
||||
p += 8 + 2 * ifaces
|
||||
out += d[p_access:p]
|
||||
|
||||
def members(p, drop):
|
||||
count = struct.unpack(">H", d[p:p + 2])[0]
|
||||
buf = bytearray(d[p:p + 2])
|
||||
p += 2
|
||||
for _ in range(count):
|
||||
buf += d[p:p + 6]
|
||||
acount = struct.unpack(">H", d[p + 6:p + 8])[0]
|
||||
p += 8
|
||||
kept = []
|
||||
for _ in range(acount):
|
||||
name, ln = struct.unpack(">HI", d[p:p + 6])
|
||||
if utf8.get(name) != drop:
|
||||
kept.append(d[p:p + 6 + ln])
|
||||
p += 6 + ln
|
||||
buf += struct.pack(">H", len(kept)) + b"".join(kept)
|
||||
return p, buf
|
||||
|
||||
p, fields = members(p, None)
|
||||
out += fields
|
||||
p, methods = members(p, b"MethodParameters")
|
||||
out += methods
|
||||
out += d[p:]
|
||||
if bytes(out) != d:
|
||||
open(path, "wb").write(bytes(out))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
changed = sum(strip(f) for f in sys.argv[1:])
|
||||
print("stripped MethodParameters from %d class files" % changed)
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal zipalign: stored entries start on 4-byte boundaries. Keeps v1 (JAR) signatures valid,
|
||||
since those cover file contents, not the zip layout. usage: zipalign.py in.apk out.apk"""
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
src, dst = sys.argv[1], sys.argv[2]
|
||||
with zipfile.ZipFile(src) as zin, open(dst, "wb") as f:
|
||||
with zipfile.ZipFile(f, "w") as zout:
|
||||
for info in zin.infolist():
|
||||
data = zin.read(info.filename)
|
||||
out = zipfile.ZipInfo(info.filename, info.date_time)
|
||||
out.compress_type = info.compress_type
|
||||
out.external_attr = info.external_attr
|
||||
if info.compress_type == zipfile.ZIP_STORED:
|
||||
header_end = f.tell() + 30 + len(info.filename.encode("utf-8"))
|
||||
out.extra = b"\0" * ((4 - header_end % 4) % 4)
|
||||
zout.writestr(out, data)
|
||||
with zipfile.ZipFile(dst) as z:
|
||||
for info in z.infolist():
|
||||
if info.compress_type == zipfile.ZIP_STORED:
|
||||
start = info.header_offset + 30 + len(info.filename.encode("utf-8")) + len(info.extra)
|
||||
assert start % 4 == 0, info.filename
|
||||
print("aligned", dst)
|
||||
Reference in new issue
Block a user