From e47c8899aec57c22082ff74d7e436b0518998cae Mon Sep 17 00:00:00 2001 From: Aunali321 Date: Thu, 12 Mar 2026 10:43:20 +0530 Subject: [PATCH] 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; + } }