Skip to content

Commit a117132

Browse files
authored
Feat: Introduce "res-mode" options. (#3318)
* Revert "Remove Apktool Dummys. (#3258)" This reverts commit 0e22692. * feat: properly add dummys * refactor: shorten ResTypeSpec * style: remove extra space * refactor: extract FlagItem into own class * refactor: notate which type is null * fix: only add dummys if enabled * feat: skip unknown (if enabled) * feat: introduce "res-mode" * feat: expose config on res table * feat: add method to base attr for res skips * fix: ensure autobuild doesn't choke * refactor: remove java17 enhanced switch * refactor: rename methods * refactor: cleanup res-mode param * test: introduction of test/sample apk * refactor: make ResXmlPatcher public for loading XML * test: assertions for dummy|leave|retain * fix: prevent using `@null` as a name * refactor: shorten long param for 'resm' * refactor: leave for preserve
1 parent f0ca6d1 commit a117132

File tree

15 files changed

+334
-34
lines changed

15 files changed

+334
-34
lines changed

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,30 @@ private static void cmdDecode(CommandLine cli, Config config) throws AndrolibExc
167167
if (cli.hasOption("m") || cli.hasOption("match-original")) {
168168
config.analysisMode = true;
169169
}
170+
if (cli.hasOption("res-mode") || cli.hasOption("resolve-resources-mode")) {
171+
String mode = cli.getOptionValue("res-mode");
172+
if (mode == null) {
173+
mode = cli.getOptionValue("resolve-resources-mode");
174+
}
175+
switch (mode) {
176+
case "remove":
177+
case "delete":
178+
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_REMOVE);
179+
break;
180+
case "dummy":
181+
case "dummies":
182+
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_DUMMY);
183+
break;
184+
case "keep":
185+
case "preserve":
186+
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_RETAIN);
187+
break;
188+
default:
189+
System.err.println("Unknown resolve resources mode: " + mode);
190+
System.err.println("Expect: 'remove', 'dummy' or 'keep'.");
191+
System.exit(1);
192+
}
193+
}
170194

