diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 14006ee..e5a0cbd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,22 +60,15 @@ jobs: workdir=$(mktemp -d) unzip -q "$tidal_src" -d "$workdir" ls "$workdir" - cp "$workdir/base.apk" ./dist/tidal-stock.apk - dist_abs=$(realpath ./dist/tidal-stock.apk) + dist_abs=$(realpath ./dist)/tidal-stock.apk + + javac scripts/MergeApk.java -d scripts + splits=("$workdir"/split_*.apk) + java -cp scripts MergeApk "$dist_abs" "$workdir/base.apk" "${splits[@]}" - # Merge every split APK's contents into base.apk - for split in "$workdir"/split_*.apk "$workdir"/config.*.apk; do - [ -f "$split" ] || continue - echo "Merging $(basename "$split")" - libdir=$(mktemp -d) - unzip -q "$split" -d "$libdir" - rm -rf "$libdir/META-INF" "$libdir/AndroidManifest.xml" "$libdir/resources.arsc" - (cd "$libdir" && zip -qrD "$dist_abs" .) - rm -rf "$libdir" - done rm -rf "$workdir" - echo "Merged tidal-stock.apk size:" - ls -la ./dist/tidal-stock.apk + echo "Merged tidal-stock.apk:" + ls -la "$dist_abs" else cp "$tidal_src" ./dist/tidal-stock.apk fi diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt index c2dd4e6..37d2334 100644 --- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt +++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt @@ -44,7 +44,12 @@ class PatchManifestStep(private val options: PatchOptions) : Step() { val bytes = if (name == "AndroidManifest.xml") { patchedManifest } else { - reader.openEntry(name)!!.read() + try { + reader.openEntry(name)!!.read() + } catch (t: Throwable) { + container.log("Failed to read entry: $name") + throw t + } } writer.writeEntry(name, bytes) } diff --git a/scripts/MergeApk.java b/scripts/MergeApk.java new file mode 100644 index 0000000..dd90e74 --- /dev/null +++ b/scripts/MergeApk.java @@ -0,0 +1,61 @@ +import java.io.*; +import java.util.*; +import java.util.zip.*; + +// Merges an APKMirror split-APK bundle into a single installable APK. +// Usage: java MergeApk out.apk base.apk split1.apk split2.apk ... +public class MergeApk { + public static void main(String[] args) throws Exception { + if (args.length < 2) { + System.err.println("Usage: java MergeApk out.apk base.apk [split.apk ...]"); + System.exit(1); + } + String out = args[0]; + Set seen = new HashSet<>(); + try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(out)))) { + for (int i = 1; i < args.length; i++) { + String src = args[i]; + boolean isBase = i == 1; + int added = 0, skipped = 0; + try (ZipFile zip = new ZipFile(src)) { + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry e = entries.nextElement(); + if (e.isDirectory()) { skipped++; continue; } + String name = e.getName(); + if (!isBase && ( + name.equals("AndroidManifest.xml") || + name.equals("resources.arsc") || + name.startsWith("META-INF/") || + name.equals("stamp-cert-sha256"))) { + skipped++; + continue; + } + if (seen.contains(name)) { skipped++; continue; } + seen.add(name); + + ZipEntry ne = new ZipEntry(name); + if (e.getMethod() == ZipEntry.STORED) { + ne.setMethod(ZipEntry.STORED); + ne.setSize(e.getSize()); + ne.setCompressedSize(e.getSize()); + ne.setCrc(e.getCrc()); + } else { + ne.setMethod(ZipEntry.DEFLATED); + } + zos.putNextEntry(ne); + try (InputStream is = zip.getInputStream(e)) { + byte[] buf = new byte[16384]; + int n; + while ((n = is.read(buf)) > 0) zos.write(buf, 0, n); + } + zos.closeEntry(); + added++; + } + } + System.out.printf("%s: +%d added, %d skipped%n", src, added, skipped); + } + } + System.out.printf("Wrote %d entries to %s%n", seen.size(), out); + } +}