Skip to content

Commit 9a66e5b

Browse files
authored
fix: support apk as library for split apks (#4128)
1 parent e9ed34f commit 9a66e5b

11 files changed

Lines changed: 119 additions & 68 deletions

File tree

brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.io.PrintWriter;
3636
import java.util.Arrays;
3737
import java.util.List;
38+
import java.util.Map;
3839
import java.util.Properties;
3940
import java.util.logging.*;
4041

@@ -423,7 +424,13 @@ private static void cmdDecode(String[] args) throws AndrolibException {
423424
config.setFrameworkTag(cli.getOptionValue(frameTagOption));
424425
}
425426
if (cli.hasOption(libOption)) {
426-
config.setLibraryFiles(cli.getOptionValues(libOption));
427+
Map<String, String[]> libraryFiles = config.getLibraryFiles();
428+
for (String entry : cli.getOptionValues(libOption)) {
429+
String[] parts = entry.split(":", 2);
430+
if (parts.length == 2) {
431+
libraryFiles.put(parts[0], parts[1].split(","));
432+
}
433+
}
427434
}
428435
if (cli.hasOption(decodeForceOption)) {
429436
config.setForced(true);
@@ -545,7 +552,13 @@ private static void cmdBuild(String[] args) throws AndrolibException {
545552
config.setFrameworkDirectory(cli.getOptionValue(frameDirOption));
546553
}
547554
if (cli.hasOption(libOption)) {
548-
config.setLibraryFiles(cli.getOptionValues(libOption));
555+
Map<String, String[]> libraryFiles = config.getLibraryFiles();
556+
for (String entry : cli.getOptionValues(libOption)) {
557+
String[] parts = entry.split(":", 2);
558+
if (parts.length == 2) {
559+
libraryFiles.put(parts[0], parts[1].split(","));
560+
}
561+
}
549562
}
550563
if (cli.hasOption(buildForceOption)) {
551564
config.setForced(true);

brut.apktool/apktool-lib/src/main/java/brut/androlib/Config.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
*/
1717
package brut.androlib;
1818

19+
import java.util.LinkedHashMap;
20+
import java.util.Map;
21+
1922
public class Config {
2023
public enum DecodeSources { FULL, ONLY_MAIN_CLASSES, NONE }
2124
public enum DecodeResources { FULL, ONLY_MANIFEST, NONE }
@@ -28,7 +31,7 @@ public enum DecodeAssets { FULL, NONE }
2831
private int mJobs;
2932
private String mFrameworkDirectory;
3033
private String mFrameworkTag;
31-
private String[] mLibraryFiles;
34+
private final Map<String, String[]> mLibraryFiles;
3235
private boolean mForced;
3336
private boolean mVerbose;
3437

@@ -57,7 +60,7 @@ public Config(String version) {
5760
mJobs = Math.min(Runtime.getRuntime().availableProcessors(), 8);
5861
mFrameworkDirectory = null;
5962
mFrameworkTag = null;
60-
mLibraryFiles = null;
63+
mLibraryFiles = new LinkedHashMap<>();
6164
mForced = false;
6265
mVerbose = false;
6366

@@ -110,14 +113,10 @@ public void setFrameworkTag(String frameworkTag) {
110113
mFrameworkTag = frameworkTag;
111114
}
112115

113-
public String[] getLibraryFiles() {
116+
public Map<String, String[]> getLibraryFiles() {
114117
return mLibraryFiles;
115118
}
116119

117-
public void setLibraryFiles(String[] libraryFiles) {
118-
mLibraryFiles = libraryFiles;
119-
}
120-
121120
public boolean isForced() {
122121
return mForced;
123122
}

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AaptInvoker.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import brut.androlib.exceptions.AndrolibException;
2121
import brut.androlib.meta.*;
2222
import brut.androlib.res.Framework;
23+
import brut.androlib.res.table.ResTable;
2324
import brut.common.BrutException;
2425
import brut.common.Log;
2526
import brut.util.OS;
@@ -127,10 +128,10 @@ public void invoke(File outApk, File manifest, File resDir) throws AndrolibExcep
127128
int pkgId = resourcesInfo.getPackageId();
128129
if (pkgId == 0) {
129130
cmd.add("--shared-lib");
130-
} else if (pkgId > 1) {
131+
} else if (pkgId > ResTable.SYS_PACKAGE_ID) {
131132
cmd.add("--package-id");
132133
cmd.add(Integer.toString(pkgId));
133-
if (pkgId < 0x7F) {
134+
if (pkgId < ResTable.APP_PACKAGE_ID) {
134135
cmd.add("--allow-reserved-package-id");
135136
}
136137
}
@@ -201,20 +202,13 @@ private List<File> getIncludeFiles() throws AndrolibException {
201202

202203
List<String> usesLibrary = mApkInfo.getUsesLibrary();
203204
if (!usesLibrary.isEmpty()) {
204-
String[] libFiles = mConfig.getLibraryFiles();
205+
Map<String, String[]> libraryFiles = mConfig.getLibraryFiles();
205206
for (String name : usesLibrary) {
206-
File libFile = null;
207-
if (libFiles != null) {
208-
for (String libEntry : libFiles) {
209-
String[] parts = libEntry.split(":", 2);
210-
if (parts.length == 2 && name.equals(parts[0])) {
211-
libFile = new File(parts[1]);
212-
break;
213-
}
207+
String[] fileNames = libraryFiles.get(name);
208+
if (fileNames != null) {
209+
for (String fileName : fileNames) {
210+
files.add(new File(fileName));
214211
}
215-
}
216-
if (libFile != null) {
217-
files.add(libFile);
218212
} else {
219213
Log.w(TAG, "Shared library was not provided: " + name);
220214
}

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResDecoder.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,14 @@ public void decodeResources(File apkDir) throws AndrolibException {
9292
ResPackage pkg = mTable.getMainPackage();
9393

9494
Log.i(TAG, "Decoding value resources...");
95-
for (ResEntry entry : Lists.newArrayList(pkg.getGroup().listEntries())) {
95+
for (ResEntry entry : Lists.newArrayList(listEntries(pkg))) {
9696
if (entry.getValue() instanceof ResBag) {
9797
((ResBag) entry.getValue()).resolveKeys();
9898
}
9999
}
100100

101101
Log.i(TAG, "Decoding file resources...");
102-
for (ResEntry entry : Lists.newArrayList(pkg.getGroup().listEntries())) {
102+
for (ResEntry entry : Lists.newArrayList(listEntries(pkg))) {
103103
if (entry.getValue() instanceof ResFileReference) {
104104
fileDecoder.decode(entry, inDir, outDir, mResFileMap);
105105
}
@@ -120,11 +120,15 @@ public void decodeResources(File apkDir) throws AndrolibException {
120120
}
121121
}
122122

123+
private static Iterable<ResEntry> listEntries(ResPackage pkg) {
124+
return pkg.getId() == ResTable.SYS_PACKAGE_ID ? pkg.getGroup().listEntries() : pkg.listEntries();
125+
}
126+
123127
private void generateValuesXmls(ResPackage pkg, Directory outDir, ResXmlSerializer serial)
124128
throws AndrolibException {
125129
// Group entries by type name + qualifiers, ignoring alias duplicates in sub-packages.
126130
Map<Pair<String, String>, List<ResEntry>> entriesMap = new HashMap<>();
127-
for (ResEntry entry : pkg.getGroup().listEntries()) {
131+
for (ResEntry entry : listEntries(pkg)) {
128132
if (entry.getValue() instanceof ValuesXmlSerializable && !pkg.isAlias(entry.getResId())) {
129133
ResType type = entry.getType();
130134
Pair<String, String> key = Pair.of(type.getName(), type.getConfig().toQualifiers());
@@ -189,7 +193,7 @@ private void generatePublicXml(ResPackage pkg, Directory outDir, ResXmlSerialize
189193

190194
private void generateStagingXmls(ResPackage pkg, Directory outDir, ResXmlSerializer serial)
191195
throws AndrolibException {
192-
if (pkg.getGroup().getPackageCount() <= 1) {
196+
if (pkg.getId() != ResTable.SYS_PACKAGE_ID) {
193197
return;
194198
}
195199

@@ -198,6 +202,9 @@ private void generateStagingXmls(ResPackage pkg, Directory outDir, ResXmlSeriali
198202
for (ResPackage subPkg : pkg.getGroup().listSubPackages()) {
199203
subSpecs.addAll(subPkg.listEntrySpecs());
200204
}
205+
if (subSpecs.isEmpty()) {
206+
return;
207+
}
201208
subSpecs.sort(Comparator.comparing(ResEntrySpec::getResId));
202209

203210
// Separate the entry specs into groups by type.
@@ -347,7 +354,6 @@ public void decodeManifest(File apkDir) throws AndrolibException {
347354

348355
Log.i(TAG, "Decoding AndroidManifest.xml with " + (pkg != null ? "resources" : "only framework resources")
349356
+ "...");
350-
351357
try (
352358
InputStream in = inDir.getFileInput("AndroidManifest.xml");
353359
OutputStream out = outDir.getFileOutput("AndroidManifest.xml")
@@ -437,7 +443,7 @@ public void decodeManifest(File apkDir) throws AndrolibException {
437443
List<String> usesLibrary = mApkInfo.getUsesLibrary();
438444
libPackageIds.sort(null);
439445
for (int id : libPackageIds) {
440-
usesLibrary.add(mTable.getDynamicRefPackageName(id));
446+
usesLibrary.add(id == pkg.getId() ? pkg.getName() : mTable.getDynamicRefPackageName(id));
441447
}
442448
}
443449
} else {

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/BinaryXmlResourceParser.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ public String getAttributeName(int index) {
332332
ResPackage pkg = mTable.getMainPackage();
333333
if (pkg == null) {
334334
// If no main package, we load "android" package instead.
335-
pkg = mTable.resolvePackageGroup(1).getBasePackage();
335+
pkg = mTable.resolvePackageGroup(ResTable.SYS_PACKAGE_ID).getBasePackage();
336336
}
337337

338338
// #2836 - Skip item if the resource cannot be resolved.
@@ -342,6 +342,12 @@ public String getAttributeName(int index) {
342342
return name;
343343
}
344344

345+
Log.d(TAG, "Injecting dummy for unresolved attr reference: ns=%s, name=%s, id=%s",
346+
getAttributePrefix(index), name, nameId);
347+
if (!pkg.hasTypeSpec(nameId.typeId())) {
348+
pkg.addTypeSpec(nameId.typeId(), "attr");
349+
pkg.addType(nameId.typeId(), ResConfig.DEFAULT);
350+
}
345351
if (name.isEmpty()) {
346352
name = ResEntrySpec.DUMMY_PREFIX + nameId;
347353
}
@@ -409,7 +415,7 @@ public String getAttributeValue(int index) {
409415
ResPackage pkg = mTable.getMainPackage();
410416
if (pkg == null) {
411417
// If no main package, we load "android" package instead.
412-
pkg = mTable.resolvePackageGroup(1).getBasePackage();
418+
pkg = mTable.resolvePackageGroup(ResTable.SYS_PACKAGE_ID).getBasePackage();
413419
}
414420

415421
if (attr.valueType == ResValue.TYPE_STRING) {

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/table/ResTable.java

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ public ResPackageGroup addPackageGroup(int id, String name) throws AndrolibExcep
167167
if (id == 0 && mMainPackage != null) {
168168
id = getDynamicRefPackageId(name);
169169
if (id == 0) {
170-
id = mNextPackageId++;
170+
do {
171+
id = mNextPackageId++;
172+
} while (mDynamicRefTable.containsKey(id));
171173
}
172174
}
173175

@@ -185,36 +187,44 @@ public Collection<ResPackageGroup> listPackageGroups() {
185187
}
186188

187189
public ResPackageGroup resolvePackageGroup(int id) throws AndrolibException {
188-
ResPackageGroup pkgGroup = mPackageGroups.get(id);
189-
if (pkgGroup == null) {
190-
pkgGroup = loadLibraryById(id);
191-
if (pkgGroup == null) {
192-
pkgGroup = loadFrameworkById(id);
190+
if (id != SYS_PACKAGE_ID && mMainPackage != null) {
191+
ResPackageGroup pkgGroup = loadLibraryById(id);
192+
if (pkgGroup != null) {
193+
return pkgGroup;
193194
}
194195
}
195-
return pkgGroup;
196+
197+
ResPackageGroup pkgGroup = mPackageGroups.get(id);
198+
if (pkgGroup != null) {
199+
return pkgGroup;
200+
}
201+
202+
return loadFrameworkById(id);
196203
}
197204

198205
private ResPackageGroup loadLibraryById(int id) throws AndrolibException {
199-
String name = mDynamicRefTable.get(id);
200-
String[] libFiles = mConfig.getLibraryFiles();
201-
if (name == null || libFiles == null) {
202-
return null;
206+
if (mLibPackageIds.contains(id)) {
207+
return mPackageGroups.get(id);
203208
}
204209

205-
File apkFile = null;
206-
for (String libEntry : libFiles) {
207-
String[] parts = libEntry.split(":", 2);
208-
if (parts.length == 2 && name.equals(parts[0])) {
209-
apkFile = new File(parts[1]);
210-
break;
210+
String name;
211+
if (id == mMainPackage.getId()) {
212+
name = mMainPackage.getName();
213+
} else {
214+
name = mDynamicRefTable.get(id);
215+
if (name == null) {
216+
return null;
211217
}
212218
}
213-
if (apkFile == null) {
219+
220+
String[] fileNames = mConfig.getLibraryFiles().get(name);
221+
if (fileNames == null) {
214222
return null;
215223
}
216224

217-
loadPackagesFromApk(apkFile);
225+
for (String fileName : fileNames) {
226+
loadPackagesFromApk(new File(fileName));
227+
}
218228

219229
ResPackageGroup pkgGroup = mPackageGroups.get(id);
220230
if (pkgGroup == null) {
@@ -226,8 +236,11 @@ private ResPackageGroup loadLibraryById(int id) throws AndrolibException {
226236
}
227237

228238
private ResPackageGroup loadFrameworkById(int id) throws AndrolibException {
229-
File apkFile = new Framework(mConfig).getApkFile(id);
230-
loadPackagesFromApk(apkFile);
239+
if (mFramePackageIds.contains(id)) {
240+
return mPackageGroups.get(id);
241+
}
242+
243+
loadPackagesFromApk(new Framework(mConfig).getApkFile(id));
231244

232245
ResPackageGroup pkgGroup = mPackageGroups.get(id);
233246
if (pkgGroup == null) {
@@ -240,7 +253,6 @@ private ResPackageGroup loadFrameworkById(int id) throws AndrolibException {
240253

241254
private void loadPackagesFromApk(File apkFile) throws AndrolibException {
242255
Log.i(TAG, "Loading resource table from file: " + apkFile);
243-
244256
try (ZipRODirectory zipDir = new ZipRODirectory(apkFile)) {
245257
loadPackagesFromApk(apkFile, zipDir, false);
246258
} catch (DirectoryException ex) {

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/table/value/ResEnum.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package brut.androlib.res.table.value;
1818

1919
import brut.androlib.exceptions.AndrolibException;
20+
import brut.androlib.res.table.ResConfig;
2021
import brut.androlib.res.table.ResEntry;
2122
import brut.androlib.res.table.ResEntrySpec;
2223
import brut.androlib.res.table.ResId;
@@ -58,10 +59,15 @@ public void resolveKeys() throws AndrolibException {
5859

5960
// #2836 - Skip item if the resource cannot be resolved.
6061
if (skipUnresolved || keyId.pkgId() != pkg.getId()) {
61-
Log.w(TAG, "Unresolved enum reference: key=%s, value=%s", key, symbol.getValue());
62+
Log.w(TAG, "Unresolved enum symbol reference: " + key);
6263
continue;
6364
}
6465

66+
Log.d(TAG, "Injecting dummy for unresolved enum symbol reference: " + key);
67+
if (!pkg.hasTypeSpec(keyId.typeId())) {
68+
pkg.addTypeSpec(keyId.typeId(), "id");
69+
pkg.addType(keyId.typeId(), ResConfig.DEFAULT);
70+
}
6571
pkg.addEntrySpec(keyId.typeId(), keyId.entryId(), ResEntrySpec.DUMMY_PREFIX + keyId);
6672
pkg.addEntry(keyId.typeId(), keyId.entryId(), ResCustom.ID);
6773
}

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/table/value/ResFlags.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package brut.androlib.res.table.value;
1818

1919
import brut.androlib.exceptions.AndrolibException;
20+
import brut.androlib.res.table.ResConfig;
2021
import brut.androlib.res.table.ResEntry;
2122
import brut.androlib.res.table.ResEntrySpec;
2223
import brut.androlib.res.table.ResId;
@@ -60,10 +61,15 @@ public void resolveKeys() throws AndrolibException {
6061

6162
// #2836 - Skip item if the resource cannot be resolved.
6263
if (skipUnresolved || keyId.pkgId() != pkg.getId()) {
63-
Log.w(TAG, "Unresolved flag reference: key=%s, value=%s", key, symbol.getValue());
64+
Log.w(TAG, "Unresolved flag symbol reference: " + key);
6465
continue;
6566
}
6667

68+
Log.d(TAG, "Injecting dummy for unresolved flag symbol reference: " + key);
69+
if (!pkg.hasTypeSpec(keyId.typeId())) {
70+
pkg.addTypeSpec(keyId.typeId(), "id");
71+
pkg.addType(keyId.typeId(), ResConfig.DEFAULT);
72+
}
6773
pkg.addEntrySpec(keyId.typeId(), keyId.entryId(), ResEntrySpec.DUMMY_PREFIX + keyId);
6874
pkg.addEntry(keyId.typeId(), keyId.entryId(), ResCustom.ID);
6975
}

0 commit comments

Comments
 (0)