soul-browser/scripts/sync-public-xml.py
KaKi87 550ddb0413
Decompile and recreate Soul Browser v1.4.85 with APK CI builds (#1)
* Decompile and recreate Soul Browser v1.4.85 with APK CI builds

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Fix CI build: include bundled google.jks and remove PR trigger

The apktool build failed in CI because app/unknown/.../google.jks was
excluded by the *.jks gitignore rule. Whitelist app-bundled JKS files
and commit the missing resource.

Also remove the redundant pull_request workflow trigger.

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Change package ID to com.github.kaki87.soulbrowser

Rename the application ID so the rebuilt APK can be installed
alongside the original Soul Browser from Google Play.

- Update AndroidManifest package, permissions, providers, and actions
- Update R class references in smali (com/github/kaki87/soulbrowser/R)
- Update shortcuts.xml targetPackage
- Rename app label to "Soul Rebuild" for easy identification

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Fix install compatibility: remove split APK metadata and bundle native libs

The rebuilt APK still declared requiredSplitTypes and Play Store split
metadata, causing Android to reject standalone installs as incompatible.

- Remove requiredSplitTypes and split-related manifest meta-data
- Merge native libs from all ABI splits with uncompressed storage
- Page-align before signing for extractNativeLibs=false
- Add .so to apktool doNotCompress list

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Fix install failure: use apksigner v2/v3 and bundle arm64 native libs

Target SDK 36 requires APK Signature Scheme v2+, but jarsigner only
produces v1 signatures, causing Android to reject the install.

- Sign with apksigner (v1+v2+v3) instead of jarsigner
- Add arm64-v8a native libraries from universal Soul Browser 1.4.79
- Set extractNativeLibs=true for reliable sideload installs
- Commit stable debug keystore for consistent signatures across builds

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Add committed debug keystore for consistent APK signatures

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Fix startup crash: use original classes2.dex desugar libraries

Apktool recompiles smali_classes2 into a broken classes2.dex, causing
ClassNotFoundException for j$.com.android.tools.r8.a at ML Kit init.

Inject the original classes2.dex (Java 8+ desugar libs) after apktool
build instead of using the recompiled version.

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Fix missing drawable resources from density split APKs

The base APK is an app bundle module; density-specific drawables like
seek_thumb_nor_b live in config.xhdpi.apk and were missing after rebuild,
causing Resources$NotFoundException at runtime.

- Merge non-9-patch resources from split APKs before apktool build
- Sync public.xml IDs from R smali only when backing files exist
- Add 713 density-specific resource IDs to public.xml

Co-authored-by: KaKi87 <KaKi87@pm.me>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-10 16:07:14 +02:00

89 lines
3 KiB
Python
Executable file

#!/usr/bin/env python3
"""Add missing resource IDs from R$*.smali into public.xml."""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
R_DIR = ROOT / "app" / "smali_classes3" / "com" / "mycompany" / "app" / "soulbrowser"
PUBLIC_XML = ROOT / "app" / "res" / "values" / "public.xml"
FIELD_RE = re.compile(r"\.field public static (\w+):I = (0x[0-9a-f]+)")
ENTRY_RE = re.compile(r'<public type="([^"]+)" name="([^"]+)" id="(0x[0-9a-f]+)"')
def resource_file_exists(rtype: str, name: str) -> bool:
res_dir = ROOT / "app" / "res"
if rtype == "drawable":
patterns = [f"**/{name}.png", f"**/{name}.xml", f"**/{name}.webp", f"**/{name}.9.png"]
elif rtype == "mipmap":
patterns = [f"**/{name}.png", f"**/{name}.webp"]
elif rtype == "layout":
patterns = [f"layout*/{name}.xml", f"**/{name}.xml"]
elif rtype == "xml":
patterns = [f"xml/{name}.xml"]
elif rtype == "raw":
patterns = [f"raw/{name}.*"]
else:
patterns = [f"**/{name}.xml"]
return any(any(res_dir.glob(pattern)) for pattern in patterns)
def load_r_entries() -> list[tuple[str, str, str]]:
entries: list[tuple[str, str, str]] = []
for smali in sorted(R_DIR.glob("R$*.smali")):
rtype = smali.stem.removeprefix("R$")
text = smali.read_text(encoding="utf-8")
for name, rid in FIELD_RE.findall(text):
entries.append((rtype, name, rid))
return entries
def main() -> int:
if not PUBLIC_XML.exists():
print(f"error: missing {PUBLIC_XML}", file=sys.stderr)
return 1
content = PUBLIC_XML.read_text(encoding="utf-8")
existing = {(m.group(1), m.group(2), m.group(3)) for m in ENTRY_RE.finditer(content)}
existing_ids = {m.group(3) for m in ENTRY_RE.finditer(content)}
existing_names = {(m.group(1), m.group(2)) for m in ENTRY_RE.finditer(content)}
missing: list[tuple[str, str, str]] = []
for rtype, name, rid in load_r_entries():
key = (rtype, name, rid)
if key in existing:
continue
if rid in existing_ids or (rtype, name) in existing_names:
continue
if not resource_file_exists(rtype, name):
continue
missing.append(key)
if not missing:
print("public.xml is already complete")
return 0
lines = [f' <public type="{rtype}" name="{name}" id="{rid}" />' for rtype, name, rid in missing]
insertion = "\n".join(lines) + "\n"
if content.rstrip().endswith("</resources>"):
updated = re.sub(r"</resources>\s*$", insertion + "</resources>\n", content, count=1)
else:
print("error: public.xml missing </resources> footer", file=sys.stderr)
return 1
if updated == content:
print("error: failed to update public.xml", file=sys.stderr)
return 1
PUBLIC_XML.write_text(updated, encoding="utf-8")
print(f"Added {len(missing)} missing public.xml entries")
return 0
if __name__ == "__main__":
raise SystemExit(main())