From 355dae94b0772b29b94b3e9c5559cb76fb86a66a Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 11 Aug 2023 00:49:37 +0200 Subject: [PATCH 01/24] chore: change publication group id --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 19784431ba..52d85e2f8e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -123,7 +123,7 @@ subprojects { publications { register("mavenJava", MavenPublication::class) { from(components["java"]) - groupId = "org.apktool" + groupId = "app.revanced" artifactId = project.name version = apktoolVersion From ad7520357fe1d64a004cf6bc3c0e2e224e08ea8d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 11 Aug 2023 00:50:32 +0200 Subject: [PATCH 02/24] fix: do not try to get existing AAPT binary if a path to it is provided --- .../src/main/java/brut/androlib/AaptInvoker.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java index 4bd891d987..ea962da454 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java @@ -365,7 +365,14 @@ public void invokeAapt(File apkFile, File manifest, File resDir, File rawDir, Fi List cmd = new ArrayList<>(); try { - String aaptCommand = AaptManager.getAaptExecutionCommand(aaptPath, getAaptBinaryFile()); + // Instead of AaptManager.getAaptExecutionCommand(aaptPath, getAaptBinaryFile()); + // it is needed to use the following command, because getAaptBinaryFile() + // may throw BrutException even when not used by AaptManager.getAaptExecutionCommand + File aaptFile; + if (aaptPath.isEmpty() || !(aaptFile = new File(aaptPath)).exists()) + aaptFile = getAaptBinaryFile(); + String aaptCommand = aaptFile.getPath(); + cmd.add(aaptCommand); } catch (BrutException ex) { LOGGER.warning("aapt: " + ex.getMessage() + " (defaulting to $PATH binary)"); From 019b2e648d9698e5b97bf5286ce6f561aabd2bce Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 11 Aug 2023 00:51:05 +0200 Subject: [PATCH 03/24] feat: allow recording uncompressed files manually --- .../apktool-lib/src/main/java/brut/androlib/ApkDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java index 859f589a17..6986aa27e5 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java @@ -315,7 +315,7 @@ private void copyOriginalFiles(File outDir) throws AndrolibException { } } - private void recordUncompressedFiles(Map resFileMapping) throws AndrolibException { + public void recordUncompressedFiles(Map resFileMapping) throws AndrolibException { try { List uncompressedFilesOrExts = new ArrayList<>(); Directory unk = mApkInfo.getApkFile().getDirectory(); From c0900e50c5b3372b6716250bea8e4f90294466e0 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 11 Aug 2023 00:51:30 +0200 Subject: [PATCH 04/24] feat: allow instantiating with existing `ApkInfo` --- .../apktool-lib/src/main/java/brut/androlib/ApkDecoder.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java index 6986aa27e5..31ad6b1b77 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java @@ -68,8 +68,12 @@ public ApkDecoder(Config config, File apkFile) { } public ApkDecoder(Config config, ExtFile apkFile) { + this(config, new ApkInfo(apkFile)); + } + + public ApkDecoder(Config config, ApkInfo apkInfo) { mConfig = config; - mApkInfo = new ApkInfo(apkFile); + mApkInfo = apkInfo; } public ApkInfo decode(File outDir) throws AndrolibException, IOException, DirectoryException { From 4608df636ef9fce62703bb9b61294fa19bcd395a Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 11 Aug 2023 00:53:43 +0200 Subject: [PATCH 05/24] feat: allow using the default `XML` serializer by `ResourcesDecoder` manually --- .../src/main/java/brut/androlib/res/ResourcesDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java index a9507b6b00..436f65cce8 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java @@ -185,7 +185,7 @@ public void decodeResources(File outDir) throws AndrolibException { } } - private ExtMXSerializer getResXmlSerializer() { + public ExtMXSerializer getResXmlSerializer() { ExtMXSerializer serial = new ExtMXSerializer(); serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION, " "); serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR, System.getProperty("line.separator")); From 72ffcbbc44e2931890a69c070220792d991319f7 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 11 Aug 2023 00:54:18 +0200 Subject: [PATCH 06/24] feat: decode `9patch` files on Android --- brut.apktool/apktool-lib/build.gradle.kts | 16 +++ .../brut/androlib/res/ResourcesDecoder.java | 7 +- .../Res9patchAndroidStreamDecoder.java | 135 ++++++++++++++++++ .../androlib/decode/MissingDiv9PatchTest.java | 5 +- .../src/main/java/brut/util/OSDetection.java | 9 ++ 5 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchAndroidStreamDecoder.java diff --git a/brut.apktool/apktool-lib/build.gradle.kts b/brut.apktool/apktool-lib/build.gradle.kts index 9e7295b400..349c57050c 100644 --- a/brut.apktool/apktool-lib/build.gradle.kts +++ b/brut.apktool/apktool-lib/build.gradle.kts @@ -11,6 +11,20 @@ val xmlunitVersion: String by rootProject.extra val gitRevision: String by rootProject.extra val apktoolVersion: String by rootProject.extra +// region Determine Android SDK location + +val sdkRoot: String? = System.getenv("ANDROID_SDK_ROOT") +val androidJarPath: String = if (sdkRoot == null) { + GradleException("Missing ANDROID_SDK_ROOT").printStackTrace() + + "com.google.android:android:4.1.1.4" +} else { + val androidVersion = 33 + File("$sdkRoot/platforms/android-$androidVersion/android.jar").path +} + +// endregion + tasks { processResources { from("src/main/resources/properties") { @@ -49,4 +63,6 @@ dependencies { testImplementation("junit:junit:$junitVersion") testImplementation("org.xmlunit:xmlunit-legacy:$xmlunitVersion") + + compileOnly(files(androidJarPath)) } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java index 436f65cce8..4cfbd08592 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java @@ -28,6 +28,7 @@ import brut.directory.Directory; import brut.directory.DirectoryException; import brut.directory.FileDirectory; +import brut.util.OSDetection; import org.xmlpull.v1.XmlSerializer; import java.io.*; @@ -148,7 +149,11 @@ public void decodeResources(File outDir) throws AndrolibException { ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); decoders.setDecoder("raw", new ResRawStreamDecoder()); - decoders.setDecoder("9patch", new Res9patchStreamDecoder()); + + decoders.setDecoder( + "9patch", + OSDetection.isAndroid() ? new Res9patchAndroidStreamDecoder() : new Res9patchStreamDecoder() + ); AXmlResourceParser axmlParser = new AXmlResourceParser(mResTable); decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchAndroidStreamDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchAndroidStreamDecoder.java new file mode 100644 index 0000000000..801b05e71d --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchAndroidStreamDecoder.java @@ -0,0 +1,135 @@ +package brut.androlib.res.decoder; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import brut.androlib.exceptions.AndrolibException; +import brut.androlib.exceptions.CantFind9PatchChunkException; +import brut.androlib.res.data.ninepatch.NinePatchData; +import brut.androlib.res.data.ninepatch.OpticalInset; +import brut.util.ExtDataInput; +import org.apache.commons.io.IOUtils; + +import java.io.*; + +public class Res9patchAndroidStreamDecoder implements ResStreamDecoder { + public void decode(InputStream in, OutputStream out) throws AndrolibException { + try { + byte[] data = IOUtils.toByteArray(in); + + if (data.length == 0) { + return; + } + Bitmap bm = BitmapFactory.decodeByteArray(data, 0, data.length); + int width = bm.getWidth(), height = bm.getHeight(); + + Bitmap outImg = Bitmap.createBitmap(width + 2, height + 2, bm.getConfig()); + + for (int w = 0; w < width; w++) + for (int h = 0; h < height; h++) outImg.setPixel(w + 1, h + 1, bm.getPixel(w, h)); + + NinePatchData np = getNinePatch(data); + drawHLineA(outImg, height + 1, np.padLeft + 1, width - np.padRight); + drawVLineA(outImg, width + 1, np.padTop + 1, height - np.padBottom); + + int[] xDivs = np.xDivs; + if (xDivs.length == 0) { + drawHLineA(outImg, 0, 1, width); + } else { + for (int i = 0; i < xDivs.length; i += 2) { + drawHLineA(outImg, 0, xDivs[i] + 1, xDivs[i + 1]); + } + } + + int[] yDivs = np.yDivs; + if (yDivs.length == 0) { + drawVLineA(outImg, 0, 1, height); + } else { + for (int i = 0; i < yDivs.length; i += 2) { + drawVLineA(outImg, 0, yDivs[i] + 1, yDivs[i + 1]); + } + } + + // Some images additionally use Optical Bounds + // https://developer.android.com/about/versions/android-4.3.html#OpticalBounds + try { + OpticalInset oi = getOpticalInset(data); + + for (int i = 0; i < oi.layoutBoundsLeft; i++) { + int x = 1 + i; + outImg.setPixel(x, height + 1, OI_COLOR); + } + + for (int i = 0; i < oi.layoutBoundsRight; i++) { + int x = width - i; + outImg.setPixel(x, height + 1, OI_COLOR); + } + + for (int i = 0; i < oi.layoutBoundsTop; i++) { + int y = 1 + i; + outImg.setPixel(width + 1, y, OI_COLOR); + } + + for (int i = 0; i < oi.layoutBoundsBottom; i++) { + int y = height - i; + outImg.setPixel(width + 1, y, OI_COLOR); + } + } catch (CantFind9PatchChunkException t) { + // This chunk might not exist + } + + outImg.compress(Bitmap.CompressFormat.PNG, 100, out); + bm.recycle(); + outImg.recycle(); + } catch (IOException ex) { + throw new AndrolibException(ex); + } + } + + private NinePatchData getNinePatch(byte[] data) throws AndrolibException, + IOException { + ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data)); + find9patchChunk(di, NP_CHUNK_TYPE); + return NinePatchData.decode(di); + } + + private OpticalInset getOpticalInset(byte[] data) throws AndrolibException, + IOException { + ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data)); + find9patchChunk(di, OI_CHUNK_TYPE); + return OpticalInset.decode(di); + } + + private void find9patchChunk(DataInput di, int magic) throws AndrolibException, + IOException { + di.skipBytes(8); + while (true) { + int size; + try { + size = di.readInt(); + } catch (IOException ex) { + throw new CantFind9PatchChunkException("Cant find nine patch chunk", ex); + } + if (di.readInt() == magic) { + return; + } + di.skipBytes(size + 4); + } + } + + private void drawHLineA(Bitmap bm, int y, int x1, int x2) { + for (int x = x1; x <= x2; x++) { + bm.setPixel(x, y, NP_COLOR); + } + } + + private void drawVLineA(Bitmap bm, int x, int y1, int y2) { + for (int y = y1; y <= y2; y++) { + bm.setPixel(x, y, NP_COLOR); + } + } + + private static final int NP_CHUNK_TYPE = 0x6e705463; // npTc + private static final int OI_CHUNK_TYPE = 0x6e704c62; // npLb + private static final int NP_COLOR = 0xff000000; + private static final int OI_COLOR = 0xffff0000; +} diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/MissingDiv9PatchTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/MissingDiv9PatchTest.java index 3f493c778f..67eeea31d2 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/MissingDiv9PatchTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/MissingDiv9PatchTest.java @@ -18,10 +18,13 @@ import brut.androlib.BaseTest; import brut.androlib.TestUtils; +import brut.androlib.res.decoder.Res9patchAndroidStreamDecoder; import brut.androlib.res.decoder.Res9patchStreamDecoder; +import brut.androlib.res.decoder.ResStreamDecoder; import brut.common.BrutException; import brut.directory.ExtFile; import brut.util.OS; +import brut.util.OSDetection; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -51,7 +54,7 @@ public void assertMissingDivAdded() throws Exception { InputStream inputStream = getFileInputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - Res9patchStreamDecoder decoder = new Res9patchStreamDecoder(); + ResStreamDecoder decoder = OSDetection.isAndroid() ? new Res9patchAndroidStreamDecoder() : new Res9patchStreamDecoder(); decoder.decode(inputStream, outputStream); BufferedImage image = ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray())); diff --git a/brut.j.util/src/main/java/brut/util/OSDetection.java b/brut.j.util/src/main/java/brut/util/OSDetection.java index 2f7eae5c5c..ec7921de87 100644 --- a/brut.j.util/src/main/java/brut/util/OSDetection.java +++ b/brut.j.util/src/main/java/brut/util/OSDetection.java @@ -32,6 +32,15 @@ public static boolean isUnix() { return (OS.contains("nix") || OS.contains("nux") || OS.contains("aix") || (OS.contains("sunos"))); } + public static boolean isAndroid() { + try { + Class.forName("android.app.Activity"); + return true; + } catch (ClassNotFoundException ignored) { + return false; + } + } + public static boolean is64Bit() { if (isWindows()) { String arch = System.getenv("PROCESSOR_ARCHITECTURE"); From b16e1763c07039e84fb20c21de6054ce2238ce68 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 11 Aug 2023 01:06:20 +0200 Subject: [PATCH 07/24] build: change publication repository --- build.gradle.kts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 52d85e2f8e..2d44de5591 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -109,14 +109,10 @@ subprojects { publishing { repositories { maven { - url = if (suffix.contains("SNAPSHOT")) { - uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") - } else { - uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") - } + url = uri("https://maven.pkg.github.com/revanced/Apktool") credentials { - username = (project.properties["ossrhUsername"] ?: "").toString() - password = (project.properties["ossrhPassword"] ?: "").toString() + username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user").toString() + password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key").toString() } } } @@ -151,9 +147,9 @@ subprojects { } } scm { - connection = "scm:git:git://github.com/iBotPeaches/Apktool.git" - developerConnection = "scm:git:git@github.com:iBotPeaches/Apktool.git" - url = "https://github.com/iBotPeaches/Apktool" + connection = "scm:git:git://github.com/revanced/Apktool.git" + developerConnection = "scm:git:git@github.com:revanced/Apktool.git" + url = "https://github.com/revanced/Apktool" } } } From 648cb8f546d72e3f58c0f98180643ba929c0e131 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 11 Aug 2023 01:08:49 +0200 Subject: [PATCH 08/24] build: start new dev cycle (2.8.2-2) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2d44de5591..c7aafe73a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ val xmlpullVersion by extra("1.1.4c") val xmlunitVersion by extra("2.9.1") val version = "2.8.2" -val suffix = "SNAPSHOT" +val suffix = "2" // Strings embedded into the build. var gitRevision by extra("") From 9c6dffab5134f0c9c549c5d987d866446841b720 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 11 Aug 2023 01:14:08 +0200 Subject: [PATCH 09/24] build: allow signing with using gpg-agent --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index c7aafe73a0..b5e941ceab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -161,6 +161,7 @@ subprojects { } signing { + useGpgCmd() sign(publishing.publications["mavenJava"]) } } From adc9452f6636562b53cad9c5a61b212a976f280d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 12 Aug 2023 01:59:12 +0200 Subject: [PATCH 10/24] feat: create missing directories --- .../apktool-lib/src/main/java/brut/androlib/AaptInvoker.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java index ea962da454..47acb6c517 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java @@ -83,6 +83,10 @@ private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir, if (resDir != null) { File buildDir = new File(resDir.getParent(), "build"); + + //noinspection ResultOfMethodCallIgnored + buildDir.mkdir(); + resourcesZip = new File(buildDir, "resources.zip"); } From e60804134acd3933961002e67005406bdf015d2b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 12 Aug 2023 01:59:33 +0200 Subject: [PATCH 11/24] build: start new dev cycle (2.8.2-3) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index b5e941ceab..864b2d4052 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ val xmlpullVersion by extra("1.1.4c") val xmlunitVersion by extra("2.9.1") val version = "2.8.2" -val suffix = "2" +val suffix = "3" // Strings embedded into the build. var gitRevision by extra("") From 2c9e14c94b38f497196ed1e170406108afc55ecd Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 19 Aug 2023 17:08:51 +0200 Subject: [PATCH 12/24] fix: Make sure the property is not null --- brut.j.util/src/main/java/brut/util/OSDetection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brut.j.util/src/main/java/brut/util/OSDetection.java b/brut.j.util/src/main/java/brut/util/OSDetection.java index ec7921de87..fcb31ef1f7 100644 --- a/brut.j.util/src/main/java/brut/util/OSDetection.java +++ b/brut.j.util/src/main/java/brut/util/OSDetection.java @@ -18,7 +18,7 @@ public class OSDetection { private static final String OS = System.getProperty("os.name").toLowerCase(); - private static final String BIT = System.getProperty("sun.arch.data.model").toLowerCase(); + private static final String BIT = System.getProperty("sun.arch.data.model"); public static boolean isWindows() { return (OS.contains("win")); @@ -48,7 +48,7 @@ public static boolean is64Bit() { return arch != null && arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64"); } - return BIT.equalsIgnoreCase("64"); + return BIT != null && BIT.equalsIgnoreCase("64"); } public static String returnOS() { From 9ae802052f9a7dbee7fd6a27e30a6e756097dcc5 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 19 Aug 2023 17:09:43 +0200 Subject: [PATCH 13/24] build: start new dev cycle (2.8.2-4) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 864b2d4052..64f825b52c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ val xmlpullVersion by extra("1.1.4c") val xmlunitVersion by extra("2.9.1") val version = "2.8.2" -val suffix = "3" +val suffix = "4" // Strings embedded into the build. var gitRevision by extra("") From 452a620775fcd30505c658f9ff4d6cee2a9ae84d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 21 Aug 2023 02:25:35 +0200 Subject: [PATCH 14/24] feat: disable `customAapt` logic --- .../apktool-lib/src/main/java/brut/androlib/AaptInvoker.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java index 47acb6c517..f340a13ab6 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java @@ -365,7 +365,10 @@ public void invokeAapt(File apkFile, File manifest, File resDir, File rawDir, Fi throws AndrolibException { String aaptPath = mConfig.aaptPath; - boolean customAapt = !aaptPath.isEmpty(); + // Mock using the included AAPT binary instead of a custom one. + // This is necessary, otherwise extension of every file from doNotCompress will be specified in the AAPT command + // which causes builds to fail. + boolean customAapt = false; // !aaptPath.isEmpty(); List cmd = new ArrayList<>(); try { From 24c0bd96ee857fb4e1e96f2235dc860c2bbdf3bf Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 22 Aug 2023 18:49:52 +0200 Subject: [PATCH 15/24] feat: remove AAPT2 argument unavailable in stable version of platform-tools --- .../apktool-lib/src/main/java/brut/androlib/AaptInvoker.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java index f340a13ab6..56d4809839 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java @@ -180,7 +180,8 @@ private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir, cmd.add("--allow-reserved-package-id"); - cmd.add("--no-compile-sdk-metadata"); + // TODO: Add this back, once AAPT2 from platform-tools 34.0.4 is stable + // cmd.add("--no-compile-sdk-metadata"); if (mApkInfo.sparseResources) { cmd.add("--enable-sparse-encoding"); From a377fde62d47aca1f48dba2ce76989746b020109 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 22 Aug 2023 18:52:30 +0200 Subject: [PATCH 16/24] build: start new dev cycle (2.8.2-5) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 64f825b52c..089c228ef6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ val xmlpullVersion by extra("1.1.4c") val xmlunitVersion by extra("2.9.1") val version = "2.8.2" -val suffix = "4" +val suffix = "5" // Strings embedded into the build. var gitRevision by extra("") From 538b8a8e4fb8fbd26ff8241bbac5e6cf0c3157b4 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 27 Aug 2023 21:30:51 +0200 Subject: [PATCH 17/24] build: start new dev cycle (2.8.2-6) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 089c228ef6..ae8ad636e8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ val xmlpullVersion by extra("1.1.4c") val xmlunitVersion by extra("2.9.1") val version = "2.8.2" -val suffix = "5" +val suffix = "6" // Strings embedded into the build. var gitRevision by extra("") From c68c74f7d46372e5592c3d6cfc2fa04f69e85f1a Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 9 Oct 2023 05:10:01 +0200 Subject: [PATCH 18/24] build: Publish on Jitpack --- .jitpack.yml | 5 ----- brut.apktool/apktool-lib/build.gradle.kts | 16 +--------------- build.gradle.kts | 20 +++----------------- gradle/libs.versions.toml | 2 ++ 4 files changed, 6 insertions(+), 37 deletions(-) delete mode 100644 .jitpack.yml diff --git a/.jitpack.yml b/.jitpack.yml deleted file mode 100644 index d8aea5abdb..0000000000 --- a/.jitpack.yml +++ /dev/null @@ -1,5 +0,0 @@ -jdk: - - openjdk9 -install: - - echo "This is not supported. See iBotPeaches/Apktool#2102" - - ./gradlew invalid-command-to-crash-out diff --git a/brut.apktool/apktool-lib/build.gradle.kts b/brut.apktool/apktool-lib/build.gradle.kts index ab393934c9..b2b4907e60 100644 --- a/brut.apktool/apktool-lib/build.gradle.kts +++ b/brut.apktool/apktool-lib/build.gradle.kts @@ -1,20 +1,6 @@ val gitRevision: String by rootProject.extra val apktoolVersion: String by rootProject.extra -// region Determine Android SDK location - -val sdkRoot: String? = System.getenv("ANDROID_SDK_ROOT") -val androidJarPath: String = if (sdkRoot == null) { - GradleException("Missing ANDROID_SDK_ROOT").printStackTrace() - - "com.google.android:android:4.1.1.4" -} else { - val androidVersion = 33 - File("$sdkRoot/platforms/android-$androidVersion/android.jar").path -} - -// endregion - tasks { processResources { from("src/main/resources/properties") { @@ -54,5 +40,5 @@ dependencies { testImplementation(libs.junit) testImplementation(libs.xmlunit) - compileOnly(files(androidJarPath)) + compileOnly(libs.android) } diff --git a/build.gradle.kts b/build.gradle.kts index 268ff87c76..3e3e296f92 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ import java.io.ByteArrayOutputStream val version = "2.9.1" -val suffix = "SNAPSHOT" +val suffix = "" // Strings embedded into the build. var gitRevision by extra("") @@ -36,7 +36,7 @@ val gitBranch: String? by lazy { } } -if ("release" !in gradle.startParameter.taskNames) { +if ("publishToMavenLocal" !in gradle.startParameter.taskNames) { val hash = this.gitDescribe if (hash == null) { @@ -84,7 +84,7 @@ subprojects { targetCompatibility = JavaVersion.VERSION_1_8 } - val mavenProjects = arrayOf("apktool-lib", "apktool-cli", "brut.j.common", "brut.j.util", "brut.j.dir") + val mavenProjects = arrayOf("apktool-lib", "brut.j.common", "brut.j.util", "brut.j.dir") if (project.name in mavenProjects) { apply(plugin = "maven-publish") @@ -96,15 +96,6 @@ subprojects { } publishing { - repositories { - maven { - url = uri("https://maven.pkg.github.com/revanced/Apktool") - credentials { - username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user").toString() - password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key").toString() - } - } - } publications { register("mavenJava", MavenPublication::class) { from(components["java"]) @@ -148,11 +139,6 @@ subprojects { tasks.withType() { (options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet") } - - signing { - useGpgCmd() - sign(publishing.publications["mavenJava"]) - } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 611f22ae9f..6b2cdcddb2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,7 @@ shadow = "8.1.1" smali = "3.0.3" xmlpull = "1.1.4c" xmlunit = "2.9.1" +android = "4.1.1.4" [libraries] baksmali = { module = "com.android.tools.smali:smali-baksmali", version.ref = "baksmali" } @@ -24,6 +25,7 @@ proguard = { module = "com.guardsquare:proguard-gradle", version.ref = "proguard smali = { module = "com.android.tools.smali:smali", version.ref = "smali" } xmlpull = { module = "xpp3:xpp3", version.ref = "xmlpull" } xmlunit = { module = "org.xmlunit:xmlunit-legacy", version.ref = "xmlunit" } +android = { module = "com.google.android:android", version.ref = "android" } [plugins] shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } From 1b1c7f8f504a6faa3f0fd869f434d7df139cbdf5 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 17 Jan 2024 06:11:22 -0500 Subject: [PATCH 19/24] fix: tighten up detectPossibleDirectoryTraversal for Windows --- brut.j.util/src/main/java/brut/util/BrutIO.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/brut.j.util/src/main/java/brut/util/BrutIO.java b/brut.j.util/src/main/java/brut/util/BrutIO.java index feecc129a9..e56867dc3d 100644 --- a/brut.j.util/src/main/java/brut/util/BrutIO.java +++ b/brut.j.util/src/main/java/brut/util/BrutIO.java @@ -95,10 +95,7 @@ public static String sanitizeFilepath(final File directory, final String entry) } public static boolean detectPossibleDirectoryTraversal(String entry) { - if (OSDetection.isWindows()) { - return entry.contains("..\\") || entry.contains("\\.."); - } - return entry.contains("../") || entry.contains("/.."); + return entry.contains("../") || entry.contains("/..") || entry.contains("..\\") || entry.contains("\\.."); } public static String adaptSeparatorToUnix(String path) { From bd82a53663e3df8a5b75ab10e3287019bada1da8 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 17 Jan 2024 06:11:34 -0500 Subject: [PATCH 20/24] test: run path traversal test on Windows --- .../brut/androlib/decode/ResourceDirectoryTraversalTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/ResourceDirectoryTraversalTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/ResourceDirectoryTraversalTest.java index 7d308db5d9..44ec664cb9 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/ResourceDirectoryTraversalTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/ResourceDirectoryTraversalTest.java @@ -23,9 +23,7 @@ import brut.common.BrutException; import brut.directory.ExtFile; import brut.util.OS; -import brut.util.OSDetection; import org.junit.AfterClass; -import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; @@ -41,7 +39,6 @@ public static void beforeClass() throws Exception { TestUtils.cleanFrameworkFile(); sTmpDir = new ExtFile(OS.createTempDirectory()); TestUtils.copyResourceDir(ResourceDirectoryTraversalTest.class, "decode/arbitrary-write/", sTmpDir); - Assume.assumeFalse(OSDetection.isWindows()); } @AfterClass From 3bb7888eb47103a1ba13c86b8a90a4d9614c9e7c Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Sat, 20 Jan 2024 06:41:07 -0500 Subject: [PATCH 21/24] build: version bump (2.9.3) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 297463b236..a3220cbdac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ import java.io.ByteArrayOutputStream -val version = "2.9.2" +val version = "2.9.3" val suffix = "" // Strings embedded into the build. From 0fd4443db71a7815cec02ef091ad47e8f11af9b0 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 14 Feb 2024 00:08:20 +0100 Subject: [PATCH 22/24] build: Revert publishing on Jitpack --- .jitpack.yml | 5 +++++ brut.apktool/apktool-lib/build.gradle.kts | 16 +++++++++++++++- build.gradle.kts | 20 +++++++++++++++++--- gradle/libs.versions.toml | 2 -- 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 .jitpack.yml diff --git a/.jitpack.yml b/.jitpack.yml new file mode 100644 index 0000000000..d8aea5abdb --- /dev/null +++ b/.jitpack.yml @@ -0,0 +1,5 @@ +jdk: + - openjdk9 +install: + - echo "This is not supported. See iBotPeaches/Apktool#2102" + - ./gradlew invalid-command-to-crash-out diff --git a/brut.apktool/apktool-lib/build.gradle.kts b/brut.apktool/apktool-lib/build.gradle.kts index b2b4907e60..ab393934c9 100644 --- a/brut.apktool/apktool-lib/build.gradle.kts +++ b/brut.apktool/apktool-lib/build.gradle.kts @@ -1,6 +1,20 @@ val gitRevision: String by rootProject.extra val apktoolVersion: String by rootProject.extra +// region Determine Android SDK location + +val sdkRoot: String? = System.getenv("ANDROID_SDK_ROOT") +val androidJarPath: String = if (sdkRoot == null) { + GradleException("Missing ANDROID_SDK_ROOT").printStackTrace() + + "com.google.android:android:4.1.1.4" +} else { + val androidVersion = 33 + File("$sdkRoot/platforms/android-$androidVersion/android.jar").path +} + +// endregion + tasks { processResources { from("src/main/resources/properties") { @@ -40,5 +54,5 @@ dependencies { testImplementation(libs.junit) testImplementation(libs.xmlunit) - compileOnly(libs.android) + compileOnly(files(androidJarPath)) } diff --git a/build.gradle.kts b/build.gradle.kts index 3e3e296f92..268ff87c76 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ import java.io.ByteArrayOutputStream val version = "2.9.1" -val suffix = "" +val suffix = "SNAPSHOT" // Strings embedded into the build. var gitRevision by extra("") @@ -36,7 +36,7 @@ val gitBranch: String? by lazy { } } -if ("publishToMavenLocal" !in gradle.startParameter.taskNames) { +if ("release" !in gradle.startParameter.taskNames) { val hash = this.gitDescribe if (hash == null) { @@ -84,7 +84,7 @@ subprojects { targetCompatibility = JavaVersion.VERSION_1_8 } - val mavenProjects = arrayOf("apktool-lib", "brut.j.common", "brut.j.util", "brut.j.dir") + val mavenProjects = arrayOf("apktool-lib", "apktool-cli", "brut.j.common", "brut.j.util", "brut.j.dir") if (project.name in mavenProjects) { apply(plugin = "maven-publish") @@ -96,6 +96,15 @@ subprojects { } publishing { + repositories { + maven { + url = uri("https://maven.pkg.github.com/revanced/Apktool") + credentials { + username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user").toString() + password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key").toString() + } + } + } publications { register("mavenJava", MavenPublication::class) { from(components["java"]) @@ -139,6 +148,11 @@ subprojects { tasks.withType() { (options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet") } + + signing { + useGpgCmd() + sign(publishing.publications["mavenJava"]) + } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 43cfa0d9c7..e50d79d57c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,6 @@ shadow = "8.1.1" smali = "3.0.3" xmlpull = "1.1.4c" xmlunit = "2.9.1" -android = "4.1.1.4" [libraries] baksmali = { module = "com.android.tools.smali:smali-baksmali", version.ref = "baksmali" } @@ -25,7 +24,6 @@ proguard = { module = "com.guardsquare:proguard-gradle", version.ref = "proguard smali = { module = "com.android.tools.smali:smali", version.ref = "smali" } xmlpull = { module = "xpp3:xpp3", version.ref = "xmlpull" } xmlunit = { module = "org.xmlunit:xmlunit-legacy", version.ref = "xmlunit" } -android = { module = "com.google.android:android", version.ref = "android" } [plugins] shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } From 49eeed6d0903a15f45600261dc528ad3414043a8 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 17 Dec 2024 04:18:43 +0100 Subject: [PATCH 23/24] build: start new dev cycle (2.10.1.1) --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d34eee1f26..60b3f21e08 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ import java.io.ByteArrayOutputStream -val version = "2.10.1" -val suffix = "SNAPSHOT" +val version = "2.10.1.1" +val suffix = "" // Strings embedded into the build. var gitRevision by extra("") From e47c8899aec57c22082ff74d7e436b0518998cae Mon Sep 17 00:00:00 2001 From: Aunali321 Date: Thu, 12 Mar 2026 10:43:20 +0530 Subject: [PATCH 24/24] feat: Add auxiliary resource table loading for split APK support --- .../brut/androlib/res/ResourcesDecoder.java | 21 ++- .../java/brut/androlib/res/data/ResTable.java | 121 +++++++++++++++--- 2 files changed, 119 insertions(+), 23 deletions(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java index 98b75733a0..0a837e679d 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java @@ -47,6 +47,7 @@ public class ResourcesDecoder { private final ApkInfo mApkInfo; private final ResTable mResTable; private final Map mResFileMapping; + private boolean mIncludeAuxiliaryPublicXml; public ResourcesDecoder(Config config, ApkInfo apkInfo) { mConfig = config; @@ -71,6 +72,18 @@ public void loadMainPkg() throws AndrolibException { mResTable.loadMainPkg(mApkInfo.getApkFile()); } + public void loadAuxiliaryPkg(ExtFile apkFile) throws AndrolibException { + mResTable.loadAuxiliaryPkg(apkFile); + } + + public void loadAuxiliaryPkgs(Collection apkFiles) throws AndrolibException { + mResTable.loadAuxiliaryPkgs(apkFiles); + } + + public void setIncludeAuxiliaryPublicXml(boolean includeAuxiliaryPublicXml) { + mIncludeAuxiliaryPublicXml = includeAuxiliaryPublicXml; + } + public void decodeManifest(File apkDir) throws AndrolibException { if (!mApkInfo.hasManifest()) { return; @@ -158,7 +171,7 @@ public void decodeResources(File apkDir) throws AndrolibException { return; } - mResTable.loadMainPkg(mApkInfo.getApkFile()); + loadMainPkg(); ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); decoders.setDecoder("raw", new ResRawStreamDecoder()); @@ -193,7 +206,7 @@ public void decodeResources(File apkDir) throws AndrolibException { generateValuesFile(valuesFile, outDir, xmlSerializer); } - generatePublicXml(pkg, outDir, xmlSerializer); + generatePublicXml(pkg, outDir, xmlSerializer, mIncludeAuxiliaryPublicXml); } AndrolibException decodeError = axmlParser.getFirstError(); @@ -237,14 +250,14 @@ private void generateValuesFile(ResValuesFile valuesFile, Directory resDir, XmlS } } - private void generatePublicXml(ResPackage pkg, Directory resDir, XmlSerializer serial) + private void generatePublicXml(ResPackage pkg, Directory resDir, XmlSerializer serial, boolean includeAuxiliary) throws AndrolibException { try (OutputStream out = resDir.getFileOutput("values/public.xml")) { serial.setOutput(out, null); serial.startDocument(null, null); serial.startTag(null, "resources"); - for (ResResSpec spec : pkg.listResSpecs()) { + for (ResResSpec spec : mResTable.listResSpecs(pkg, includeAuxiliary)) { serial.startTag(null, "public"); serial.attribute(null, "type", spec.getType().getName()); serial.attribute(null, "name", spec.getName()); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java index c2c51dda8c..60cba27258 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java @@ -42,6 +42,8 @@ public class ResTable { private final ApkInfo mApkInfo; private final Map mPackagesById; private final Map mPackagesByName; + private final Map mAuxiliaryResSpecs; + private final Map mAuxiliaryResSpecsByName; private final Set mMainPackages; private final Set mFramePackages; @@ -63,6 +65,8 @@ public ResTable(Config config, ApkInfo apkInfo) { mApkInfo = apkInfo; mPackagesById = new HashMap<>(); mPackagesByName = new HashMap<>(); + mAuxiliaryResSpecs = new LinkedHashMap<>(); + mAuxiliaryResSpecsByName = new LinkedHashMap<>(); mMainPackages = new LinkedHashSet<>(); mFramePackages = new LinkedHashSet<>(); } @@ -91,6 +95,16 @@ public ResResSpec getResSpec(int resID) throws AndrolibException { } public ResResSpec getResSpec(ResID resID) throws AndrolibException { + ResPackage pkg = mPackagesById.get(resID.pkgId); + if (pkg != null && pkg.hasResSpec(resID)) { + return pkg.getResSpec(resID); + } + + ResResSpec auxiliarySpec = mAuxiliaryResSpecs.get(resID); + if (auxiliarySpec != null) { + return auxiliarySpec; + } + return getPackage(resID.pkgId).getResSpec(resID); } @@ -131,29 +145,27 @@ private ResPackage selectPkgWithMostResSpecs(ResPackage[] pkgs) { } public void loadMainPkg(ExtFile apkFile) throws AndrolibException { - LOGGER.info("Loading resource table..."); - ResPackage[] pkgs = loadResPackagesFromApk(apkFile, mConfig.keepBrokenResources); - ResPackage pkg; - - switch (pkgs.length) { - case 0: - pkg = new ResPackage(this, 0, null); - break; - case 1: - pkg = pkgs[0]; - break; - case 2: - LOGGER.warning("Skipping package group: " + pkgs[0].getName()); - pkg = pkgs[1]; - break; - default: - pkg = selectPkgWithMostResSpecs(pkgs); - break; + if (mMainPkgLoaded) { + return; } + + LOGGER.info("Loading resource table..."); + ResPackage pkg = selectPkg(loadResPackagesFromApk(apkFile, mConfig.keepBrokenResources)); addPackage(pkg, true); mMainPkgLoaded = true; } + public void loadAuxiliaryPkg(ExtFile apkFile) throws AndrolibException { + LOGGER.info("Loading auxiliary resource table from file: " + apkFile); + addAuxiliaryPackage(selectPkg(loadResPackagesFromApk(apkFile, mConfig.keepBrokenResources))); + } + + public void loadAuxiliaryPkgs(Collection apkFiles) throws AndrolibException { + for (ExtFile apkFile : apkFiles) { + loadAuxiliaryPkg(apkFile); + } + } + private ResPackage loadFrameworkPkg(int id) throws AndrolibException { Framework framework = new Framework(mConfig); File frameworkApk = framework.getFrameworkApk(id, mConfig.frameworkTag); @@ -224,7 +236,32 @@ public ResPackage getPackage(String name) throws AndrolibException { } public ResValue getValue(String pkg, String type, String name) throws AndrolibException { - return getPackage(pkg).getType(type).getResSpec(name).getDefaultResource().getValue(); + try { + return getPackage(pkg).getType(type).getResSpec(name).getDefaultResource().getValue(); + } catch (UndefinedResObjectException ex) { + ResResSpec auxiliarySpec = mAuxiliaryResSpecsByName.get(getSpecNameKey(pkg, type, name)); + if (auxiliarySpec == null) { + throw ex; + } + return auxiliarySpec.getDefaultResource().getValue(); + } + } + + public Collection listResSpecs(ResPackage pkg, boolean includeAuxiliary) { + if (!includeAuxiliary) { + return pkg.listResSpecs(); + } + + Map specs = new LinkedHashMap<>(); + for (ResResSpec spec : pkg.listResSpecs()) { + specs.put(spec.getId(), spec); + } + for (ResResSpec spec : mAuxiliaryResSpecs.values()) { + if (spec.getId().pkgId == pkg.getId()) { + specs.putIfAbsent(spec.getId(), spec); + } + } + return specs.values(); } public void addPackage(ResPackage pkg, boolean main) throws AndrolibException { @@ -246,6 +283,12 @@ public void addPackage(ResPackage pkg, boolean main) throws AndrolibException { } } + void addAuxiliaryPackage(ResPackage pkg) { + for (ResResSpec spec : pkg.listResSpecs()) { + addAuxiliaryResSpec(spec); + } + } + public void setPackageRenamed(String pkg) { mPackageRenamed = pkg; } @@ -381,4 +424,44 @@ private void loadVersionName(File apkDir) { mApkInfo.versionInfo.versionName = refValue; } } + + private ResPackage selectPkg(ResPackage[] pkgs) { + switch (pkgs.length) { + case 0: + return new ResPackage(this, 0, null); + case 1: + return pkgs[0]; + case 2: + LOGGER.warning("Skipping package group: " + pkgs[0].getName()); + return pkgs[1]; + default: + return selectPkgWithMostResSpecs(pkgs); + } + } + + private void addAuxiliaryResSpec(ResResSpec spec) { + ResResSpec existingSpec = mAuxiliaryResSpecs.get(spec.getId()); + if (existingSpec != null) { + if (!existingSpec.getName().equals(spec.getName()) + || !existingSpec.getType().getName().equals(spec.getType().getName())) { + LOGGER.warning("Ignoring conflicting auxiliary resource spec: " + spec); + } + return; + } + + mAuxiliaryResSpecs.put(spec.getId(), spec); + + String key = getSpecNameKey(spec.getPackage().getName(), spec.getType().getName(), spec.getName()); + ResResSpec existingNamedSpec = mAuxiliaryResSpecsByName.get(key); + if (existingNamedSpec != null && !existingNamedSpec.getId().equals(spec.getId())) { + LOGGER.warning("Ignoring conflicting auxiliary resource name: " + key); + return; + } + + mAuxiliaryResSpecsByName.putIfAbsent(key, spec); + } + + private String getSpecNameKey(String pkg, String type, String name) { + return pkg + ":" + type + "/" + name; + } }