lookupMap;
29 | private final int shortest;
30 | private final int longest;
31 |
32 | /**
33 | * Define the lookup table to be used in translation
34 | *
35 | * Note that, as of Lang 3.1, the key to the lookup table is converted to a
36 | * java.lang.String, while the value remains as a java.lang.CharSequence.
37 | * This is because we need the key to support hashCode and equals(Object),
38 | * allowing it to be the key for a HashMap. See LANG-882.
39 | *
40 | * @param lookup CharSequence[][] table of size [*][2]
41 | */
42 | public LookupTranslator(final CharSequence[]... lookup) {
43 | lookupMap = new HashMap<>();
44 | int _shortest = Integer.MAX_VALUE;
45 | int _longest = 0;
46 | if (lookup != null) {
47 | for (final CharSequence[] seq : lookup) {
48 | this.lookupMap.put(seq[0].toString(), seq[1]);
49 | final int sz = seq[0].length();
50 | if (sz < _shortest) {
51 | _shortest = sz;
52 | }
53 | if (sz > _longest) {
54 | _longest = sz;
55 | }
56 | }
57 | }
58 | shortest = _shortest;
59 | longest = _longest;
60 | }
61 |
62 | /**
63 | * {@inheritDoc}
64 | */
65 | @Override
66 | public int translate(final CharSequence input, final int index, final Writer out) throws IOException {
67 | int max = longest;
68 | if (index + longest > input.length()) {
69 | max = input.length() - index;
70 | }
71 | // descend so as to get a greedy algorithm
72 | for (int i = max; i >= shortest; i--) {
73 | final CharSequence subSeq = input.subSequence(index, index + i);
74 | final CharSequence result = lookupMap.get(subSeq.toString());
75 | if (result != null) {
76 | out.write(result.toString());
77 | return i;
78 | }
79 | }
80 | return 0;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/library/src/main/java/mt/modder/hub/axmlTools/utils/AttributeSet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature
3 | * Copyright 2024, developer-krushna
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of developer-krushna nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 |
32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need
33 | * additional information or have any questions
34 | */
35 |
36 | package mt.modder.hub.axmlTools.utils;
37 |
38 | public interface AttributeSet {
39 | int getAttributeCount();
40 |
41 | String getAttributeName(int index);
42 |
43 | String getAttributeValue(int index);
44 |
45 | String getPositionDescription();
46 |
47 | int getAttributeNameResource(int index);
48 |
49 | int getAttributeListValue(int index, String options[], int defaultValue);
50 |
51 | boolean getAttributeBooleanValue(int index, boolean defaultValue);
52 |
53 | int getAttributeResourceValue(int index, int defaultValue);
54 |
55 | int getAttributeIntValue(int index, int defaultValue);
56 |
57 | int getAttributeUnsignedIntValue(int index, int defaultValue);
58 |
59 | float getAttributeFloatValue(int index, float defaultValue);
60 |
61 | String getIdAttribute();
62 |
63 | String getClassAttribute();
64 |
65 | int getIdAttributeResourceValue(int index);
66 |
67 | int getStyleAttribute();
68 |
69 | String getAttributeValue(String namespace, String attribute);
70 |
71 | int getAttributeListValue(String namespace, String attribute,
72 | String options[], int defaultValue);
73 |
74 | boolean getAttributeBooleanValue(String namespace, String attribute,
75 | boolean defaultValue);
76 |
77 | int getAttributeResourceValue(String namespace, String attribute,
78 | int defaultValue);
79 |
80 | int getAttributeIntValue(String namespace, String attribute,
81 | int defaultValue);
82 |
83 | int getAttributeUnsignedIntValue(String namespace, String attribute,
84 | int defaultValue);
85 |
86 | float getAttributeFloatValue(String namespace, String attribute,
87 | float defaultValue);
88 |
89 | // TODO: remove
90 | int getAttributeValueType(int index);
91 |
92 | int getAttributeValueData(int index);
93 | }
94 |
--------------------------------------------------------------------------------
/library/src/main/java/mt/modder/hub/axmlTools/escaper/NumericEntityEscaper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://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 mt.modder.hub.axmlTools.escaper;
18 |
19 | import java.io.IOException;
20 | import java.io.Writer;
21 |
22 | /**
23 | * Translates codepoints to their XML numeric entity escaped value.
24 | */
25 | class NumericEntityEscaper extends CodePointTranslator {
26 |
27 | private final int below;
28 | private final int above;
29 | private final boolean between;
30 |
31 | /**
32 | * Constructs a NumericEntityEscaper for the specified range. This is
33 | * the underlying method for the other constructors/builders. The below
34 | * and above boundaries are inclusive when between is
35 | * true and exclusive when it is false.
36 | *
37 | * @param below int value representing the lowest codepoint boundary
38 | * @param above int value representing the highest codepoint boundary
39 | * @param between whether to escape between the boundaries or outside them
40 | */
41 | private NumericEntityEscaper(final int below, final int above, final boolean between) {
42 | this.below = below;
43 | this.above = above;
44 | this.between = between;
45 | }
46 |
47 | /**
48 | * Constructs a NumericEntityEscaper for all characters.
49 | */
50 | public NumericEntityEscaper() {
51 | this(0, Integer.MAX_VALUE, true);
52 | }
53 |
54 | /**
55 | * Constructs a NumericEntityEscaper below the specified value (exclusive).
56 | *
57 | * @param codepoint below which to escape
58 | * @return the newly created {@code NumericEntityEscaper} instance
59 | */
60 | public static NumericEntityEscaper below(final int codepoint) {
61 | return outsideOf(codepoint, Integer.MAX_VALUE);
62 | }
63 |
64 | /**
65 | * Constructs a NumericEntityEscaper above the specified value (exclusive).
66 | *
67 | * @param codepoint above which to escape
68 | * @return the newly created {@code NumericEntityEscaper} instance
69 | */
70 | public static NumericEntityEscaper above(final int codepoint) {
71 | return outsideOf(0, codepoint);
72 | }
73 |
74 | /**
75 | * Constructs a NumericEntityEscaper between the specified values (inclusive).
76 | *
77 | * @param codepointLow above which to escape
78 | * @param codepointHigh below which to escape
79 | * @return the newly created {@code NumericEntityEscaper} instance
80 | */
81 | public static NumericEntityEscaper between(final int codepointLow, final int codepointHigh) {
82 | return new NumericEntityEscaper(codepointLow, codepointHigh, true);
83 | }
84 |
85 | /**
86 | * Constructs a NumericEntityEscaper outside of the specified values (exclusive).
87 | *
88 | * @param codepointLow below which to escape
89 | * @param codepointHigh above which to escape
90 | * @return the newly created {@code NumericEntityEscaper} instance
91 | */
92 | public static NumericEntityEscaper outsideOf(final int codepointLow, final int codepointHigh) {
93 | return new NumericEntityEscaper(codepointLow, codepointHigh, false);
94 | }
95 |
96 | /**
97 | * {@inheritDoc}
98 | */
99 | @Override
100 | public boolean translate(final int codepoint, final Writer out) throws IOException {
101 | if(between) {
102 | if (codepoint < below || codepoint > above) {
103 | return false;
104 | }
105 | } else {
106 | if (codepoint >= below && codepoint <= above) {
107 | return false;
108 | }
109 | }
110 |
111 | out.write("");
112 | out.write(Integer.toString(codepoint, 10));
113 | out.write(';');
114 | return true;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/app/src/main/java/mt/modder/hub/axml/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature
3 | * Copyright 2024, developer-krushna
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of developer-krushna nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 |
32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need
33 | * additional information or have any questions
34 | */
35 |
36 | package mt.modder.hub.axml;
37 |
38 | import android.app.*;
39 | import android.os.*;
40 | import java.io.FileInputStream;
41 | import java.io.IOException;
42 |
43 | import android.widget.TextView;
44 | import java.io.File;
45 | import java.io.FileWriter;
46 | import java.io.StringWriter;
47 | import java.io.PrintWriter;
48 | import android.widget.ScrollView;
49 | import android.widget.LinearLayout.LayoutParams;
50 | import android.widget.LinearLayout;
51 | import java.util.regex.*;
52 | import java.io.*;
53 | import java.nio.*;
54 |
55 |
56 |
57 |
58 | public class MainActivity extends Activity {
59 |
60 | public String Input_Path = "/storage/emulated/0/AndroidManifest.xml";
61 |
62 | public String outPath = "sdcard/MyDecompiledAXML.xml";
63 |
64 | @Override
65 | protected void onCreate(Bundle savedInstanceState) {
66 | super.onCreate(savedInstanceState);
67 |
68 | final ScrollView scroll = new ScrollView(this);
69 | scroll.setLayoutParams(new LinearLayout.LayoutParams(-1, -1));
70 | scroll.setFillViewport(true);
71 | scroll.setPadding(8,8,8,8);
72 | TextView text = new TextView(this);
73 | text.setText("Processing "+ Input_Path +" ...");
74 | try {
75 | // Read the binary XML file into a byte array
76 | FileInputStream fis = new FileInputStream(Input_Path);
77 | byte[] byteArray = new byte[fis.available()];
78 | fis.read(byteArray);
79 | fis.close();
80 |
81 | // initialize the axmlprinter class
82 | AXMLPrinter axmlPrinter = new AXMLPrinter();
83 | axmlPrinter.setEnableID2Name(true);
84 | axmlPrinter.setAttrValueTranslation(true);
85 | axmlPrinter.setExtractPermissionDescription(true);
86 |
87 | // Use the XMLDecompiler to decompile to an XML string
88 | // Place your resources.arsc file in the same directory of your xml file
89 | String xmlString = axmlPrinter.readFromFile(Input_Path);
90 |
91 | // Direct process without enabling custom resource id2name
92 | // String xmlString = axmlPrinter.convertXml(byteArray);
93 |
94 | // Output the XML string
95 | saveAsFile(xmlString, outPath);
96 | text.setText("Processing complete .File saved in " + outPath);
97 | } catch (Exception e) {
98 | // Complete extraction of error
99 | StringWriter sw = new StringWriter();
100 | PrintWriter pw = new PrintWriter(sw);
101 | e.printStackTrace(pw);
102 | String exceptionDetails = sw.toString();
103 | text.setText(exceptionDetails);
104 | e.printStackTrace();
105 | }
106 |
107 |
108 | scroll.addView(text);
109 | setContentView(scroll);
110 | }
111 |
112 | public void saveAsFile(String data, String path) throws IOException{
113 | File outputFile = new File(path);
114 | FileWriter fileWriter = new FileWriter(outputFile);
115 | fileWriter.write(data.toString());
116 | fileWriter.close();
117 | }
118 |
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/library/src/main/java/mt/modder/hub/axmlTools/arsc/ResourceIdExtractor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature
3 | * Copyright 2024, developer-krushna
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of developer-krushna nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 |
32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need
33 | * additional information or have any questions
34 | */
35 |
36 | package mt.modder.hub.axmlTools.arsc;
37 |
38 | import mt.modder.hub.arsc.*;
39 | import java.io.*;
40 | import java.util.*;
41 | import java.util.regex.*;
42 |
43 | /*
44 | Author @developer-krushna
45 | Thanks to ChatGPT for helping me to explain if arsc file is obfuscated with random names
46 | and also helped me to add comments for better understanding
47 | */
48 | public class ResourceIdExtractor {
49 | private Map idToNameCache = new HashMap<>();
50 |
51 | // Method to load and parse the ARSC file
52 | public void loadArscData(InputStream arsc) throws Exception {
53 | BinaryResourceFile resourceFile = BinaryResourceFile.fromInputStream(arsc);
54 | List chunks = resourceFile.getChunks();
55 |
56 | for (Chunk chunk : chunks) {
57 | if (chunk instanceof ResourceTableChunk) {
58 | ResourceTableChunk resourceTableChunk = (ResourceTableChunk) chunk;
59 | for (PackageChunk packageChunk : resourceTableChunk.getPackages()) {
60 | StringPoolChunk keyStringPool = packageChunk.getKeyStringPool();
61 | for (TypeChunk typeChunk : packageChunk.getTypeChunks()) {
62 | for (Map.Entry entry : typeChunk.getEntries().entrySet()) {
63 | BinaryResourceIdentifier binaryResourceIdentifier = BinaryResourceIdentifier.create(packageChunk.getId(), typeChunk.getId(), (int) entry.getKey());
64 |
65 | String hexId = binaryResourceIdentifier.toString();
66 | String resourceTypeName = typeChunk.getTypeName(); // Get resource type name directly
67 |
68 | // Extract data for different resource types
69 | String extractedData = extractResourceData(resourceTypeName, keyStringPool, entry, resourceTableChunk);
70 |
71 | // Cache the extracted data based on the hex ID
72 | idToNameCache.put(hexId, extractedData);
73 | }
74 | }
75 | }
76 | }
77 | }
78 |
79 | }
80 |
81 | private String extractResourceData(String resourceTypeName, StringPoolChunk keyStringPool, Map.Entry entry, ResourceTableChunk resourceTableChunk) {
82 | String extractedData = null;
83 |
84 | if (resourceTypeName.equals("attr") || resourceTypeName.equals("style") || resourceTypeName.equals("color")) {
85 | // Handle @attr or @style
86 | String key = keyStringPool.getString(entry.getValue().keyIndex());
87 | extractedData = resourceTypeName + "/" + key;
88 | } else {
89 | // Handle other resource types
90 | if (entry.getValue().value() == null || entry.getValue().value().data() > resourceTableChunk.getStringPool().getStringCount() || entry.getValue().value().data() < 0) {
91 | extractedData = null; // Handle invalid data
92 | } else {
93 | String key = keyStringPool.getString(entry.getValue().keyIndex());
94 | extractedData = resourceTypeName + "/" + key;
95 | }
96 | }
97 |
98 | return extractedData;
99 | }
100 |
101 | // Method to retrieve the name for a given hex ID
102 | public String getNameForHexId(String hexId) {
103 | return idToNameCache.get("0x" + hexId);
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/library/src/main/java/mt/modder/hub/axmlTools/escaper/CharSequenceTranslator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://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 mt.modder.hub.axmlTools.escaper;
18 |
19 | import java.io.IOException;
20 | import java.io.StringWriter;
21 | import java.io.Writer;
22 | import java.util.Locale;
23 |
24 | /**
25 | * An API for translating text.
26 | * Its core use is to escape and unescape text. Because escaping and unescaping
27 | * is completely contextual, the API does not present two separate signatures.
28 | *
29 | */
30 | abstract class CharSequenceTranslator {
31 |
32 | /**
33 | * Translate a set of codepoints, represented by an int index into a CharSequence,
34 | * into another set of codepoints. The number of codepoints consumed must be returned,
35 | * and the only IOExceptions thrown must be from interacting with the Writer so that
36 | * the top level API may reliably ignore StringWriter IOExceptions.
37 | *
38 | * @param input CharSequence that is being translated
39 | * @param index int representing the current point of translation
40 | * @param out Writer to translate the text to
41 | * @return int count of codepoints consumed
42 | * @throws IOException if and only if the Writer produces an IOException
43 | */
44 | public abstract int translate(CharSequence input, int index, Writer out) throws IOException;
45 |
46 | /**
47 | * Helper for non-Writer usage.
48 | * @param input CharSequence to be translated
49 | * @return String output of translation
50 | */
51 | public final String translate(final CharSequence input) {
52 | if (input == null) {
53 | return null;
54 | }
55 | try {
56 | final StringWriter writer = new StringWriter(input.length() * 2);
57 | translate(input, writer);
58 | return writer.toString();
59 | } catch (final IOException ioe) {
60 | // this should never ever happen while writing to a StringWriter
61 | throw new RuntimeException(ioe);
62 | }
63 | }
64 |
65 | /**
66 | * Translate an input onto a Writer. This is intentionally final as its algorithm is
67 | * tightly coupled with the abstract method of this class.
68 | *
69 | * @param input CharSequence that is being translated
70 | * @param out Writer to translate the text to
71 | * @throws IOException if and only if the Writer produces an IOException
72 | */
73 | public final void translate(final CharSequence input, final Writer out) throws IOException {
74 | if (out == null) {
75 | throw new IllegalArgumentException("The Writer must not be null");
76 | }
77 | if (input == null) {
78 | return;
79 | }
80 | int pos = 0;
81 | final int len = input.length();
82 | while (pos < len) {
83 | final int consumed = translate(input, pos, out);
84 | if (consumed == 0) {
85 | final char[] c = Character.toChars(Character.codePointAt(input, pos));
86 | out.write(c);
87 | pos+= c.length;
88 | continue;
89 | }
90 | // contract with translators is that they have to understand codepoints
91 | // and they just took care of a surrogate pair
92 | for (int pt = 0; pt < consumed; pt++) {
93 | pos += Character.charCount(Character.codePointAt(input, pos));
94 | }
95 | }
96 | }
97 |
98 | /**
99 | * Helper method to create a merger of this translator with another set of
100 | * translators. Useful in customizing the standard functionality.
101 | *
102 | * @param translators CharSequenceTranslator array of translators to merge with this one
103 | * @return CharSequenceTranslator merging this translator with the others
104 | */
105 | public final CharSequenceTranslator with(final CharSequenceTranslator... translators) {
106 | final CharSequenceTranslator[] newArray = new CharSequenceTranslator[translators.length + 1];
107 | newArray[0] = this;
108 | System.arraycopy(translators, 0, newArray, 1, translators.length);
109 | return new AggregateTranslator(newArray);
110 | }
111 |
112 | /**
113 | * Returns an upper case hexadecimal String for the given
114 | * character.
115 | *
116 | * @param codepoint The codepoint to convert.
117 | * @return An upper case hexadecimal String
118 | */
119 | public static String hex(final int codepoint) {
120 | return Integer.toHexString(codepoint).toUpperCase(Locale.ENGLISH);
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/library/src/main/java/mt/modder/hub/axmlTools/IntReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature
3 | * Copyright 2024, developer-krushna
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of developer-krushna nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 |
32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need
33 | * additional information or have any questions
34 | */
35 |
36 | package mt.modder.hub.axmlTools;
37 |
38 | import java.io.EOFException;
39 | import java.io.IOException;
40 | import java.io.InputStream;
41 |
42 | public final class IntReader {
43 | private boolean m_bigEndian;
44 | private int m_position;
45 | private InputStream m_stream;
46 |
47 | public IntReader() {
48 | }
49 |
50 | public IntReader(InputStream inputStream, boolean bigEndian) {
51 | reset(inputStream, bigEndian);
52 | }
53 |
54 | public final int available() throws IOException {
55 | return this.m_stream.available();
56 | }
57 |
58 | public final void close() {
59 | InputStream inputStream = this.m_stream;
60 | if (inputStream == null) {
61 | return;
62 | }
63 | try {
64 | inputStream.close();
65 | } catch (IOException e) {
66 | }
67 | reset(null, false);
68 | }
69 |
70 | public final int getPosition() {
71 | return this.m_position;
72 | }
73 |
74 | public final InputStream getStream() {
75 | return this.m_stream;
76 | }
77 |
78 | public final boolean isBigEndian() {
79 | return this.m_bigEndian;
80 | }
81 |
82 | public final int readByte() throws IOException {
83 | return readInt(1);
84 | }
85 |
86 | public final byte[] readByteArray(int length) throws IOException {
87 | byte[] array = new byte[length];
88 | int read = m_stream.read(array);
89 | m_position += read;
90 | if (read != length) {
91 | throw new EOFException();
92 | }
93 | return array;
94 | }
95 |
96 | public final int readInt() throws IOException {
97 | return readInt(4);
98 | }
99 |
100 | public final int readInt(int length) throws IOException {
101 | if (length < 0 || length > 4) {
102 | throw new IllegalArgumentException();
103 | }
104 | int result = 0;
105 | if (m_bigEndian) {
106 | for (int i = (length - 1) * 8; i >= 0; i -= 8) {
107 | int b = m_stream.read();
108 | if (b == -1) {
109 | throw new EOFException();
110 | }
111 | m_position += 1;
112 | result |= (b << i);
113 | }
114 | } else {
115 | length *= 8;
116 | for (int i = 0; i != length; i += 8) {
117 | int b = m_stream.read();
118 | if (b == -1) {
119 | throw new EOFException();
120 | }
121 | m_position += 1;
122 | result |= (b << i);
123 | }
124 | }
125 | return result;
126 | }
127 |
128 | public final void readIntArray(int[] array, int offset, int length) throws IOException {
129 | for (; length > 0; length -= 1) {
130 | array[offset++] = readInt();
131 | }
132 | }
133 |
134 | public final int[] readIntArray(int length) throws IOException {
135 | int[] array = new int[length];
136 | readIntArray(array, 0, length);
137 | return array;
138 | }
139 |
140 | public final int readShort() throws IOException {
141 | return readInt(2);
142 | }
143 |
144 | public final void reset(InputStream inputStream, boolean z) {
145 | this.m_stream = inputStream;
146 | this.m_bigEndian = z;
147 | this.m_position = 0;
148 | }
149 |
150 | public final void setBigEndian(boolean z) {
151 | this.m_bigEndian = z;
152 | }
153 |
154 | public final void skip(int bytes) throws IOException {
155 | if (bytes <= 0) {
156 | return;
157 | }
158 | long skip = this.m_stream.skip(bytes);
159 | this.m_position = (int) (this.m_position + skip);
160 | if (skip != bytes) {
161 | throw new EOFException();
162 | }
163 | }
164 |
165 | public final void skipInt() throws IOException {
166 | skip(4);
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/library/src/main/java/mt/modder/hub/axml/AttributesExtractor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature
3 | * Copyright 2024, developer-krushna
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of developer-krushna nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS NTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 |
32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need
33 | * additional information or have any questions
34 | */
35 |
36 | package mt.modder.hub.axml;
37 |
38 | import android.util.Log;
39 | import java.io.InputStream;
40 | import java.util.ArrayList;
41 | import java.util.HashMap;
42 | import java.util.LinkedHashMap;
43 | import java.util.List;
44 | import java.util.Map;
45 |
46 | import javax.xml.parsers.DocumentBuilder;
47 | import java.util.Collections;
48 | import java.util.Comparator;
49 | import javax.xml.parsers.DocumentBuilderFactory;
50 |
51 | import org.w3c.dom.Document;
52 | import org.w3c.dom.NamedNodeMap;
53 | import org.w3c.dom.Node;
54 | import org.w3c.dom.NodeList;
55 |
56 | /*Idea from Jadx(By https://github.com/skylot/jadx)*/
57 | public class AttributesExtractor {
58 |
59 | private static final String ATTR_XML = "/assets/attrs.xml";
60 | private static final String MANIFEST_ATTR_XML = "/assets/attrs_manifest.xml";
61 |
62 | private enum MAttrType {
63 | ENUM, FLAG
64 | }
65 |
66 | private static class MAttr {
67 | private final MAttrType type;
68 | private final Map values = new LinkedHashMap<>();
69 |
70 | public MAttr(MAttrType type) {
71 | this.type = type;
72 | }
73 |
74 | public MAttrType getType() {
75 | return type;
76 | }
77 |
78 | public Map getValues() {
79 | return values;
80 | }
81 |
82 | @Override
83 | public String toString() {
84 | return "[" + type + ", " + values + ']';
85 | }
86 | }
87 |
88 | private final Map attrMap = new HashMap<>();
89 |
90 | private final Map appAttrMap = new HashMap<>();
91 |
92 | private static AttributesExtractor instance;
93 |
94 | public static AttributesExtractor getInstance() {
95 | if (instance == null) {
96 | try {
97 | instance = new AttributesExtractor();
98 | } catch (Exception e) {
99 |
100 | }
101 | }
102 | return instance;
103 | }
104 |
105 | private AttributesExtractor() {
106 | parseAll();
107 | }
108 |
109 | private void parseAll() {
110 | parse(loadXML(ATTR_XML));
111 | parse(loadXML(MANIFEST_ATTR_XML));
112 |
113 | }
114 |
115 | private Document loadXML(String xml) {
116 | Document doc;
117 | try {
118 | InputStream xmlStream = AttributesExtractor.class.getResourceAsStream(xml);
119 | if (xmlStream == null) {
120 | throw new RuntimeException(xml + " not found in classpath");
121 | }
122 | DocumentBuilder dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
123 | doc = dBuilder.parse(xmlStream);
124 | } catch (Exception e) {
125 | throw new RuntimeException("Xml load error, file: " + xml, e);
126 | }
127 | return doc;
128 | }
129 |
130 | private void parse(Document doc) {
131 | NodeList nodeList = doc.getChildNodes();
132 | for (int count = 0; count < nodeList.getLength(); count++) {
133 | Node node = nodeList.item(count);
134 | if (node.getNodeType() == Node.ELEMENT_NODE
135 | && node.hasChildNodes()) {
136 | parseAttrList(node.getChildNodes());
137 | }
138 | }
139 | }
140 |
141 | private void parseAttrList(NodeList nodeList) {
142 | for (int count = 0; count < nodeList.getLength(); count++) {
143 | Node tempNode = nodeList.item(count);
144 | if (tempNode.getNodeType() == Node.ELEMENT_NODE
145 | && tempNode.hasAttributes()
146 | && tempNode.hasChildNodes()) {
147 | String name = null;
148 | NamedNodeMap nodeMap = tempNode.getAttributes();
149 | for (int i = 0; i < nodeMap.getLength(); i++) {
150 | Node node = nodeMap.item(i);
151 | if (node.getNodeName().equals("name")) {
152 | name = node.getNodeValue();
153 | break;
154 | }
155 | }
156 | if (name != null && tempNode.getNodeName().equals("attr")) {
157 | parseValues(name, tempNode.getChildNodes());
158 | } else {
159 | parseAttrList(tempNode.getChildNodes());
160 | }
161 | }
162 | }
163 | }
164 |
165 | private void parseValues(String name, NodeList nodeList) {
166 | MAttr attr = null;
167 | for (int count = 0; count < nodeList.getLength(); count++) {
168 | Node tempNode = nodeList.item(count);
169 | if (tempNode.getNodeType() == Node.ELEMENT_NODE && tempNode.hasAttributes()) {
170 | if (attr == null) {
171 | if (tempNode.getNodeName().equals("enum")) {
172 | attr = new MAttr(MAttrType.ENUM);
173 | } else if (tempNode.getNodeName().equals("flag")) {
174 | attr = new MAttr(MAttrType.FLAG);
175 | }
176 | if (attr == null) {
177 | return;
178 | }
179 | System.out.println("Parsed attribute: " + name + " with type: " + attr.getType());
180 | attrMap.put(name, attr);
181 | }
182 | NamedNodeMap attributes = tempNode.getAttributes();
183 | Node nameNode = attributes.getNamedItem("name");
184 | if (nameNode != null) {
185 | Node valueNode = attributes.getNamedItem("value");
186 | if (valueNode != null) {
187 | try {
188 | long key;
189 | String nodeValue = valueNode.getNodeValue();
190 | if (nodeValue.startsWith("0x")) {
191 | nodeValue = nodeValue.substring(2);
192 | key = Long.parseLong(nodeValue, 16);
193 | } else {
194 | key = Long.parseLong(nodeValue);
195 | }
196 | attr.getValues().put(key, nameNode.getNodeValue());
197 | System.out.println("Added value: " + key + " -> " + nameNode.getNodeValue());
198 | } catch (NumberFormatException e) {
199 | e.printStackTrace();
200 | }
201 | }
202 | }
203 | }
204 | }
205 | }
206 |
207 | public String decode(String attrName, long value) {
208 | MAttr attr = attrMap.get(attrName);
209 | if (attr == null) {
210 | attr = appAttrMap.get(attrName);
211 | if (attr == null) {
212 | return null;
213 | }
214 | }
215 | Log.d(attrName,"" + value);
216 | System.out.println(attrName+ " : " + value);
217 | if (attr.getType() == MAttrType.ENUM) {
218 | return attr.getValues().get(value);
219 | } else if (attr.getType() == MAttrType.FLAG) {
220 | List flagList = new ArrayList<>();
221 | List attrKeys = new ArrayList<>(attr.getValues().keySet());
222 | Collections.sort(attrKeys, new Comparator() {
223 | @Override
224 | public int compare(Long a, Long b) {
225 | return Long.compare(b, a); // for descending order
226 | }
227 | });
228 | for (Long key : attrKeys) {
229 | String attrValue = attr.getValues().get(key);
230 | if (value == key) {
231 | flagList.add(attrValue);
232 | break;
233 | } else if ((key != 0) && ((value & key) == key)) {
234 | flagList.add(attrValue);
235 | value ^= key;
236 | }
237 | }
238 |
239 | StringBuilder sb = new StringBuilder();
240 | for (int i = 0; i < flagList.size(); i++) {
241 | if (i > 0) {
242 | sb.append("|");
243 | }
244 | sb.append(flagList.get(i));
245 | }
246 | return sb.toString();
247 | }
248 | return null;
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/library/src/main/java/mt/modder/hub/axmlTools/StringBlock.java:
--------------------------------------------------------------------------------
1 | /*
2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature
3 | * Copyright 2024, developer-krushna
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of developer-krushna nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 |
32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need
33 | * additional information or have any questions
34 | */
35 |
36 | package mt.modder.hub.axmlTools;
37 |
38 | import java.io.IOException;
39 | import java.nio.charset.Charset;
40 | import java.nio.charset.StandardCharsets;
41 | import java.util.*;
42 | import java.nio.*;
43 | import mt.modder.hub.axmlTools.escaper.*;
44 |
45 | public class StringBlock {
46 | private static final int CHUNK_TYPE = 0x001C0001; // Type identifier for the chunk
47 | public static final int UTF8_FLAG = 0x00000100; // Flag for UTF-8 encoding
48 | private boolean isUTF8; // Flag to indicate if the strings are UTF-8 encoded
49 | private int[] stringOffsets; // Offsets for the start of each string
50 | private int[] strings; // Array containing the actual string data
51 | private int[] styleOffsets; // Offsets for the start of each style
52 | private int[] styles; // Array containing the style data
53 |
54 |
55 | private StringBlock() {
56 | }
57 |
58 | // Retrieves a byte from an int array at a specified index
59 | private static int getByte(int[] array, int index) {
60 | return (array[index / 4] >>> ((index % 4) * 8)) & 255;
61 | }
62 |
63 | // Converts a segment of an int array into a byte array
64 | private static byte[] getByteArray(int[] array, int offset, int length) {
65 | byte[] bytes = new byte[length];
66 | for (int i = 0; i < length; i++) {
67 | bytes[i] = (byte) getByte(array, offset + i);
68 | }
69 | return bytes;
70 | }
71 |
72 | // Determines the size of the length field based on the encoding
73 | private int getLengthFieldSize(int[] array, int offset) {
74 | if (!this.isUTF8) {
75 | return (32768 & getShort(array, offset)) != 0 ? 4 : 2;
76 | }
77 | int size = (getByte(array, offset) & 128) != 0 ? 2 + 1 : 2;
78 | return (getByte(array, offset) & 128) != 0 ? size + 1 : size;
79 | }
80 |
81 | // Retrieves a short value from an int array at a specified offset
82 | private static final int getShort(int[] array, int offset) {
83 | int value = array[offset / 4];
84 | if ((offset % 4) / 2 == 0) {
85 | return (value & 0xFFFF);
86 | } else {
87 | return (value >>> 16);
88 | }
89 | }
90 |
91 | // Retrieves the length of a string from the array
92 | private int getStringLength(int[] array, int offset) {
93 | if (!this.isUTF8) {
94 | int value = getShort(array, offset);
95 | if ((32768 & value) != 0) {
96 | return getShort(array, offset + 2) | ((value & 32767) << 16);
97 | }
98 | return value;
99 | }
100 | if ((getByte(array, offset) & 128) != 0) {
101 | offset++;
102 | }
103 | int nextOffset = offset + 1;
104 | int byte1 = getByte(array, nextOffset);
105 | return (byte1 & 128) != 0 ? ((byte1 & 127) << 8) | getByte(array, nextOffset + 1) : byte1;
106 | }
107 |
108 | // Retrieves the style array for a given index
109 | private int[] getStyle(int index) {
110 | if (styleOffsets == null || styles == null || index >= styleOffsets.length) {
111 | return null;
112 | }
113 | int offsetIndex = styleOffsets[index] / 4;
114 | int count = 0;
115 | int offsetIndex2 = offsetIndex;
116 | while (true) {
117 | if (offsetIndex2 >= styles.length || styles[offsetIndex2] == -1) {
118 | break;
119 | }
120 | count++;
121 | offsetIndex2++;
122 | }
123 | if (count == 0 || count % 3 != 0) {
124 | return null;
125 | }
126 | int[] style = new int[count];
127 | int offsetIndex3 = offsetIndex;
128 | int styleIndex = 0;
129 | while (true) {
130 | if (offsetIndex3 >= styles.length || styles[offsetIndex3] == -1) {
131 | break;
132 | }
133 | style[styleIndex++] = styles[offsetIndex3++];
134 | }
135 | return style;
136 | }
137 |
138 | // Reads a StringBlock from the given IntReader
139 | public static StringBlock read(IntReader intReader) throws IOException {
140 | ChunkUtil.readCheckType(intReader, CHUNK_TYPE); // Check the chunk type
141 | int chunkSize = intReader.readInt(); // Total size of the chunk
142 | int stringCount = intReader.readInt(); // Number of strings
143 | int styleCount = intReader.readInt(); // Number of styles
144 | int flags = intReader.readInt(); // Flags (including UTF-8 flag)
145 | int stringDataOffset = intReader.readInt(); // Offset to string data
146 | int stylesOffset = intReader.readInt(); // Offset to styles data
147 |
148 | StringBlock stringBlock = new StringBlock();
149 | stringBlock.isUTF8 = (flags & UTF8_FLAG) != 0; // Determine if strings are UTF-8 encoded
150 | stringBlock.stringOffsets = intReader.readIntArray(stringCount); // Read string offsets
151 | if (styleCount != 0) {
152 | stringBlock.styleOffsets = intReader.readIntArray(styleCount); // Read style offsets
153 | }
154 |
155 | int stringDataSize = (stylesOffset == 0 ? chunkSize : stylesOffset) - stringDataOffset;
156 | if (stringDataSize % 4 == 0) {
157 | stringBlock.strings = intReader.readIntArray(stringDataSize / 4); // Read string data
158 | if (stylesOffset != 0) {
159 | int stylesDataSize = chunkSize - stylesOffset;
160 | if (stylesDataSize % 4 != 0) {
161 | throw new IOException("Style data size is not multiple of 4 (" + stylesDataSize + ").");
162 | }
163 | stringBlock.styles = intReader.readIntArray(stylesDataSize / 4); // Read styles data
164 | }
165 | return stringBlock;
166 | }
167 | throw new IOException("String data size is not multiple of 4 (" + stringDataSize + ").");
168 | }
169 |
170 | // Finds the index of a string in the string block
171 | public int find(String str) {
172 | if (str == null) {
173 | return -1;
174 | }
175 | for (int i = 0; i < stringOffsets.length; i++) {
176 | int offset = stringOffsets[i];
177 | int length = getShort(strings, offset);
178 | if (length == str.length()) {
179 | int j = 0;
180 | while (j != length) {
181 | offset += 2;
182 | if (str.charAt(j) != getShort(strings, offset)) {
183 | break;
184 | }
185 | j++;
186 | }
187 | if (j == length) {
188 | return i;
189 | }
190 | }
191 | }
192 | return -1;
193 | }
194 |
195 | // Gets the string at the specified index
196 | public CharSequence get(int index) {
197 | return getString(index);
198 | }
199 |
200 | // Gets the count of strings in the string block
201 | public int getCount() {
202 | if (stringOffsets != null) {
203 | return stringOffsets.length;
204 | }
205 | return 0;
206 | }
207 |
208 | // Gets the HTML representation of the string at the specified index
209 | public String getHTML(int index) {
210 | String str = getString(index);
211 | if (str == null) {
212 | return null;
213 | }
214 | int[] style = getStyle(index);
215 | if (style == null) {
216 | return str;
217 | }
218 | StringBuilder htmlBuilder = new StringBuilder(str.length() + 32);
219 | int currentIndex = 0;
220 | while (true) {
221 | int nextStyleIndex = -1;
222 | for (int i = 0; i < style.length; i += 3) {
223 | if (style[i + 1] != -1 && (nextStyleIndex == -1 || style[nextStyleIndex + 1] > style[i + 1])) {
224 | nextStyleIndex = i;
225 | }
226 | }
227 | int nextStylePosition = nextStyleIndex != -1 ? style[nextStyleIndex + 1] : str.length();
228 | for (int i = 0; i < style.length; i += 3) {
229 | int end = style[i + 2];
230 | if (end != -1 && end < nextStylePosition) {
231 | if (currentIndex <= end) {
232 | htmlBuilder.append(str, currentIndex, end + 1);
233 | currentIndex = end + 1;
234 | }
235 | style[i + 2] = -1;
236 | htmlBuilder.append("").append(getString(style[i])).append('>');
237 | }
238 | }
239 | if (currentIndex < nextStylePosition) {
240 | htmlBuilder.append(str, currentIndex, nextStylePosition);
241 | currentIndex = nextStylePosition;
242 | }
243 | if (nextStyleIndex == -1) {
244 | return htmlBuilder.toString();
245 | }
246 | htmlBuilder.append('<').append(getString(style[nextStyleIndex])).append('>');
247 | style[nextStyleIndex + 1] = -1;
248 | }
249 | }
250 |
251 | // Retrieves the string at the specified index
252 | public String getString(int index) {
253 | if (index < 0 || stringOffsets == null || index >= stringOffsets.length) {
254 | return null;
255 | }
256 | int offset = stringOffsets[index];
257 | int length = getStringLength(strings, offset);
258 | int lengthFieldSize = offset + getLengthFieldSize(strings, offset);
259 | Charset charset = this.isUTF8 ? StandardCharsets.UTF_8 : StandardCharsets.UTF_16LE;
260 | if (!this.isUTF8) {
261 | length <<= 1;
262 | }
263 | String originalString = new String(getByteArray(strings, lengthFieldSize, length), 0, length, charset);
264 | return XmlEscaper.escapeXml10(originalString);
265 | }
266 |
267 |
268 | }
269 |
270 |
--------------------------------------------------------------------------------
/library/src/main/java/mt/modder/hub/axmlTools/utils/TypedValue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature
3 | * Copyright 2024, developer-krushna
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of developer-krushna nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 |
32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need
33 | * additional information or have any questions
34 | */
35 |
36 | package mt.modder.hub.axmlTools.utils;
37 |
38 | /**
39 | * Container for a dynamically typed data value. Primarily used with
40 | * {@link android.content.res.Resources} for holding resource values.
41 | */
42 | public class TypedValue {
43 | /**
44 | * The value contains no data.
45 | */
46 | public static final int TYPE_NULL = 0x00;
47 |
48 | /**
49 | * The data field holds a resource identifier.
50 | */
51 | public static final int TYPE_REFERENCE = 0x01;
52 | /**
53 | * The data field holds an attribute resource
54 | * identifier (referencing an attribute in the current theme
55 | * style, not a resource entry).
56 | */
57 | public static final int TYPE_ATTRIBUTE = 0x02;
58 | /**
59 | * The string field holds string data. In addition, if
60 | * data is non-zero then it is the string block
61 | * index of the string and assetCookie is the set of
62 | * assets the string came from.
63 | */
64 | public static final int TYPE_STRING = 0x03;
65 | /**
66 | * The data field holds an IEEE 754 floating point number.
67 | */
68 | public static final int TYPE_FLOAT = 0x04;
69 | /**
70 | * The data field holds a complex number encoding a
71 | * dimension value.
72 | */
73 | public static final int TYPE_DIMENSION = 0x05;
74 | /**
75 | * The data field holds a complex number encoding a fraction
76 | * of a container.
77 | */
78 | public static final int TYPE_FRACTION = 0x06;
79 |
80 | /**
81 | * Identifies the start of plain integer values. Any type value
82 | * from this to {@link #TYPE_LAST_INT} means the
83 | * data field holds a generic integer value.
84 | */
85 | public static final int TYPE_FIRST_INT = 0x10;
86 |
87 | /**
88 | * The data field holds a number that was
89 | * originally specified in decimal.
90 | */
91 | public static final int TYPE_INT_DEC = 0x10;
92 | /**
93 | * The data field holds a number that was
94 | * originally specified in hexadecimal (0xn).
95 | */
96 | public static final int TYPE_INT_HEX = 0x11;
97 | /**
98 | * The data field holds 0 or 1 that was originally
99 | * specified as "false" or "true".
100 | */
101 | public static final int TYPE_INT_BOOLEAN = 0x12;
102 |
103 | /**
104 | * Identifies the start of integer values that were specified as
105 | * color constants (starting with '#').
106 | */
107 | public static final int TYPE_FIRST_COLOR_INT = 0x1c;
108 |
109 | /**
110 | * The data field holds a color that was originally
111 | * specified as #aarrggbb.
112 | */
113 | public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;
114 | /**
115 | * The data field holds a color that was originally
116 | * specified as #rrggbb.
117 | */
118 | public static final int TYPE_INT_COLOR_RGB8 = 0x1d;
119 | /**
120 | * The data field holds a color that was originally
121 | * specified as #argb.
122 | */
123 | public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;
124 | /**
125 | * The data field holds a color that was originally
126 | * specified as #rgb.
127 | */
128 | public static final int TYPE_INT_COLOR_RGB4 = 0x1f;
129 |
130 | /**
131 | * Identifies the end of integer values that were specified as color
132 | * constants.
133 | */
134 | public static final int TYPE_LAST_COLOR_INT = 0x1f;
135 |
136 | /**
137 | * Identifies the end of plain integer values.
138 | */
139 | public static final int TYPE_LAST_INT = 0x1f;
140 |
141 | /* ------------------------------------------------------------ */
142 |
143 | /**
144 | * Complex data: bit location of unit information.
145 | */
146 | public static final int COMPLEX_UNIT_SHIFT = 0;
147 | /**
148 | * Complex data: mask to extract unit information (after shifting by
149 | * {@link #COMPLEX_UNIT_SHIFT}). This gives us 16 possible types, as
150 | * defined below.
151 | */
152 | public static final int COMPLEX_UNIT_MASK = 0xf;
153 |
154 | /**
155 | * {@link #TYPE_DIMENSION} complex unit: Value is raw pixels.
156 | */
157 | public static final int COMPLEX_UNIT_PX = 0;
158 | /**
159 | * {@link #TYPE_DIMENSION} complex unit: Value is Device Independent
160 | * Pixels.
161 | */
162 | public static final int COMPLEX_UNIT_DIP = 1;
163 | /**
164 | * {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel.
165 | */
166 | public static final int COMPLEX_UNIT_SP = 2;
167 | /**
168 | * {@link #TYPE_DIMENSION} complex unit: Value is in points.
169 | */
170 | public static final int COMPLEX_UNIT_PT = 3;
171 | /**
172 | * {@link #TYPE_DIMENSION} complex unit: Value is in inches.
173 | */
174 | public static final int COMPLEX_UNIT_IN = 4;
175 | /**
176 | * {@link #TYPE_DIMENSION} complex unit: Value is in millimeters.
177 | */
178 | public static final int COMPLEX_UNIT_MM = 5;
179 |
180 | /**
181 | * {@link #TYPE_FRACTION} complex unit: A basic fraction of the overall
182 | * size.
183 | */
184 | public static final int COMPLEX_UNIT_FRACTION = 0;
185 | /**
186 | * {@link #TYPE_FRACTION} complex unit: A fraction of the parent size.
187 | */
188 | public static final int COMPLEX_UNIT_FRACTION_PARENT = 1;
189 |
190 | /**
191 | * Complex data: where the radix information is, telling where the decimal
192 | * place appears in the mantissa.
193 | */
194 | public static final int COMPLEX_RADIX_SHIFT = 4;
195 | /**
196 | * Complex data: mask to extract radix information (after shifting by
197 | * {@link #COMPLEX_RADIX_SHIFT}). This give us 4 possible fixed point
198 | * representations as defined below.
199 | */
200 | public static final int COMPLEX_RADIX_MASK = 0x3;
201 |
202 | /**
203 | * Complex data: the mantissa is an integral number -- i.e., 0xnnnnnn.0
204 | */
205 | public static final int COMPLEX_RADIX_23p0 = 0;
206 | /**
207 | * Complex data: the mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
208 | */
209 | public static final int COMPLEX_RADIX_16p7 = 1;
210 | /**
211 | * Complex data: the mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
212 | */
213 | public static final int COMPLEX_RADIX_8p15 = 2;
214 | /**
215 | * Complex data: the mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
216 | */
217 | public static final int COMPLEX_RADIX_0p23 = 3;
218 |
219 | /**
220 | * Complex data: bit location of mantissa information.
221 | */
222 | public static final int COMPLEX_MANTISSA_SHIFT = 8;
223 | /**
224 | * Complex data: mask to extract mantissa information (after shifting by
225 | * {@link #COMPLEX_MANTISSA_SHIFT}). This gives us 23 bits of precision;
226 | * the top bit is the sign.
227 | */
228 | public static final int COMPLEX_MANTISSA_MASK = 0xffffff;
229 |
230 | /* ------------------------------------------------------------ */
231 |
232 | /**
233 | * If {@link #density} is equal to this value, then the density should be
234 | * treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}.
235 | */
236 | public static final int DENSITY_DEFAULT = 0;
237 |
238 | /**
239 | * If {@link #density} is equal to this value, then there is no density
240 | * associated with the resource and it should not be scaled.
241 | */
242 | public static final int DENSITY_NONE = 0xffff;
243 |
244 | /* ------------------------------------------------------------ */
245 | private static final float MANTISSA_MULT =
246 | 1.0f / (1 << TypedValue.COMPLEX_MANTISSA_SHIFT);
247 | private static final float[] RADIX_MULTS = new float[]{
248 | 1.0f * MANTISSA_MULT, 1.0f / (1 << 7) * MANTISSA_MULT,
249 | 1.0f / (1 << 15) * MANTISSA_MULT, 1.0f / (1 << 23) * MANTISSA_MULT
250 | };
251 | private static final String[] DIMENSION_UNIT_STRS = new String[]{
252 | "px", "dip", "sp", "pt", "in", "mm"
253 | };
254 | private static final String[] FRACTION_UNIT_STRS = new String[]{
255 | "%", "%p"
256 | };
257 | /**
258 | * The type held by this value, as defined by the constants here.
259 | * This tells you how to interpret the other fields in the object.
260 | */
261 | public int type;
262 |
263 | /**
264 | * Retrieve the base value from a complex data integer. This uses the
265 | * {@link #COMPLEX_MANTISSA_MASK} and {@link #COMPLEX_RADIX_MASK} fields of
266 | * the data to compute a floating point representation of the number they
267 | * describe. The units are ignored.
268 | *
269 | * @param complex A complex data value.
270 | * @return A floating point value corresponding to the complex data.
271 | */
272 | public static float complexToFloat(int complex) {
273 | return (complex & (TypedValue.COMPLEX_MANTISSA_MASK
274 | << TypedValue.COMPLEX_MANTISSA_SHIFT))
275 | * RADIX_MULTS[(complex >> TypedValue.COMPLEX_RADIX_SHIFT)
276 | & TypedValue.COMPLEX_RADIX_MASK];
277 | }
278 |
279 | /**
280 | * Perform type conversion as per {@link #coerceToString()} on an
281 | * explicitly supplied type and data.
282 | *
283 | * @param type The data type identifier.
284 | * @param data The data value.
285 | * @return String The coerced string value. If the value is
286 | * null or the type is not known, null is returned.
287 | */
288 | public static final String coerceToString(int type, int data) {
289 | switch (type) {
290 | case TYPE_NULL:
291 | return null;
292 | case TYPE_REFERENCE:
293 | return "@" + data;
294 | case TYPE_ATTRIBUTE:
295 | return "?" + data;
296 | case TYPE_FLOAT:
297 | return Float.toString(Float.intBitsToFloat(data));
298 | case TYPE_DIMENSION:
299 | return Float.toString(complexToFloat(data)) + DIMENSION_UNIT_STRS[
300 | (data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK];
301 | case TYPE_FRACTION:
302 | return Float.toString(complexToFloat(data) * 100) + FRACTION_UNIT_STRS[
303 | (data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK];
304 | case TYPE_INT_HEX:
305 | return "0x" + Integer.toHexString(data);
306 | case TYPE_INT_BOOLEAN:
307 | return data != 0 ? "true" : "false";
308 | }
309 |
310 | if (type >= TYPE_FIRST_COLOR_INT && type <= TYPE_LAST_COLOR_INT) {
311 | return "#" + Integer.toHexString(data);
312 | } else if (type >= TYPE_FIRST_INT && type <= TYPE_LAST_INT) {
313 | return Integer.toString(data);
314 | }
315 |
316 | return null;
317 | }
318 |
319 | };
320 |
--------------------------------------------------------------------------------
/library/src/main/java/mt/modder/hub/axmlTools/AXmlResourceParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature
3 | * Copyright 2024, developer-krushna
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of developer-krushna nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 |
32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need
33 | * additional information or have any questions
34 | */
35 |
36 | package mt.modder.hub.axmlTools;
37 |
38 | import java.io.IOException;
39 | import java.io.InputStream;
40 | import java.io.Reader;
41 | import mt.modder.hub.axmlTools.utils.TypedValue;
42 | import org.xmlpull.v1.XmlPullParser;
43 | import org.xmlpull.v1.XmlPullParserException;
44 | import mt.modder.hub.axmlTools.utils.*;
45 | import java.nio.*;
46 |
47 | public class AXmlResourceParser implements XmlResourceParser, AutoCloseable {
48 |
49 | // constant values representing different xml chunk types
50 | private static final int CHUNK_AXML_FILE = 0x80003;
51 | private static final int CHUNK_RESOURCEIDS = 0x80180;
52 | private static final int CHUNK_XML_END_NAMESPACE = 0x100101;
53 | private static final int CHUNK_XML_END_TAG = 0x100103;
54 | private static final int CHUNK_XML_START_TAG = 0x100102;
55 | private static final String E_NOT_SUPPORTED = "Method is not supported.";
56 | public static final int XML_RESOURCE_MAP = 0x0180;
57 |
58 | private static final int XML_START_TAG_CHUNK = 1048832;
59 | private static final int XML_TEXT_CHUNK = 1048836;
60 | private static final int XML_NAMESPACE_PUSH_CHUNK = 1048832;
61 |
62 | public static final String NS_ANDROID = "http://schemas.android.com/apk/res/android";
63 |
64 | // vriables for storing attributes and state information
65 | private int[] mAttributes;
66 | private int mClassAttribute;
67 | private boolean mDecreaseDepth;
68 | private int eventType;
69 | private int mIdAttribute;
70 | private int mLineNumber;
71 | private int mName;
72 | private int mNamespaceUri;
73 | private IntReader mReader;
74 | private int[] mResourceIDs;
75 | private StringBlock stringBlock;
76 | private int mStyleAttribute;
77 | private PrecededXmlToken precededXmlToken = null;
78 | private boolean mOperational = false;
79 |
80 | private String[] resourceMap;
81 | public static boolean isChunkResourceIDs = false;
82 |
83 |
84 | private NamespaceStack mNamespaces = new NamespaceStack();
85 |
86 | // class to handle the namespace stack
87 | public static final class NamespaceStack {
88 | private int mCount;
89 | private int[] mData = new int[32];
90 | private int mDataLength;
91 | private int mDepth;
92 |
93 |
94 | // ensures the internal data array has enough capacity
95 | private void ensureDataCapacity(int capacity) {
96 | int available = (mData.length - mDataLength);
97 | if (available > capacity) {
98 | return;
99 | }
100 | int newLength = (mData.length + available) * 2;
101 | int[] newData = new int[newLength];
102 | System.arraycopy(mData, 0, newData, 0, mDataLength);
103 | mData = newData;
104 | }
105 |
106 | // finds a prefix or URI in the stack
107 | private final int find(int prefixOrUri, boolean prefix) {
108 | if (mDataLength == 0) {
109 | return -1;
110 | }
111 | int offset = mDataLength - 1;
112 | for (int i = mDepth; i != 0; --i) {
113 | int count = mData[offset];
114 | offset -= 2;
115 | for (; count != 0; --count) {
116 | if (prefix) {
117 | if (mData[offset] == prefixOrUri) {
118 | return mData[offset + 1];
119 | }
120 | } else {
121 | if (mData[offset + 1] == prefixOrUri) {
122 | return mData[offset];
123 | }
124 | }
125 | offset -= 2;
126 | }
127 | }
128 | return -1;
129 | }
130 |
131 | //retrive a prefix or URI at a specific index
132 |
133 | private final int get(int index, boolean prefix) {
134 | if (mDataLength == 0 || index < 0) {
135 | return -1;
136 | }
137 | int offset = 0;
138 | for (int i = mDepth; i != 0; --i) {
139 | int count = mData[offset];
140 | if (index >= count) {
141 | index -= count;
142 | offset += (2 + count * 2);
143 | continue;
144 | }
145 | offset += (1 + index * 2);
146 | if (!prefix) {
147 | offset += 1;
148 | }
149 | return mData[offset];
150 | }
151 | return -1;
152 | }
153 |
154 | // decreases the depth of the namespace stack
155 | public final void decreaseDepth() {
156 | if (mDataLength == 0) {
157 | return;
158 | }
159 | int offset = mDataLength - 1;
160 | int count = mData[offset];
161 | if ((offset - 1 - count * 2) == 0) {
162 | return;
163 | }
164 | mDataLength -= 2 + count * 2;
165 | mCount -= count;
166 | mDepth -= 1;
167 | }
168 |
169 | //finds a prefix in the stack
170 | public int findPrefix(int prefix) {
171 | return find(prefix, false);
172 | }
173 |
174 | // finds a URI in a stack
175 | public int findUri(int uri) {
176 | return find(uri, true);
177 | }
178 |
179 | // gets the accumulated count of namespace at a given depth
180 | public final int getAccumulatedCount(int depth) {
181 | if (mDataLength == 0 || depth < 0) {
182 | return 0;
183 | }
184 | if (depth > mDepth) {
185 | depth = mDepth;
186 | }
187 | int accumulatedCount = 0;
188 | int offset = 0;
189 | for (; depth != 0; --depth) {
190 | int count = mData[offset];
191 | accumulatedCount += count;
192 | offset += (2 + count * 2);
193 | }
194 | return accumulatedCount;
195 | }
196 |
197 | // gets the current count of namespaces
198 | public final int getCurrentCount() {
199 | if (mDataLength == 0) {
200 | return 0;
201 | }
202 | int offset = mDataLength - 1;
203 | return mData[offset];
204 | }
205 |
206 | // gets the current depth of the stack
207 | public int getDepth() {
208 | return this.mDepth;
209 | }
210 |
211 | // gets the prefix at a specific index
212 | public int getPrefix(int index) {
213 | return get(index, true);
214 | }
215 |
216 | // gets the total counts of namespace
217 | public final int getTotalCount() {
218 | return this.mCount;
219 | }
220 |
221 | // gets the URI at a specfic index
222 | public int getUri(int index) {
223 | return get(index, false);
224 | }
225 |
226 | // increases the depth of the namespace stack
227 | public final void increaseDepth() {
228 | ensureDataCapacity(2);
229 | int offset = mDataLength;
230 | mData[offset] = 0;
231 | mData[offset + 1] = 0;
232 | mDataLength += 2;
233 | mDepth += 1;
234 | }
235 |
236 | // Pops the namespace entry from the stack
237 | public final boolean pop() {
238 | if (mDataLength == 0) {
239 | return false;
240 | }
241 | int offset = mDataLength - 1;
242 | int count = mData[offset];
243 | if (count == 0) {
244 | return false;
245 | }
246 | count -= 1;
247 | offset -= 2;
248 | mData[offset] = count;
249 | offset -= (1 + count * 2);
250 | mData[offset] = count;
251 | mDataLength -= 2;
252 | mCount -= 1;
253 | return true;
254 | }
255 |
256 | // Pops the specific prefix and URI from the stack
257 | public final boolean pop(int prefix, int uri) {
258 | if (mDataLength == 0) {
259 | return false;
260 | }
261 | int offset = mDataLength - 1;
262 | int count = mData[offset];
263 | for (int i = 0, o = offset - 2; i != count; ++i, o -= 2) {
264 | if (mData[o] != prefix || mData[o + 1] != uri) {
265 | continue;
266 | }
267 | count -= 1;
268 | if (i == 0) {
269 | mData[o] = count;
270 | o -= (1 + count * 2);
271 | mData[o] = count;
272 | } else {
273 | mData[offset] = count;
274 | offset -= (1 + 2 + count * 2);
275 | mData[offset] = count;
276 | System.arraycopy(
277 | mData, o + 2,
278 | mData, o,
279 | mDataLength - o);
280 | }
281 | mDataLength -= 2;
282 | mCount -= 1;
283 | return true;
284 | }
285 | return false;
286 | }
287 |
288 | // pushes a prefix and URI onto the stack
289 | public final void push(int prefix, int uri) {
290 | if (mDepth == 0) {
291 | increaseDepth();
292 | }
293 | ensureDataCapacity(2);
294 | int offset = mDataLength - 1;
295 | int count = mData[offset];
296 | mData[offset - 1 - count * 2] = count + 1;
297 | mData[offset] = prefix;
298 | mData[offset + 1] = uri;
299 | mData[offset + 2] = count + 1;
300 | mDataLength += 2;
301 | mCount += 1;
302 | }
303 |
304 | // resets the nemespace stack
305 | public final void reset() {
306 | this.mDataLength = 0;
307 | this.mCount = 0;
308 | this.mDepth = 0;
309 | }
310 | }
311 |
312 | // Class to store information about previous xml token
313 | public static final class PrecededXmlToken {
314 | public String name;
315 | public String namespace;
316 | public int type;
317 |
318 | public PrecededXmlToken(String name, String namespace, int type) {
319 | this.name = name;
320 | this.namespace = namespace;
321 | this.type = type;
322 | }
323 | }
324 |
325 | // Constructor to intialize the parser
326 | public AXmlResourceParser() {
327 | resetEventInfo();
328 |
329 | }
330 |
331 |
332 |
333 |
334 | // reads the next xml token and updatea the parser state
335 | private void doNext() throws IOException {
336 | int readInt = 0;
337 | if (this.stringBlock == null) {
338 | ChunkUtil.readCheckType(this.mReader, CHUNK_AXML_FILE);
339 | this.mReader.skipInt();
340 | this.stringBlock = StringBlock.read(this.mReader);
341 | this.mNamespaces.increaseDepth();
342 | this.mOperational = true;
343 | }
344 | if (this.eventType == XmlPullParser.END_DOCUMENT) {
345 | return;
346 | }
347 | int previousEvent = this.eventType;
348 | resetEventInfo();
349 | while (true) {
350 | if (this.mDecreaseDepth) {
351 | this.mDecreaseDepth = false;
352 | this.mNamespaces.decreaseDepth();
353 | }
354 | if (previousEvent == XmlPullParser.END_TAG && this.mNamespaces.getDepth() == 1 && this.mNamespaces.getCurrentCount() == 0) {
355 | this.eventType = XmlPullParser.END_DOCUMENT;
356 | return;
357 | }
358 | int chunkType = previousEvent == XmlPullParser.START_DOCUMENT ? CHUNK_XML_START_TAG : this.mReader.readInt();
359 | if (chunkType == CHUNK_RESOURCEIDS) {
360 | readInt = this.mReader.readInt();
361 | if (readInt < 8 || readInt % 4 != 0) {
362 | break;
363 | }
364 | this.mResourceIDs = this.mReader.readIntArray((readInt / 4) - 2);
365 | resourceMap = new String[mResourceIDs.length];
366 | } else if (chunkType < XML_START_TAG_CHUNK || chunkType > XML_TEXT_CHUNK) {
367 | break;
368 | } else if (chunkType == CHUNK_XML_START_TAG && previousEvent == -1) {
369 | this.eventType = XmlPullParser.START_DOCUMENT;
370 | return;
371 | } else {
372 | this.mReader.skipInt();
373 | int lineNumber = this.mReader.readInt();
374 | this.mReader.skipInt();
375 | if (chunkType != XML_START_TAG_CHUNK && chunkType != CHUNK_XML_END_NAMESPACE) {
376 | this.mLineNumber = lineNumber;
377 | if (chunkType == CHUNK_XML_START_TAG) {
378 | this.mNamespaceUri = this.mReader.readInt();
379 | this.mName = this.mReader.readInt();
380 | this.mReader.skipInt();
381 | int attributeCount = this.mReader.readInt();
382 | this.mIdAttribute = (attributeCount >>> 16) - 1;
383 | int classAttr = this.mReader.readInt();
384 | this.mClassAttribute = classAttr;
385 | this.mStyleAttribute = (classAttr >>> 16) - 1;
386 | this.mClassAttribute = (65535 & classAttr) - 1;
387 | this.mAttributes = this.mReader.readIntArray((attributeCount & 65535) * 5);
388 | int i = 3;
389 | while (true) {
390 | int[] attributes = this.mAttributes;
391 | if (i >= attributes.length) {
392 | this.mNamespaces.increaseDepth();
393 | this.eventType = 2;
394 | return;
395 | }
396 | attributes[i] = attributes[i] >>> 24;
397 | i += 5;
398 | }
399 | } else if (chunkType == CHUNK_XML_END_TAG) {
400 | this.mNamespaceUri = this.mReader.readInt();
401 | this.mName = this.mReader.readInt();
402 | this.eventType = 3;
403 | this.mDecreaseDepth = true;
404 | return;
405 | } else if (chunkType == XML_TEXT_CHUNK) {
406 | this.mName = this.mReader.readInt();
407 | this.mReader.skipInt();
408 | this.mReader.skipInt();
409 | this.eventType = 4;
410 | return;
411 | }
412 | } else if (chunkType == XML_NAMESPACE_PUSH_CHUNK) {
413 | this.mNamespaces.push(this.mReader.readInt(), this.mReader.readInt());
414 | } else {
415 | this.mReader.skipInt();
416 | this.mReader.skipInt();
417 | this.mNamespaces.pop();
418 | }
419 | }
420 | }
421 | throw new IOException("Invalid resource ids size (" + readInt + ").");
422 | }
423 |
424 | /**
425 | * Finds the attribute index for a given namespace and attribute name.
426 | */
427 |
428 | private final int findAttribute(String namespace, String attributeName) {
429 | if (stringBlock == null || attributeName == null) {
430 | return -1;
431 | }
432 |
433 | int attributeIndex = stringBlock.find(attributeName);
434 | if (attributeIndex == -1) {
435 | return -1;
436 | }
437 |
438 | int namespaceIndex = namespace != null ? stringBlock.find(namespace) : -1;
439 |
440 | for (int i = 0; i < mAttributes.length; i += 5) {
441 | if (attributeIndex == mAttributes[i + 1] &&
442 | (namespaceIndex == -1 || namespaceIndex == mAttributes[i])) {
443 | return i / 5;
444 | }
445 | }
446 |
447 | return -1;
448 | }
449 |
450 |
451 |
452 | /**
453 | * Gets the attribute offset for a given index.
454 | */
455 |
456 | private int getAttributeOffset(int index) {
457 | if (this.eventType == XmlPullParser.START_TAG) {
458 | int offset = index * 5;
459 | if (offset < this.mAttributes.length) {
460 | return offset;
461 | }
462 | throw new IndexOutOfBoundsException("Invalid attribute index (" + index + ").");
463 | }
464 | throw new IndexOutOfBoundsException("Current event is not START_TAG.");
465 | }
466 |
467 |
468 | /**
469 | * Resets the event information.
470 | */
471 |
472 | private final void resetEventInfo() {
473 | this.eventType = -1;
474 | this.mLineNumber = -1;
475 | this.mName = -1;
476 | this.mNamespaceUri = -1;
477 | this.mAttributes = null;
478 | this.mIdAttribute = -1;
479 | this.mClassAttribute = -1;
480 | this.mStyleAttribute = -1;
481 | }
482 |
483 | @Override
484 | public void close() {
485 | if (this.mOperational) {
486 | this.mOperational = false;
487 | this.mReader.close();
488 | this.mReader = null;
489 | this.stringBlock = null;
490 | this.mResourceIDs = null;
491 | this.mNamespaces.reset();
492 | resetEventInfo();
493 | }
494 | }
495 |
496 | @Override
497 | public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException {
498 | throw new XmlPullParserException("Entity replacement text not supported.");
499 | }
500 |
501 | @Override
502 | public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
503 | return getAttributeIntValue(index, defaultValue ? 1 : 0) != 0;
504 | }
505 |
506 | @Override
507 | public boolean getAttributeBooleanValue(String namespace, String attributeName, boolean defaultValue) {
508 | int attributeIndex = findAttribute(namespace, attributeName);
509 | return attributeIndex == -1 ? defaultValue : getAttributeBooleanValue(attributeIndex, defaultValue);
510 | }
511 |
512 | @Override
513 | public int getAttributeCount() {
514 | if (this.eventType != XmlPullParser.START_TAG) {
515 | return -1;
516 | }
517 | return this.mAttributes.length / 5;
518 | }
519 |
520 |
521 | @Override
522 | public float getAttributeFloatValue(int index, float defaultValue) {
523 | int attributeOffset = getAttributeOffset(index);
524 | int[] attributeArray = this.mAttributes;
525 | return attributeArray[attributeOffset + 3] == TypedValue.TYPE_FLOAT ? Float.intBitsToFloat(attributeArray[attributeOffset + 4]) : defaultValue;
526 | }
527 |
528 | @Override
529 | public float getAttributeFloatValue(String namespace, String attributeName, float defaultValue) {
530 | int attributeIndex = findAttribute(namespace, attributeName);
531 | return attributeIndex == -1 ? defaultValue : getAttributeFloatValue(attributeIndex, defaultValue);
532 | }
533 |
534 | @Override
535 | public int getAttributeIntValue(int index, int defaultValue) {
536 | int attributeOffset = getAttributeOffset(index);
537 | int[] attributeArray = this.mAttributes;
538 | int attributeType = attributeArray[attributeOffset + 3];
539 | return (attributeType < TypedValue.TYPE_FIRST_INT || attributeType > TypedValue.TYPE_LAST_INT) ? defaultValue : attributeArray[attributeOffset + 4];
540 | }
541 |
542 | @Override
543 | public int getAttributeIntValue(String namespace, String attributeName, int defaultValue) {
544 | int attributeIndex = findAttribute(namespace, attributeName);
545 | return attributeIndex == -1 ? defaultValue : getAttributeIntValue(attributeIndex, defaultValue);
546 | }
547 |
548 | @Override
549 | public int getAttributeListValue(int index, String[] options, int defaultValue) {
550 | int attributeOffset = getAttributeOffset(index);
551 | int[] attributeArray = this.mAttributes;
552 | int type = attributeArray[attributeOffset + 3];
553 | int value = attributeArray[attributeOffset + 4];
554 |
555 | if (type != TypedValue.TYPE_STRING) {
556 | return defaultValue;
557 | }
558 |
559 | String attributeValue = this.stringBlock.getString(value);
560 | if (options != null) {
561 | for (int i = 0; i < options.length; i++) {
562 | if (options[i].equals(attributeValue)) {
563 | return i;
564 | }
565 | }
566 | }
567 | return defaultValue;
568 | }
569 |
570 | ///////////////////////////////////
571 |
572 | @Override
573 | public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) {
574 | int attributeIndex = findAttribute(namespace, attribute);
575 | return attributeIndex == -1 ? defaultValue : getAttributeListValue(attributeIndex, options, defaultValue);
576 | }
577 |
578 |
579 | @Override
580 | public String getAttributeName(int index) {
581 | int nameIndex = this.mAttributes[getAttributeOffset(index) + 1];
582 | String attrName = stringBlock.getString(nameIndex);
583 | if(!attrName.isEmpty()){
584 | isChunkResourceIDs = false;
585 | return attrName;
586 | }else{
587 | isChunkResourceIDs = true;
588 | return Integer.toHexString(mResourceIDs[nameIndex]);
589 | }
590 |
591 | }
592 |
593 | @Override
594 | public String getAttributeNamespace(int index) {
595 | int namespaceIndex = this.mAttributes[getAttributeOffset(index)];
596 | return namespaceIndex == -1 ? "" : this.stringBlock.getString(namespaceIndex);
597 | }
598 |
599 | @Override
600 | public int getAttributeNameResource(int index) {
601 | int resourceNameIndex = this.mAttributes[getAttributeOffset(index) + 1];
602 | int[] resourceArray = this.mResourceIDs;
603 | if (resourceArray == null || resourceNameIndex < 0 || resourceNameIndex >= resourceArray.length) {
604 | return 0;
605 | }
606 | return resourceArray[resourceNameIndex];
607 | }
608 |
609 |
610 |
611 | @Override
612 | public String getAttributePrefix(int index) {
613 | int findPrefix = this.mNamespaces.findPrefix(this.mAttributes[getAttributeOffset(index)]);
614 | String prefix = (findPrefix == -1) ? "" : this.stringBlock.getString(findPrefix);
615 | switch (prefix) {
616 | case "axml_auto_00":
617 | return "android";
618 | default:
619 | return prefix;
620 | }
621 | }
622 |
623 | @Override
624 | public int getAttributeResourceValue(int index, int defaultValue) {
625 | int attributeOffset = getAttributeOffset(index);
626 | int[] resourceArray = this.mAttributes;
627 | return resourceArray[attributeOffset + 3] == 1 ? resourceArray[attributeOffset + 4] : defaultValue;
628 | }
629 |
630 | @Override
631 | public int getAttributeResourceValue(String namespace, String attributeName, int defaultValue) {
632 | int findAttribute = findAttribute(namespace, attributeName);
633 | return findAttribute == -1 ? defaultValue : getAttributeResourceValue(findAttribute, defaultValue);
634 | }
635 |
636 | @Override
637 | public String getAttributeType(int index) {
638 | return "CDATA";
639 | }
640 |
641 | @Override
642 | public int getAttributeUnsignedIntValue(int index, int defaultValue) {
643 | return getAttributeIntValue(index, defaultValue);
644 | }
645 |
646 | @Override
647 | public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) {
648 | int findAttribute = findAttribute(namespace, attribute);
649 | return findAttribute == -1 ? defaultValue : getAttributeUnsignedIntValue(findAttribute, defaultValue);
650 | }
651 |
652 | @Override
653 | public String getAttributeValue(int index) {
654 | int attributeOffset = getAttributeOffset(index);
655 | int[] attributeArray = this.mAttributes;
656 | if (attributeArray[attributeOffset + 3] == 3) {
657 | return this.stringBlock.getString(attributeArray[attributeOffset + 2]);
658 | }
659 | return "";
660 | }
661 |
662 | @Override
663 | public String getAttributeValue(String namespace, String attribute) {
664 | int findAttribute = findAttribute(namespace, attribute);
665 | if (findAttribute == -1) {
666 | return null;
667 | }
668 | return getAttributeValue(findAttribute);
669 | }
670 |
671 | @Override
672 | public int getAttributeValueData(int index) {
673 | return this.mAttributes[getAttributeOffset(index) + 4];
674 | }
675 |
676 | @Override
677 | public int getAttributeValueType(int index) {
678 | return this.mAttributes[getAttributeOffset(index) + 3];
679 | }
680 |
681 | @Override
682 | public String getClassAttribute() {
683 | int i = this.mClassAttribute;
684 | if (i == -1) {
685 | return null;
686 | }
687 | return this.stringBlock.getString(this.mAttributes[getAttributeOffset(i) + 2]);
688 | }
689 |
690 | @Override
691 | public int getColumnNumber() {
692 | return -1;
693 | }
694 |
695 | @Override
696 | public int getDepth() {
697 | return this.mNamespaces.getDepth() - 1;
698 | }
699 |
700 | @Override
701 | public int getEventType() {
702 | return this.eventType;
703 | }
704 |
705 | @Override
706 | public boolean getFeature(String value) {
707 | return false;
708 | }
709 |
710 | @Override
711 | public String getIdAttribute() {
712 | int i = this.mIdAttribute;
713 | if (i == -1) {
714 | return null;
715 | }
716 | return this.stringBlock.getString(this.mAttributes[getAttributeOffset(i) + 2]);
717 | }
718 |
719 | @Override
720 | public int getIdAttributeResourceValue(int index) {
721 | int resourceValueIndex = this.mIdAttribute;
722 | if (resourceValueIndex == -1) {
723 | return index;
724 | }
725 | int attributeOffset = getAttributeOffset(resourceValueIndex);
726 | int[] attributeArray = this.mAttributes;
727 | return attributeArray[attributeOffset + 3] != 1 ? index : attributeArray[attributeOffset + 4];
728 | }
729 |
730 | @Override
731 | public String getInputEncoding() {
732 | return null;
733 | }
734 |
735 | @Override
736 | public int getLineNumber() {
737 | return this.mLineNumber;
738 | }
739 |
740 | @Override
741 | public String getName() {
742 | int nameIndex = this.mName;
743 | if (nameIndex != -1) {
744 | int mEvent = this.eventType;
745 | if (mEvent == 2 || mEvent == 3) {
746 | return this.stringBlock.getString(nameIndex);
747 | }
748 | return null;
749 | }
750 | return null;
751 | }
752 |
753 | @Override
754 | public String getNamespace() {
755 | return this.stringBlock.getString(this.mNamespaceUri);
756 | }
757 |
758 | @Override
759 | public String getNamespace(String value) {
760 | throw new RuntimeException(E_NOT_SUPPORTED);
761 | }
762 |
763 | @Override
764 | public int getNamespaceCount(int index) {
765 | return this.mNamespaces.getAccumulatedCount(index);
766 | }
767 |
768 | @Override
769 | public String getNamespacePrefix(int i) {
770 | String namespacePrefix = this.stringBlock.getString(this.mNamespaces.getPrefix(i));
771 | switch (namespacePrefix) {
772 | case "axml_auto_00":
773 | return "android";
774 | default:
775 | return namespacePrefix;
776 | }
777 | }
778 |
779 | @Override
780 | public String getNamespaceUri(int pos) {
781 | String naespaceUri = this.stringBlock.getString(this.mNamespaces.getUri(pos));
782 | if(naespaceUri != null){
783 | return naespaceUri;
784 | } else {
785 | return NS_ANDROID;
786 | }
787 | }
788 |
789 | @Override
790 | public String getPositionDescription() {
791 | return "XML line #" + getLineNumber();
792 | }
793 |
794 | @Override
795 | public String getPrefix() {
796 | return this.stringBlock.getString(this.mNamespaces.findPrefix(this.mNamespaceUri));
797 | }
798 |
799 | public PrecededXmlToken getPrevious() {
800 | return this.precededXmlToken;
801 | }
802 |
803 | @Override
804 | public Object getProperty(String name) {
805 | return null;
806 | }
807 |
808 | final StringBlock getStrings() {
809 | return this.stringBlock;
810 | }
811 |
812 | @Override
813 | public int getStyleAttribute() {
814 | int i = this.mStyleAttribute;
815 | if (i == -1) {
816 | return 0;
817 | }
818 | return this.mAttributes[getAttributeOffset(i) + 4];
819 | }
820 |
821 | @Override
822 | public String getText() {
823 | if (this.eventType == XmlPullParser.TEXT) {
824 | return this.stringBlock.getString(this.mName);
825 | }
826 | return null;
827 | }
828 |
829 | @Override
830 | public char[] getTextCharacters(int[] holderForStartAndLength) {
831 | String text = getText();
832 | if (text == null) {
833 | holderForStartAndLength[0] = -1;
834 | holderForStartAndLength[1] = -1;
835 | return null;
836 | }
837 | holderForStartAndLength[0] = 0;
838 | holderForStartAndLength[1] = text.length();
839 | char[] characters = new char[text.length()];
840 | text.getChars(0, text.length(), characters, 0);
841 | return characters;
842 | }
843 |
844 | @Override
845 | public boolean isAttributeDefault(int index) {
846 | // No default attributes, returning false
847 | return false;
848 | }
849 |
850 | @Override
851 | public boolean isEmptyElementTag() {
852 | return false;
853 | }
854 |
855 | @Override
856 | public boolean isWhitespace() throws XmlPullParserException {
857 | if (this.eventType != XmlPullParser.TEXT) {
858 | throw new XmlPullParserException("Current event is not TEXT");
859 | }
860 | String text = getText();
861 | if (text == null) {
862 | return false;
863 | }
864 | for (int i = 0; i < text.length(); i++) {
865 | if (!Character.isWhitespace(text.charAt(i))) {
866 | return false;
867 | }
868 | }
869 | return true;
870 | }
871 |
872 | @Override
873 | public int next() throws XmlPullParserException, IOException {
874 | if (this.mReader != null) {
875 | try {
876 | if (this.stringBlock != null) {
877 | this.precededXmlToken = new PrecededXmlToken(getName(), getPrefix(), getEventType());
878 | }
879 | doNext();
880 | return this.eventType;
881 | } catch (IOException e) {
882 | close();
883 | throw e;
884 | }
885 | }
886 | throw new XmlPullParserException("Parser is not opened.", this, null);
887 | }
888 |
889 | @Override
890 | public int nextTag() throws XmlPullParserException, IOException {
891 | int eventType = next();
892 | if (eventType == XmlPullParser.TEXT && isWhitespace()) {
893 | eventType = next();
894 | }
895 | if (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_TAG) {
896 | throw new XmlPullParserException("Expected start or end tag.", this, null);
897 | }
898 | return eventType;
899 | }
900 |
901 | @Override
902 | public String nextText() throws XmlPullParserException, IOException {
903 | if (getEventType() != XmlPullParser.START_TAG) {
904 | throw new XmlPullParserException("Parser must be on START_TAG to read next text.", this, null);
905 | }
906 | int eventType = next();
907 | if (eventType == XmlPullParser.TEXT) {
908 | String result = getText();
909 | eventType = next();
910 | if (eventType != XmlPullParser.END_TAG) {
911 | throw new XmlPullParserException("Event TEXT must be immediately followed by END_TAG.", this, null);
912 | }
913 | return result;
914 | } else if (eventType == XmlPullParser.END_TAG) {
915 | return "";
916 | } else {
917 | throw new XmlPullParserException("Parser must be on START_TAG or TEXT to read text.", this, null);
918 | }
919 | }
920 |
921 | @Override
922 | public int nextToken() throws XmlPullParserException, IOException {
923 | return next();
924 | }
925 |
926 | public void open(InputStream inputStream) {
927 | close();
928 | if (inputStream != null) {
929 | this.mReader = new IntReader(inputStream, false);
930 | }
931 | }
932 |
933 | @Override
934 | public void require(int type, String namespace, String name) throws XmlPullParserException, IOException {
935 | if (type != getEventType() || ((namespace != null && !namespace.equals(getNamespace())) || (name != null && !name.equals(getName())))) {
936 | throw new XmlPullParserException(TYPES[type] + " is expected.", this, null);
937 | }
938 | }
939 |
940 | @Override
941 | public void setFeature(String name, boolean value) throws XmlPullParserException {
942 | throw new XmlPullParserException(E_NOT_SUPPORTED);
943 | }
944 |
945 | @Override
946 | public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
947 | throw new XmlPullParserException(E_NOT_SUPPORTED);
948 | }
949 |
950 | @Override
951 | public void setInput(Reader reader) throws XmlPullParserException {
952 | throw new XmlPullParserException(E_NOT_SUPPORTED);
953 | }
954 |
955 | @Override
956 | public void setProperty(String name, Object value) throws XmlPullParserException {
957 | throw new XmlPullParserException(E_NOT_SUPPORTED);
958 | }
959 | }
960 |
--------------------------------------------------------------------------------
/library/src/main/java/mt/modder/hub/axml/AXMLPrinter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * AxmlPrinter - An Advanced Axml Printer available with proper xml style/format feature
3 | * Copyright 2025, developer-krushna
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are
7 | * met:
8 | *
9 | * * Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above
12 | * copyright notice, this list of conditions and the following disclaimer
13 | * in the documentation and/or other materials provided with the
14 | * distribution.
15 | * * Neither the name of developer-krushna nor the names of its
16 | * contributors may be used to endorse or promote products derived from
17 | * this software without specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 |
32 | * Please contact Krushna by email mt.modder.hub@gmail.com if you need
33 | * additional information or have any questions
34 | */
35 |
36 | package mt.modder.hub.axml;
37 |
38 | import java.io.*;
39 | import java.util.*;
40 | import mt.modder.hub.axmlTools.*;
41 | import mt.modder.hub.axmlTools.utils.*;
42 | import org.xmlpull.v1.*;
43 | import java.util.regex.*;
44 | import mt.modder.hub.axmlTools.arsc.*;
45 | import java.nio.charset.*;
46 |
47 | /**
48 | * AXMLPrinter
49 | * A tool for decompiling Android binary XML files into human-readable XML format.
50 | * Author: @developer-krushna
51 | * Thanks to ChatGPT for adding comments. :)
52 | */
53 | public final class AXMLPrinter {
54 |
55 | private static final String COPYRIGHT = "AXMLPrinter\nCopyright (C) developer-krushna [https://github.com/developer-krushna/](krushnachandramaharna57@gmail.com)\nThis project is distributed under the Apache License v2.0 license";
56 |
57 | // Constants for conversion factors and unit strings
58 | private static final float MANTISSA_MULT =
59 | 1.0f / (1 << TypedValue.COMPLEX_MANTISSA_SHIFT);
60 |
61 | private static final float[] RADIX_MULTS = new float[]{
62 | 1.0f * MANTISSA_MULT /* 0.00390625f */,
63 | 1.0f / (1 << 7) * MANTISSA_MULT /* 3.051758E-5f */,
64 | 1.0f / (1 << 15) * MANTISSA_MULT /* 1.192093E-7f */,
65 | 1.0f / (1 << 23) * MANTISSA_MULT /* 4.656613E-10f */
66 | };
67 | private static final String[] DIMENSION_UNIT_STRS = new String[]{
68 | "px", "dp", "sp", "pt", "in", "mm"
69 | };
70 | private static final String[] FRACTION_UNIT_STRS = new String[]{
71 | "%", "%p"
72 | };
73 |
74 |
75 | private boolean enableId2Name = false;// Enable ID to name translation
76 | private boolean enableAttributeConversion = false; // Enable attribute value conversion
77 |
78 | private ResourceIdExtractor systemResourceFile = new ResourceIdExtractor(); // Handles system resource IDs
79 | private ResourceIdExtractor customResourceFile = new ResourceIdExtractor();// Handles custom resource IDs
80 | public String CUSTOM_ATTRIBUTE_TAG = "_Custom";
81 | public String SYSTEM_ATTRIBUTE_TAG = "_Systumm"; //😂
82 | public boolean isCustomResourceFileExist = false; // Indicates if a custom resource file exists
83 | public boolean isResObfuscationCheckDone = false;
84 | public boolean isResObfuscated = false;
85 |
86 | private NamespaceChecker namespaceChecker = new NamespaceChecker();
87 | public String ANDROID_PREFIX = "android";
88 | public String APP_PREFIX = "app";
89 | public String AUTO_NAMESPACE = "http://schemas.android.com/apk/res-auto";
90 | public String ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";
91 | public boolean isExistAndroidNamespace = false;
92 | public boolean isAuto_NameSpaceExists = false;
93 | public String RandomResAutoPrefix_Name = null;
94 |
95 | private Map permissionInfoMap;
96 | private boolean isPermissionInfoLoaded = false;
97 | private String PERMISSION_TAG = "uses-permission";
98 | private boolean isExtractPermissionDescription = false;
99 |
100 |
101 |
102 | /**
103 | * Enables or disables ID-to-name conversion.
104 | * This allows translating hex IDs in XML to their corresponding resource names.
105 | *
106 | * @param enable Flag to enable or disable this feature.
107 | */
108 | public void setEnableID2Name(boolean enable) {
109 | enableId2Name = enable;
110 | if (enable) {
111 | try {
112 | // Load System resource file
113 | loadSystemResources();
114 | } catch (Exception e) {
115 | systemResourceFile = null;
116 | System.err.println("Failed to load system resources.");
117 | }
118 | }
119 | }
120 |
121 |
122 | /**
123 | * Enables or disables attribute value translation.
124 | * This feature interprets attribute values and converts them to human-readable formats.
125 | *
126 | * @param enable Flag to enable or disable this feature.
127 | */
128 | public void setAttrValueTranslation(boolean enable) {
129 | enableAttributeConversion = enable;
130 | }
131 |
132 | /**
133 | * Enables or disables permission description extraction from XML.
134 | *
135 | * @param enable Flag to enable or disable this feature.
136 | */
137 | public void setExtractPermissionDescription(boolean isExtract) {
138 | isExtractPermissionDescription = isExtract;
139 | }
140 |
141 | /**
142 | * Loads system resources from the bundled resources.arsc file.
143 | *
144 | * @throws Exception if the resource file cannot be loaded.
145 | */
146 | private void loadSystemResources() throws Exception {
147 | try (InputStream arscStream = AXMLPrinter.class.getResourceAsStream("/assets/resources.arsc")) {
148 | systemResourceFile.loadArscData(arscStream);
149 | }
150 | }
151 |
152 | /**
153 | * Reads a binary XML file and converts it into a human-readable XML string.
154 | *
155 | * @param path Path to the binary XML file.
156 | * @return The converted XML content as a string.
157 | * @throws Exception if an error occurs while reading the file.
158 | */
159 | public String readFromFile(String path) throws Exception {
160 | FileInputStream fis = new FileInputStream(path);
161 | byte[] byteArray = new byte[fis.available()];
162 | fis.read(byteArray);
163 | fis.close();
164 |
165 | if (enableId2Name) {
166 | File resourceFile = new File(path).getParentFile().toPath().resolve("resources.arsc").toFile();
167 | if (resourceFile.exists()) {
168 | try (InputStream arscStream = new FileInputStream(resourceFile)) {
169 | customResourceFile.loadArscData(arscStream);
170 | isCustomResourceFileExist = true;
171 | }
172 | }
173 | }
174 |
175 | // Convert the binary XML to readable XML
176 | return convertXml(byteArray);
177 | }
178 |
179 | // for MT Manager
180 | public void readProcessRes(String path) throws Exception {
181 | if (!path.endsWith(".xml")) {
182 | return;
183 | }
184 | File file = new File(path);
185 | String resourceFile = file.getParent() + "/resources.arsc";
186 | System.out.println(resourceFile);
187 | if (new File(resourceFile).exists()) {
188 | try {
189 | try (InputStream arscStream = new FileInputStream(resourceFile)) {
190 | customResourceFile.loadArscData(arscStream);
191 | }
192 | isCustomResourceFileExist = true;
193 | } catch (Exception e) {
194 | System.out.println(e);
195 | isCustomResourceFileExist = false;
196 | }
197 | }
198 | }
199 |
200 |
201 | /**
202 | * Converts a binary XML byte array into a readable XML string.
203 | *
204 | * @param byteArray The binary XML byte array.
205 | * @return The converted XML content as a string.
206 | */
207 | public String convertXml(byte[] byteArray) {
208 | System.out.println(COPYRIGHT);
209 | if (!enableId2Name) {
210 | try {
211 | loadSystemResources();
212 | } catch (Exception e) {
213 | systemResourceFile = null;
214 | }
215 | }
216 | try {
217 | // Initialize the XML parser with the byte array input
218 | AXmlResourceParser xmlParser = new AXmlResourceParser();
219 | xmlParser.open(new ByteArrayInputStream(byteArray));
220 | StringBuilder indentation = new StringBuilder();
221 | StringBuilder xmlContent = new StringBuilder();
222 | boolean isManuallyAddedAndroidNamespace = false;
223 | while (true) {
224 | int eventType = xmlParser.next();
225 | int attributeCount = xmlParser.getAttributeCount(); // count attributes
226 | if (eventType == XmlPullParser.END_DOCUMENT) {
227 | // End of document
228 | String result = xmlContent.toString();
229 | xmlParser.close();
230 | return result;
231 | }
232 | switch (eventType) {
233 | case XmlPullParser.START_DOCUMENT:
234 | // Append XML declaration at the start of the document
235 | xmlContent.append("\n");
236 | xmlContent.append("\n");
237 | break;
238 |
239 | case XmlPullParser.START_TAG:
240 | // Handle the start of a new XML tag
241 | if (xmlParser.getPrevious().type == XmlPullParser.START_TAG) {
242 | xmlContent.append(">\n");
243 | }
244 |
245 | String prefix = xmlParser.getName();
246 |
247 | //check if the user-permission prefix found (We know its available only in AndroidManifest.xml)
248 | if (isExtractPermissionDescription) {
249 | if (prefix.contains(PERMISSION_TAG)) {
250 | if (!isPermissionInfoLoaded) {
251 | //load permissionInfo one time only
252 | permissionInfoMap = loadPermissionsInfo();
253 | isPermissionInfoLoaded = true;
254 | }
255 | //extract permission description from corresponding permissionName
256 | if (attributeCount > 0) {
257 | for (int i = 0; i < attributeCount; i++) {
258 | String permissionName = xmlParser.getAttributeValue(i);
259 | String description = permissionInfoMap.get(permissionName);
260 | if (description != null) {
261 | // Print permission description
262 | xmlContent.append(indentation).append("\n");
263 | }
264 | }
265 | }
266 | }
267 | }
268 |
269 | xmlContent.append(String.format("%s<%s%s",
270 | indentation,
271 | getMainNodeNamespacePrefix(xmlParser.getPrefix()),
272 | prefix));
273 | indentation.append(" ");
274 |
275 | // Handle namespaces
276 | int depth = xmlParser.getDepth();
277 | int namespaceStart = xmlParser.getNamespaceCount(depth - 1);
278 | int namespaceEnd = xmlParser.getNamespaceCount(depth);
279 |
280 | //xmlContent.append(indentation).append("");
281 | for (int i = namespaceStart; i < namespaceEnd; i++) {
282 | String namespaceFormat = (i == namespaceStart) ? "%sxmlns:%s=\"%s\"" : "\n%sxmlns:%s=\"%s\"";
283 | String nameSpacePrefix = xmlParser.getNamespacePrefix(i);
284 | String nameSpaceUri = xmlParser.getNamespaceUri(i);
285 | if(nameSpaceUri.equals(AUTO_NAMESPACE)){
286 | isAuto_NameSpaceExists = true;
287 | }
288 | if(nameSpaceUri.equals(AUTO_NAMESPACE) && !nameSpacePrefix.equals("app")){
289 | RandomResAutoPrefix_Name = nameSpacePrefix;
290 | nameSpacePrefix = "app";
291 | }
292 |
293 | xmlContent.append(String.format(namespaceFormat,
294 | (i == namespaceStart) ? " " : indentation,
295 | nameSpacePrefix,
296 | nameSpaceUri));
297 | isExistAndroidNamespace = true; // make it true as it completed the above task
298 |
299 | }
300 |
301 | // if NamespaceUri failed to extract then add this manually
302 | if (!isExistAndroidNamespace && !isManuallyAddedAndroidNamespace) {
303 | String namespaceFormat = "%sxmlns:%s=\"%s\"";
304 | // Search android namespace if exist then add
305 | if(containsSequence(byteArray, ANDROID_NAMESPACE.getBytes(StandardCharsets.UTF_8)) || prefix.equals("manifest")){
306 | xmlContent.append(String.format(namespaceFormat, " ", ANDROID_PREFIX, ANDROID_NAMESPACE));
307 | isManuallyAddedAndroidNamespace = true;
308 | }
309 | // Search auto namespace if exist then add
310 | if(containsSequence(byteArray, AUTO_NAMESPACE.getBytes(StandardCharsets.UTF_8))){
311 | xmlContent.append("\n");
312 | xmlContent.append(String.format(namespaceFormat, indentation, APP_PREFIX, AUTO_NAMESPACE));
313 | isAuto_NameSpaceExists = true;
314 | }
315 | isExistAndroidNamespace = false; // false because we add this mannualy
316 | }
317 |
318 | // Handle attributes
319 | if (attributeCount > 0) {
320 | if (attributeCount == 1) {
321 | xmlContent.append("");
322 | for (int i = 0; i < attributeCount; i++) {
323 | // Skip attributes with a dot (.)
324 | if (xmlParser.getAttributeName(i).contains(".")) {
325 | continue; // Skip this attribute if its name contains a dot
326 | }
327 |
328 | String attributeFormat = (i == attributeCount - 1) ? "%s%s%s=\"%s\"" : "%s%s%s=\"%s\"\n";
329 | String attributeName = getAttributeName(xmlParser, i);
330 | String attributeValue = getAttributeValue(xmlParser, i);
331 | // Final Addition of namespace , attribute along with its corresponding value
332 | int valueSize = attributeValue.codePointCount(0, attributeValue.length());
333 | if(valueSize <= 14 || prefix.equals(PERMISSION_TAG)){
334 | // Indention is not needed because it has 1 attribute only its main node
335 | xmlContent.append(String.format(attributeFormat,
336 | " ",
337 | getAttrNamespacePrefix(xmlParser, i, attributeName),
338 | attributeName.replaceAll(CUSTOM_ATTRIBUTE_TAG, "").replaceAll(SYSTEM_ATTRIBUTE_TAG, ""),
339 | attributeValue));
340 | } else {
341 | xmlContent.append('\n');
342 | xmlContent.append(String.format(attributeFormat,
343 | indentation,
344 | getAttrNamespacePrefix(xmlParser, i, attributeName),
345 | attributeName.replaceAll(CUSTOM_ATTRIBUTE_TAG, "").replaceAll(SYSTEM_ATTRIBUTE_TAG, ""),
346 | attributeValue));
347 | }
348 | }
349 | } else {
350 | xmlContent.append('\n');
351 | for (int i = 0; i < attributeCount; i++) {
352 | // Skip attributes with a dot (.)
353 | if (xmlParser.getAttributeName(i).contains(".")) {
354 | continue; // Skip this attribute if its name contains a dot
355 | }
356 |
357 | String attributeFormat = (i == attributeCount - 1) ? "%s%s%s=\"%s\"" : "%s%s%s=\"%s\"\n";
358 | String attributeName = getAttributeName(xmlParser, i); //Attribute name
359 | // Final Addition of namespace , attribute along with its corresponding value
360 | // Indention is needed because it 2 or more attributes
361 |
362 | xmlContent.append(String.format(attributeFormat,
363 | indentation,
364 | getAttrNamespacePrefix(xmlParser, i, attributeName),
365 | attributeName.replaceAll(CUSTOM_ATTRIBUTE_TAG, "").replaceAll(SYSTEM_ATTRIBUTE_TAG, ""),
366 | getAttributeValue(xmlParser, i)));
367 |
368 | }
369 | }
370 | }
371 |
372 | break;
373 |
374 | case XmlPullParser.END_TAG:
375 | // Handle the end of an XML tag
376 | indentation.setLength(indentation.length() - " ".length());
377 | if (!isEndOfPrecededXmlTag(xmlParser, xmlParser.getPrevious())) {
378 |
379 | xmlContent.append(String.format("%s%s%s>\n",
380 | indentation,
381 | getMainNodeNamespacePrefix(xmlParser.getPrefix()),
382 | xmlParser.getName()));
383 | } else {
384 | xmlContent.append(" />\n");
385 | }
386 | break;
387 |
388 | case XmlPullParser.TEXT:
389 | // Handle text within an XML tag
390 | if (xmlParser.getPrevious().type == XmlPullParser.START_TAG) {
391 | xmlContent.append(">\n");
392 | }
393 | xmlContent.append(String.format("%s%s\n",
394 | indentation,
395 | xmlParser.getText()));
396 | break;
397 | }
398 | }
399 | } catch (Exception e) {
400 | // Handle exceptions and return the stack trace
401 | StringWriter sw = new StringWriter();
402 | PrintWriter pw = new PrintWriter(sw);
403 | e.printStackTrace(pw);
404 | String exceptionDetails = sw.toString();
405 | return "----StackTrace----\n" + exceptionDetails;
406 | }
407 | }
408 |
409 | /**
410 | * Retrieves the value of an attribute based on its type.
411 | * This method processes attribute types like strings, references, dimensions, fractions, etc.,
412 | * and returns their human-readable representation.
413 | *
414 | * @param xmlParser The XML parser instance.
415 | * @param index The index of the attribute to retrieve.
416 | * @return A formatted string representing the attribute value.
417 | */
418 | private String getAttributeValue(AXmlResourceParser xmlParser, int index) {
419 |
420 | String attributeName = getAttributeName(xmlParser, index).replaceAll(CUSTOM_ATTRIBUTE_TAG, "").replaceAll(SYSTEM_ATTRIBUTE_TAG, "");
421 | // String attributeName = getAttributeName(xmlParser, index);
422 |
423 | int attributeValueType = xmlParser.getAttributeValueType(index);
424 |
425 | int attributeValueData = xmlParser.getAttributeValueData(index);
426 |
427 | switch (attributeValueType) {
428 | case TypedValue.TYPE_STRING /* 3 */:
429 | // String value
430 |
431 | String stringValue = xmlParser.getAttributeValue(index);
432 | // Preserve newlines as \n for XML
433 | return stringValue.replace("\n", "\\n");
434 |
435 | case TypedValue.TYPE_ATTRIBUTE /* 2 */:
436 | // Resource ID
437 | if (enableId2Name) {
438 | return "?" + extractResourecID(attributeValueData);
439 | } else {
440 | return "?" + formatToHex(attributeValueData);
441 | }
442 |
443 | case TypedValue.TYPE_REFERENCE /* 1 */:
444 | // Reference
445 | if (enableId2Name) {
446 | return "@" + extractResourecID(attributeValueData);
447 | } else {
448 | return "@" + formatToHex(attributeValueData);
449 | }
450 |
451 | case TypedValue.TYPE_FLOAT /* 4 */:
452 | // Float value
453 | return Float.toString(Float.intBitsToFloat(attributeValueData));
454 |
455 | case TypedValue.TYPE_INT_HEX /* 17 */:
456 | // Hex integer value or flag values
457 | if (enableAttributeConversion) {
458 | String decodedValue = AttributesExtractor.getInstance().decode(attributeName, attributeValueData);
459 | if (decodedValue != null && !decodedValue.isEmpty()) {
460 | return decodedValue; // Return the decoded value if found
461 | } else {
462 | return "0x" + Integer.toHexString(attributeValueData);
463 | }
464 | } else {
465 | return "0x" + Integer.toHexString(attributeValueData);
466 | }
467 |
468 | case TypedValue.TYPE_INT_BOOLEAN /* 18 */:
469 | // Boolean value
470 | return attributeValueData != 0 ? "true" : "false";
471 |
472 | case TypedValue.TYPE_DIMENSION /* 5 */:
473 | // Dimension value
474 | return formatDimension(attributeValueData);
475 |
476 | case TypedValue.TYPE_FRACTION /* 6 */:
477 | // Fraction value
478 | return formatFraction(attributeValueData);
479 |
480 | default:
481 | // Handle enum or flag values and other cases
482 | if (enableAttributeConversion) {
483 | String decodedValue = AttributesExtractor.getInstance().decode(attributeName, attributeValueData);
484 | if (decodedValue != null) {
485 | return decodedValue; // Return the decoded value if found
486 | }
487 | }
488 | // For unhandled types or cases
489 | String result;
490 | if (attributeValueType >= TypedValue.TYPE_FIRST_COLOR_INT && attributeValueType <= TypedValue.TYPE_LAST_COLOR_INT) {
491 | // Condition 1: attributeValueType is a color type (0x1c-0x1f)
492 | result = String.format("#%08x", attributeValueData);
493 | } else if (attributeValueType >= 0x10 && attributeValueType <= TypedValue.TYPE_LAST_INT) {
494 | // Condition 2: attributeValueType is in the general integer range but not the color range(0x10-0x1f, but not 0x1c-0x1f)
495 | result = String.valueOf(attributeValueData);
496 | } else {
497 | // Condition 3: All other cases,
498 | result = String.format("<0x%X, type 0x%02X>", attributeValueData, attributeValueType);
499 | }
500 |
501 | return result;
502 | }
503 | }
504 |
505 |
506 |
507 |
508 | // Checks if the current XML tag is the end of the previous tag
509 | private boolean isEndOfPrecededXmlTag(AXmlResourceParser xmlParser, AXmlResourceParser.PrecededXmlToken precededXmlToken) {
510 | return precededXmlToken.type == XmlPullParser.START_TAG &&
511 | xmlParser.getEventType() == XmlPullParser.END_TAG &&
512 | xmlParser.getName().equals(precededXmlToken.name) &&
513 | ((precededXmlToken.namespace == null && xmlParser.getPrefix() == null) ||
514 | (precededXmlToken.namespace != null && xmlParser.getPrefix() != null && xmlParser.getPrefix().equals(precededXmlToken.namespace)));
515 | }
516 |
517 | // Retrieves the main node namespace prefix if it exists
518 | private String getMainNodeNamespacePrefix(String prefix) {
519 | return (prefix == null || prefix.length() == 0) ? "" : prefix + ":";
520 | }
521 |
522 | // Retrieves the attribute namespace prefix if it exists
523 | private String getAttrNamespacePrefix(AXmlResourceParser xmlParser, int position, String attributeName) {
524 | String namespace = xmlParser.getAttributePrefix(position);
525 | int attributeNameResource = xmlParser.getAttributeNameResource(position);
526 |
527 | if (attributeName.contains(CUSTOM_ATTRIBUTE_TAG)) {
528 | // Check if auto namespace exists or not
529 | if(isAuto_NameSpaceExists){
530 | return APP_PREFIX + ":";
531 | }
532 | return "";
533 | // check if any unknown attributes are found and it will start from "id"
534 | } else if (isUnknownAttribute(attributeName)) {
535 | return "";
536 | } else if (attributeName.contains(SYSTEM_ATTRIBUTE_TAG)) {
537 | return ANDROID_PREFIX + ":";
538 | } else if (namespace.isEmpty()) {
539 | if (xmlParser.isChunkResourceIDs || !isExistAndroidNamespace) {
540 | if (namespaceChecker.isAttributeExist(attributeName)) {
541 | return "";
542 | } else {
543 | if(attributeNameResource == 0){
544 | return "";
545 | }
546 | if(checkIfCustomAttribute(attributeNameResource)){
547 | return APP_PREFIX + ":";
548 | } else {
549 | return ANDROID_PREFIX + ":";
550 | }
551 | }
552 | }
553 | return "";
554 | }
555 | //check if the is res-auto nameSpaceUri's prefix name is random
556 | if(RandomResAutoPrefix_Name != null && namespace.equals(RandomResAutoPrefix_Name)){
557 | return "app" + ":";
558 | }
559 | return namespace + ":";
560 | }
561 |
562 | /**
563 | * Extracts the attribute name dynamically based on system or custom resources.
564 | *
565 | * @param xmlParser The XML parser instance.
566 | * @param index The index of the attribute to retrieve.
567 | * @return The attribute name as a string.
568 | */
569 | public String getAttributeName(AXmlResourceParser xmlParser, int index) {
570 | String attributeName = xmlParser.getAttributeName(index);
571 | int attributeNameResource = xmlParser.getAttributeNameResource(index);
572 |
573 | //check if the attributes are encrypted with attribute hex id
574 | if (xmlParser.isChunkResourceIDs || isUnknownAttribute(attributeName)) {
575 | try {
576 | String extractedName = getAttributeNameFromResources(attributeName.replace("id", ""));
577 | return extractedName != null ? extractedName.replaceAll("attr/", "") : getFallbackAttributeName(attributeName);
578 | } catch (Exception e) {
579 | return getFallbackAttributeName(attributeName);
580 | }
581 | } else {
582 | // Again ceck if custom res file is exist or not so that we can even extract more precise attribute data
583 | if(isCustomResourceFileExist){
584 | try {
585 | String extractedName = getAttributeNameFromCustomRes(Integer.toHexString(attributeNameResource), attributeName);
586 | return extractedName != null ? extractedName.replaceAll("attr/", "") : attributeName;
587 | } catch (Exception e) {
588 | return attributeName;
589 | }
590 | }
591 | return attributeName;
592 | }
593 | }
594 |
595 |
596 | private String getAttributeNameFromCustomRes(String attribute_hexId, String attributeName) throws Exception {
597 | String nameForHexId;
598 | if (this.isCustomResourceFileExist && (nameForHexId = customResourceFile.getNameForHexId(attribute_hexId)) != null) {
599 | if(!isResObfuscationCheckDone){
600 | String nextIdNameToCheckObfuscation = customResourceFile.getNameForHexId(generateNextHexId(attribute_hexId));
601 | if(nextIdNameToCheckObfuscation != null){
602 | if(nameForHexId.equals(nextIdNameToCheckObfuscation)){
603 | isResObfuscationCheckDone = true;
604 | isResObfuscated = true;
605 | return attributeName; //normal attribute
606 | } else{
607 | return CUSTOM_ATTRIBUTE_TAG + nameForHexId;
608 | }
609 | } else {
610 | return attributeName;
611 | }
612 | }
613 |
614 | //finally check of res file is obfuscated or not
615 | if(!isResObfuscated){
616 | return CUSTOM_ATTRIBUTE_TAG + nameForHexId;
617 | } else {
618 | return attributeName;
619 | }
620 | }
621 | return attributeName;
622 | }
623 |
624 |
625 | // Get attribute name from either system resource file or custom resource file
626 | private String getAttributeNameFromResources(String attribute_hexId) throws Exception {
627 | String systemAttribute = systemResourceFile.getNameForHexId("0" + attribute_hexId);
628 | String extractedAttributeName = null;
629 | String nameForHexId;
630 | if (systemAttribute != null) {
631 | extractedAttributeName = SYSTEM_ATTRIBUTE_TAG + systemAttribute;
632 | }
633 | // Process custom resource file if exist and also check if the system resource file don't have target hex id
634 | if (this.isCustomResourceFileExist && extractedAttributeName == null && (nameForHexId = customResourceFile.getNameForHexId(attribute_hexId)) != null) {
635 | //check if res file is obfuscated or not
636 | if(!isResObfuscationCheckDone){
637 | String nextIdNameToCheckObfuscation = customResourceFile.getNameForHexId(generateNextHexId(attribute_hexId));
638 | if(nextIdNameToCheckObfuscation != null){
639 | if(nameForHexId.equals(nextIdNameToCheckObfuscation)){
640 | isResObfuscationCheckDone = true;
641 | isResObfuscated = true;
642 | return "id" + attribute_hexId;
643 | } else{
644 | return CUSTOM_ATTRIBUTE_TAG + nameForHexId;
645 | }
646 | } else {
647 | return "id" + attribute_hexId;
648 | }
649 | }
650 |
651 | //finally check of res file is obfuscated or not
652 | if(!isResObfuscated){
653 | return CUSTOM_ATTRIBUTE_TAG + nameForHexId;
654 | } else {
655 | return "id" + attribute_hexId;
656 | }
657 |
658 | }
659 |
660 | return extractedAttributeName;
661 | }
662 |
663 |
664 | //check the without namespace based specific attributes if matched
665 | private String getFallbackAttributeName(String attributeName) {
666 | if (namespaceChecker.isAttributeExist(attributeName)) {
667 | return attributeName;
668 | } else if (attributeName != null && attributeName.startsWith("id")) {
669 | return attributeName;
670 | } else {
671 | return "id" + attributeName;
672 | }
673 | }
674 |
675 | /**
676 | * Determines if an attribute name matches an unknown or obfuscated pattern.
677 | *
678 | * @param attributeName The name of the attribute to check.
679 | * @return True if the attribute is unknown or obfuscated, false otherwise.
680 | */
681 | public boolean isUnknownAttribute(String attributeName) {
682 | return attributeName.matches("^id\\d[a-z0-9]*$");
683 | }
684 |
685 | /**
686 | * Extracts the resource name based on its hexadecimal ID.
687 | * If the resource name cannot be found, the hexadecimal ID is returned.
688 | *
689 | * @param resourceId The resource ID to extract.
690 | * @return The resource name or its hexadecimal representation.
691 | */
692 | public String extractResourecID(int resourceId) {
693 | String resHexId = formatToHex(resourceId);
694 | String systemId2Name = null;
695 | String customResId2Name = null;
696 | try {
697 | if (enableId2Name) {
698 | // Load system resource file
699 | systemId2Name = systemResourceFile.getNameForHexId(resHexId);
700 |
701 | // If System don't have the id then lets move to custom resource file
702 | if (isCustomResourceFileExist && systemId2Name == null) {
703 | customResId2Name = customResourceFile.getNameForHexId(resHexId);
704 | }
705 | // If id name is extracted from system resource then add "android:" before the attribute name
706 | if (systemId2Name != null) {
707 | return ANDROID_PREFIX + ":" + systemId2Name;
708 | }
709 | // Check if the custom id2name is not null .. and return the entry name without name space
710 | if (customResId2Name != null) {
711 | //check if res file is obfuscated or not
712 | if(!isResObfuscationCheckDone){
713 | String nextIdNameToCheckObfuscation = customResourceFile.getNameForHexId(generateNextHexId(resHexId));
714 | if(nextIdNameToCheckObfuscation != null){
715 | if(customResId2Name.equals(nextIdNameToCheckObfuscation)){
716 | isResObfuscationCheckDone = true;
717 | isResObfuscated = true;
718 | return resHexId;
719 | } else{
720 | return customResId2Name;
721 | }
722 | } else {
723 | return resHexId;
724 | }
725 | }
726 |
727 | //finally check of res file is obfuscated or not
728 | if(!isResObfuscated){
729 | return customResId2Name;
730 | } else {
731 | return resHexId;
732 | }
733 | }
734 | return resHexId;
735 | } else {
736 | return resHexId;
737 | }
738 | } catch (Exception e) {
739 | return resHexId;
740 | }
741 | }
742 |
743 |
744 | //Load manifest permission description
745 | private Map loadPermissionsInfo() throws Exception {
746 | Map map = new HashMap<>();
747 | InputStream is = AXMLPrinter.class.getResourceAsStream("/assets/permissions_info_en.txt");
748 | InputStreamReader reader = new InputStreamReader(is);
749 | BufferedReader bufferedReader = new BufferedReader(reader);
750 | String permission = null;
751 | String description = null;
752 | String line;
753 | while ((line = bufferedReader.readLine()) != null) {
754 | line = line.trim();
755 | // If the line is empty, we can skip it
756 | if (line.isEmpty()) {
757 | continue;
758 | }
759 | // match the permission and description with regex
760 | if (line.matches("^[a-zA-Z0-9._]+$")) {
761 | // If there's an existing permission and description, store it in the map
762 | if (permission != null && description != null) {
763 | map.put(permission, description);
764 | }
765 | // Now the new permission starts
766 | permission = line;
767 | description = null;
768 | } else {
769 | // If the line is a description, append it to the current description
770 | if (description != null) {
771 | description += " " + line;
772 | } else {
773 | description = line;
774 | }
775 | }
776 | }
777 | // Add the last permission entry to the map
778 | if (permission != null && description != null) {
779 | map.put(permission, description);
780 | }
781 |
782 | return map;
783 | }
784 |
785 | /**
786 | * Converts an integer to a hexadecimal string format.
787 | *
788 | * @param value The integer value to convert.
789 | * @return The formatted hexadecimal string.
790 | */
791 | public String formatToHex(int i) {
792 | return String.format("%08x", Integer.valueOf(i));
793 | }
794 |
795 | /* This method helped us to search and detect if a specific text is exists or not*/
796 | private boolean containsSequence(byte[] source, byte[] sequence) {
797 | if (sequence.length == 0 || source.length == 0 || sequence.length > source.length) {
798 | return false;
799 | }
800 | for (int i = 0; i <= source.length - sequence.length; i++) {
801 | boolean found = true;
802 | for (int j = 0; j < sequence.length; j++) {
803 | if (source[i + j] != sequence[j]) {
804 | found = false;
805 | break;
806 | }
807 | }
808 | if (found) {
809 | return true;
810 | }
811 | }
812 | return false;
813 | }
814 |
815 | /* check if any custom attributes are used for xml ui,
816 | * Generally custom attribute dec ids are 10 digits unlike system which have 8 digits
817 | * And also custom atributes dec ids are started with 2 unlike system which generally 1
818 | * This can help us wheather it should use app: namespace or not
819 | */
820 | public boolean checkIfCustomAttribute(int number) {
821 | String numberStr = String.valueOf(number);
822 | int digitCount = numberStr.length();
823 | char firstDigitChar = numberStr.charAt(0);
824 | int firstDigit = Character.getNumericValue(firstDigitChar);
825 | if (digitCount == 10 || firstDigit == 2) {
826 | return true;
827 | }
828 | return false;
829 | }
830 | /**
831 | * Formats a dimension value (e.g., pixels, dp, sp).
832 | *
833 | * @param attributeValueData The raw attribute value data (as an int).
834 | * @return A string representation of the dimension, including the unit.
835 | */
836 | private String formatDimension(int attributeValueData) {
837 | // Convert the attribute data to a float value.
838 | float floatValue = TypedValue.complexToFloat(attributeValueData);
839 |
840 | // Extract the unit from the attribute value data using a bitwise AND to get the unit index (0-15)
841 | String unit = DIMENSION_UNIT_STRS[attributeValueData & 15];
842 |
843 | // Format the float value and append the unit to create the final output string.
844 | return formatFloat(floatValue) + unit;
845 | }
846 |
847 |
848 | /**
849 | * Formats a fraction value (e.g., percentage).
850 | *
851 | * @param attributeValueData The raw attribute value data (as an int).
852 | * @return A string representation of the fraction, including the unit.
853 | */
854 | private String formatFraction(int attributeValueData) {
855 | // Convert the attribute data to a float value, and then converts it to percentage value by multiplying with 100
856 | float floatValue = TypedValue.complexToFloat(attributeValueData) * 100.0f;
857 |
858 | // Extract the unit from the attribute value data using a bitwise AND to get the unit index (0-15).
859 | String unit = FRACTION_UNIT_STRS[attributeValueData & 15];
860 |
861 | // Format the float value and append the unit to create the final output string
862 | return formatFloat(floatValue) + unit;
863 | }
864 |
865 | /**
866 | * Formats a float value to a string representation.
867 | *
868 | * @param floatValue The float value to format.
869 | * @return A string representation of the float, either as an integer if possible or with one decimal place.
870 | */
871 | private String formatFloat(float floatValue) {
872 | // Check if the float value is equivalent to an integer value
873 | if (floatValue == (int) floatValue) {
874 | // If it's an integer, return its String value without decimal places
875 | return String.valueOf((int) floatValue);
876 | } else {
877 | // If it has a decimal part, format it to one decimal place
878 | return String.format(Locale.US, "%.1f", floatValue);
879 | }
880 | }
881 |
882 | /**
883 | * Extracts the text after the last slash in a given string.
884 | *
885 | * @param text The input string to process.
886 | * @return The text after the last slash, or the original text if no slash is found.
887 | */
888 | private String textAfterSlash(String text) {
889 | // Define the regex pattern to match any characters followed by a slash, and capture the text after the last slash
890 | Pattern pattern = Pattern.compile(".*/(.*)");
891 | // Create a Matcher object to perform the matching operation
892 | Matcher matcher = pattern.matcher(text);
893 |
894 | // Check if the regex pattern matches the given input text
895 | if (matcher.find()) {
896 | // If a match is found, extract the captured text which is the text after the last slash
897 | String textAfterSlash = matcher.group(1);
898 | return textAfterSlash;
899 | }
900 | // If no match is found return the original text
901 | return text;
902 | }
903 | /**
904 | * Generates the next sequential hexadecimal ID.
905 | *
906 | * @param inputHex The input hexadecimal string (without "0x" prefix).
907 | * @return The next sequential hexadecimal ID as a string. Returns inputHex if parsing fails.
908 | */
909 | public String generateNextHexId(String inputHexID) {
910 | try {
911 | // Parse the input hex string as a long (base 16)
912 | long hexValue = Long.parseLong(inputHexID, 16);
913 |
914 | // Increment the long value
915 | hexValue++;
916 |
917 | // Format the incremented value back to a hex string (8 digits)
918 | return formatToHex((int)hexValue);
919 | }
920 | catch(NumberFormatException e){
921 | // If there's a NumberFormatException (invalid input), return original value.
922 | return inputHexID;
923 | }
924 | }
925 |
926 | }
927 |
--------------------------------------------------------------------------------
/library/src/main/assets/attrs_manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
568 |
569 |
570 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
774 |
775 |
776 |
777 |
778 |
784 |
785 |
786 |
787 |
788 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
804 |
805 |
806 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
851 |
852 |
853 |
854 |
855 |
856 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
876 |
877 |
878 |
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
911 |
912 |
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 |
922 |
923 |
924 |
925 |
926 |
927 |
928 |
929 |
930 |
931 |
932 |
934 |
935 |
936 |
937 |
--------------------------------------------------------------------------------