171195
File outDir;
172196
if (cli.hasOption("o") || cli.hasOption("output")) {
@@ -301,8 +325,6 @@ private static void _version() {
301325
}
302326

303327
private static void _Options() {
304-
305-
// create options
306328
Option versionOption = Option.builder("version")
307329
.longOpt("version")
308330
.desc("Print the version.")
@@ -409,6 +431,13 @@ private static void _Options() {
409431
.desc("Skip changes detection and build all files.")
410432
.build();
411433

434+
Option resolveResModeOption = Option.builder("resm")
435+
.longOpt("resource-mode")
436+
.desc("Sets the resolve resources mode. Possible values are: 'remove' (default), 'dummy' or 'keep'.")
437+
.hasArg(true)
438+
.argName("mode")
439+
.build();
440+
412441
Option aaptOption = Option.builder("a")
413442
.longOpt("aapt")
414443
.hasArg(true)
@@ -469,6 +498,7 @@ private static void _Options() {
469498
decodeOptions.addOption(apiLevelOption);
470499
decodeOptions.addOption(noAssetOption);
471500
decodeOptions.addOption(forceManOption);
501+
decodeOptions.addOption(resolveResModeOption);
472502

473503
buildOptions.addOption(apiLevelOption);
474504
buildOptions.addOption(debugBuiOption);
@@ -525,6 +555,7 @@ private static void _Options() {
525555
allOptions.addOption(debugDecOption);
526556
allOptions.addOption(noDbgOption);
527557
allOptions.addOption(forceManOption);
558+
allOptions.addOption(resolveResModeOption);
528559
allOptions.addOption(noAssetOption);
529560
allOptions.addOption(keepResOption);
530561
allOptions.addOption(debugBuiOption);

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.logging.Logger;
2424

2525
public class Config {
26+
private static Config instance = null;
2627
private final static Logger LOGGER = Logger.getLogger(Config.class.getName());
2728

2829
public final static short DECODE_SOURCES_NONE = 0x0000;
@@ -38,6 +39,10 @@ public class Config {
3839
public final static short DECODE_ASSETS_NONE = 0x0000;
3940
public final static short DECODE_ASSETS_FULL = 0x0001;
4041

42+
public final static short DECODE_RES_RESOLVE_REMOVE = 0x0000;
43+
public final static short DECODE_RES_RESOLVE_DUMMY = 0x0001;
44+
public final static short DECODE_RES_RESOLVE_RETAIN = 0x0002;
45+
4146
// Build options
4247
public boolean forceBuildAll = false;
4348
public boolean forceDeleteFramework = false;
@@ -55,6 +60,7 @@ public class Config {
5560
public short decodeResources = DECODE_RESOURCES_FULL;
5661
public short forceDecodeManifest = FORCE_DECODE_MANIFEST_NONE;
5762
public short decodeAssets = DECODE_ASSETS_FULL;
63+
public short decodeResolveMode = DECODE_RES_RESOLVE_REMOVE;
5864
public int apiLevel = 0;
5965
public boolean analysisMode = false;
6066
public boolean forceDelete = false;
@@ -72,8 +78,23 @@ public boolean isAapt2() {
7278
return this.useAapt2 || this.aaptVersion == 2;
7379
}
7480

81+
public boolean isDecodeResolveModeUsingDummies() {
82+
return decodeResolveMode == DECODE_RES_RESOLVE_DUMMY;
83+
}
84+
85+
public boolean isDecodeResolveModeRemoving() {
86+
return decodeResolveMode == DECODE_RES_RESOLVE_REMOVE;
87+
}
88+
7589
private Config() {
90+
instance = this;
91+
}
7692

93+
public static Config getInstance() {
94+
if (instance == null) {
95+
instance = new Config();
96+
}
97+
return instance;
7798
}
7899

79100
private void setDefaultFrameworkDirectory() {
@@ -105,6 +126,13 @@ public void setDecodeSources(short mode) throws AndrolibException {
105126
decodeSources = mode;
106127
}
107128

129+
public void setDecodeResolveMode(short mode) throws AndrolibException {
130+
if (mode != DECODE_RES_RESOLVE_REMOVE && mode != DECODE_RES_RESOLVE_DUMMY && mode != DECODE_RES_RESOLVE_RETAIN) {
131+
throw new AndrolibException("Invalid decode resources mode");
132+
}
133+
decodeResolveMode = mode;
134+
}
135+
108136
public void setDecodeResources(short mode) throws AndrolibException {
109137
if (mode != DECODE_RESOURCES_NONE && mode != DECODE_RESOURCES_FULL) {
110138
throw new AndrolibException("Invalid decode resources mode");

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResSpec.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ public ResTypeSpec getType() {
105105
return mType;
106106
}
107107

108+
public boolean isDummyResSpec() {
109+
return getName().startsWith("APKTOOL_DUMMY_");
110+
}
111+
108112
public void addResource(ResResource res) throws AndrolibException {
109113
addResource(res, false);
110114
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ public boolean getAnalysisMode() {
7070
return mConfig.analysisMode;
7171
}
7272

73+
public Config getConfig() {
74+
return mConfig;
75+
}
76+
7377
public boolean isMainPkgLoaded() {
7478
return mMainPkgLoaded;
7579
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
3+
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package brut.androlib.res.data.arsc;
18+
19+
import brut.androlib.exceptions.AndrolibException;
20+
import brut.androlib.res.data.value.ResReferenceValue;
21+
22+
public class FlagItem {
23+
public final ResReferenceValue ref;
24+
public final int flag;
25+
public String value;
26+
27+
public FlagItem(ResReferenceValue ref, int flag) {
28+
this.ref = ref;
29+
this.flag = flag;
30+
}
31+
32+
public String getValue() throws AndrolibException {
33+
if (value == null) {
34+
if (ref.referentIsNull()) {
35+
return String.format("APKTOOL_MISSING_0x%08x", ref.getRawIntValue());
36+
}
37+
value = ref.getReferent().getName();
38+
}
39+
return value;
40+
}
41+
}

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResEnumAttr.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.io.IOException;
2626
import java.util.HashMap;
2727
import java.util.Map;
28+
import java.util.logging.Logger;
2829

2930
public class ResEnumAttr extends ResAttr {
3031
ResEnumAttr(ResReferenceValue parent, int type, Integer min, Integer max,
@@ -46,15 +47,21 @@ public String convertToResXmlFormat(ResScalarValue value)
4647
}
4748

4849
@Override
49-
protected void serializeBody(XmlSerializer serializer, ResResource res)
50-
throws AndrolibException, IOException {
50+
protected void serializeBody(XmlSerializer serializer, ResResource res) throws AndrolibException, IOException {
5151
for (Duo<ResReferenceValue, ResIntValue> duo : mItems) {
5252
int intVal = duo.m2.getValue();
53+
54+
// #2836 - Support skipping items if the resource cannot be identified.
5355
ResResSpec m1Referent = duo.m1.getReferent();
56+
if (m1Referent == null && shouldRemoveUnknownRes()) {
57+
LOGGER.fine(String.format("null enum reference: m1=0x%08x(%s), m2=0x%08x(%s)",
58+
duo.m1.getRawIntValue(), duo.m1.getType(), duo.m2.getRawIntValue(), duo.m2.getType()));
59+
continue;
60+
}
5461

5562
serializer.startTag(null, "enum");
5663
serializer.attribute(null, "name",
57-
m1Referent != null ? m1Referent.getName() : "@null"
64+
m1Referent != null ? m1Referent.getName() : String.format("APKTOOL_MISSING_0x%08x", duo.m1.getRawIntValue())
5865
);
5966
serializer.attribute(null, "value", String.valueOf(intVal));
6067
serializer.endTag(null, "enum");
@@ -81,4 +88,6 @@ private String decodeValue(int value) throws AndrolibException {
8188

8289
private final Duo<ResReferenceValue, ResIntValue>[] mItems;
8390
private final Map<Integer, String> mItemsCache = new HashMap<>();
91+
92+
private static final Logger LOGGER = Logger.getLogger(ResEnumAttr.class.getName());
8493
}

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFlagsAttr.java

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717
package brut.androlib.res.data.value;
1818

1919
import brut.androlib.exceptions.AndrolibException;
20+
import brut.androlib.res.data.ResResSpec;
2021
import brut.androlib.res.data.ResResource;
22+
import brut.androlib.res.data.arsc.FlagItem;
2123
import brut.util.Duo;
2224
import org.xmlpull.v1.XmlSerializer;
2325

2426
import java.io.IOException;
2527
import java.util.Arrays;
28+
import java.util.logging.Logger;
2629

2730
public class ResFlagsAttr extends ResAttr {
2831
ResFlagsAttr(ResReferenceValue parent, int type, Integer min, Integer max,
@@ -38,7 +41,7 @@ public class ResFlagsAttr extends ResAttr {
3841
@Override
3942
public String convertToResXmlFormat(ResScalarValue value)
4043
throws AndrolibException {
41-
if(value instanceof ResReferenceValue) {
44+
if (value instanceof ResReferenceValue) {
4245
return value.encodeAsResXml();
4346
}
4447
if (!(value instanceof ResIntValue)) {
@@ -70,13 +73,19 @@ public String convertToResXmlFormat(ResScalarValue value)
7073
}
7174

7275
@Override
73-
protected void serializeBody(XmlSerializer serializer, ResResource res)
74-
throws AndrolibException, IOException {
76+
protected void serializeBody(XmlSerializer serializer, ResResource res) throws AndrolibException, IOException {
7577
for (FlagItem item : mItems) {
78+
ResResSpec referent = item.ref.getReferent();
79+
80+
// #2836 - Support skipping items if the resource cannot be identified.
81+
if (referent == null && shouldRemoveUnknownRes()) {
82+
LOGGER.fine(String.format("null flag reference: 0x%08x(%s)", item.ref.getValue(), item.ref.getType()));
83+
continue;
84+
}
85+
7686
serializer.startTag(null, "flag");
7787
serializer.attribute(null, "name", item.getValue());
78-
serializer.attribute(null, "value",
79-
String.format("0x%08x", item.flag));
88+
serializer.attribute(null, "value", String.format("0x%08x", item.flag));
8089
serializer.endTag(null, "flag");
8190
}
8291
}
@@ -130,24 +139,5 @@ private void loadFlags() {
130139
private FlagItem[] mZeroFlags;
131140
private FlagItem[] mFlags;
132141

133-
private static class FlagItem {
134-
public final ResReferenceValue ref;
135-
public final int flag;
136-
public String value;
137-
138-
public FlagItem(ResReferenceValue ref, int flag) {
139-
this.ref = ref;
140-
this.flag = flag;
141-
}
142-
143-
public String getValue() throws AndrolibException {
144-
if (value == null) {
145-
if (ref.referentIsNull()) {
146-
return "@null";
147-
}
148-
value = ref.getReferent().getName();
149-
}
150-
return value;
151-
}
152-
}
142+
private static final Logger LOGGER = Logger.getLogger(ResFlagsAttr.class.getName());
153143
}

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResScalarValue.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ public void serializeToResValuesXml(XmlSerializer serializer,
8181
}
8282
}
8383

84+
// Dummy attributes should be <item> with type attribute
85+
if (res.getResSpec().isDummyResSpec()) {
86+
item = true;
87+
}
88+
8489
// Android does not allow values (false) for ids.xml anymore
8590
// https://issuetracker.google.com/issues/80475496
8691
// But it decodes as a ResBoolean, which makes no sense. So force it to empty

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResStyleValue.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void serializeToResValuesXml(XmlSerializer serializer,
5353
ResResSpec spec = mItem.m1.getReferent();
5454

5555
if (spec == null) {
56-
LOGGER.fine(String.format("null reference: m1=0x%08x(%s), m2=0x%08x(%s)",
56+
LOGGER.fine(String.format("null style reference: m1=0x%08x(%s), m2=0x%08x(%s)",
5757
mItem.m1.getRawIntValue(), mItem.m1.getType(), mItem.m2.getRawIntValue(), mItem.m2.getType()));
5858
continue;
5959
}

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValue.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
*/
1717
package brut.androlib.res.data.value;
1818

19-
public class ResValue {
19+
import brut.androlib.Config;
2020

21+
public class ResValue {
22+
public boolean shouldRemoveUnknownRes() {
23+
return Config.getInstance().isDecodeResolveModeRemoving();
24+
}
2125
}

0 commit comments

Comments
 (0)