diff --git a/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java b/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java index 08e6ce3c..5cbca987 100644 --- a/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java +++ b/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java @@ -149,10 +149,9 @@ final public class AndrolibResources { inApk = apkFile.getDirectory(); out = new FileDirectory(outDir); - LOGGER.info("Decoding AndroidManifest.xml with resources..."); - - fileDecoder.decodeManifest( - inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); + fileDecoder.decode( + inApk, "AndroidManifest.xml", out, "AndroidManifest.xml", + "xml"); if (inApk.containsDir("res")) { in = inApk.getDir("res"); diff --git a/apktool-lib/src/main/java/brut/androlib/res/data/value/ResPluralsValue.java b/apktool-lib/src/main/java/brut/androlib/res/data/value/ResPluralsValue.java index 39930619..accb4bb0 100644 --- a/apktool-lib/src/main/java/brut/androlib/res/data/value/ResPluralsValue.java +++ b/apktool-lib/src/main/java/brut/androlib/res/data/value/ResPluralsValue.java @@ -51,15 +51,7 @@ public class ResPluralsValue extends ResBagValue implements ResValuesXmlSerializ } serializer.startTag(null, "item"); serializer.attribute(null, "quantity", QUANTITY_MAP[i]); - - String rawValue = item.encodeAsResXmlValueExt(); - //AAPT don`t parse formatted for item tag(only for string and string-array tag), - // so adding "formatted='fasle'" is useless. - /*if (ResXmlEncoders.hasMultipleNonPositionalSubstitutions(rawValue)) { - serializer.attribute(null, "formatted", "false"); - }*/ - serializer.text(rawValue); - + serializer.text(item.encodeAsResXmlValue()); serializer.endTag(null, "item"); } serializer.endTag(null, "plurals"); diff --git a/apktool-lib/src/main/java/brut/androlib/res/data/value/ResScalarValue.java b/apktool-lib/src/main/java/brut/androlib/res/data/value/ResScalarValue.java index 0764d98b..c0a3fe52 100644 --- a/apktool-lib/src/main/java/brut/androlib/res/data/value/ResScalarValue.java +++ b/apktool-lib/src/main/java/brut/androlib/res/data/value/ResScalarValue.java @@ -52,7 +52,7 @@ public abstract class ResScalarValue extends ResValue if (mRawValue != null) { return mRawValue; } - return encodeAsResXml(); + return encodeAsResXmlValueExt(); } public String encodeAsResXmlValueExt() throws AndrolibException { @@ -60,7 +60,7 @@ public abstract class ResScalarValue extends ResValue if (rawValue != null) { if (ResXmlEncoders.hasMultipleNonPositionalSubstitutions(rawValue)) { int count = 1; - StringBuffer result = new StringBuffer(); + StringBuilder result = new StringBuilder(); String tmp1[] = rawValue.split("%%", -1); int tmp1_sz = tmp1.length; for(int i=0;i * @author Dmitry Skiba - * + * * Binary xml files parser. - * - * Parser has only two states: - * (1) Operational state, which parser obtains after first successful call - * to next() and retains until open(), close(), or failed call to next(). - * (2) Closed state, which parser obtains after open(), close(), or failed - * call to next(). In this state methods return invalid values or throw exceptions. - * - * TODO: - * * check all methods in closed state + * + * Parser has only two states: (1) Operational state, which parser obtains after + * first successful call to next() and retains until open(), close(), or failed + * call to next(). (2) Closed state, which parser obtains after open(), close(), + * or failed call to next(). In this state methods return invalid values or + * throw exceptions. + * + * TODO: * check all methods in closed state * */ public class AXmlResourceParser implements XmlResourceParser { @@ -319,20 +317,19 @@ public class AXmlResourceParser implements XmlResourceParser { try { return mAttrDecoder.decode(valueType, valueData, valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars( - m_strings.getString(valueRaw)), + m_strings.getString(valueRaw)), getAttributeNameResource(index)); } catch (AndrolibException ex) { setFirstError(ex); LOGGER.log(Level.WARNING, String.format( - "Could not decode attr value, using undecoded value " + - "instead: ns=%s, name=%s, value=0x%08x", - getAttributePrefix(index), getAttributeName(index), - valueData - ), ex); + "Could not decode attr value, using undecoded value " + + "instead: ns=%s, name=%s, value=0x%08x", + getAttributePrefix(index), getAttributeName(index), + valueData), ex); } } else { - if (valueType==TypedValue.TYPE_STRING) { - return m_strings.getString(valueRaw); + if (valueType == TypedValue.TYPE_STRING) { + return ResXmlEncoders.escapeXmlChars(m_strings.getString(valueRaw)); } } @@ -493,20 +490,16 @@ public class AXmlResourceParser implements XmlResourceParser { ///////////////////////////////////////////// implementation /** - * Namespace stack, holds prefix+uri pairs, as well as - * depth information. - * All information is stored in one int[] array. - * Array consists of depth frames: - * Data=DepthFrame*; - * DepthFrame=Count+[Prefix+Uri]*+Count; - * Count='count of Prefix+Uri pairs'; - * Yes, count is stored twice, to enable bottom-up traversal. - * increaseDepth adds depth frame, decreaseDepth removes it. - * push/pop operations operate only in current depth frame. - * decreaseDepth removes any remaining (not pop'ed) namespace pairs. - * findXXX methods search all depth frames starting - * from the last namespace pair of current depth frame. - * All functions that operate with int, use -1 as 'invalid value'. + * Namespace stack, holds prefix+uri pairs, as well as depth information. + * All information is stored in one int[] array. Array consists of depth + * frames: Data=DepthFrame*; DepthFrame=Count+[Prefix+Uri]*+Count; + * Count='count of Prefix+Uri pairs'; Yes, count is stored twice, to enable + * bottom-up traversal. increaseDepth adds depth frame, decreaseDepth + * removes it. push/pop operations operate only in current depth frame. + * decreaseDepth removes any remaining (not pop'ed) namespace pairs. findXXX + * methods search all depth frames starting from the last namespace pair of + * current depth frame. All functions that operate with int, use -1 as + * 'invalid value'. * * !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !! * @@ -764,7 +757,7 @@ public class AXmlResourceParser implements XmlResourceParser { if (m_event != START_TAG) { throw new IndexOutOfBoundsException("Current event is not START_TAG."); } - int offset = index * ATTRIBUTE_LENGHT; + int offset = index * ATTRIBUTE_LENGHT; if (offset >= m_attributes.length) { throw new IndexOutOfBoundsException("Invalid attribute index (" + index + ")."); } @@ -806,7 +799,9 @@ public class AXmlResourceParser implements XmlResourceParser { // Delayed initialization. if (m_strings == null) { m_reader.skipCheckInt(CHUNK_AXML_FILE); - /*chunkSize*/ m_reader.skipInt(); + /* + * chunkSize + */ m_reader.skipInt(); m_strings = StringBlock.read(m_reader); m_namespaces.increaseDepth(); m_operational = true; @@ -856,9 +851,13 @@ public class AXmlResourceParser implements XmlResourceParser { } // Common header. - /*chunkSize*/ m_reader.skipInt(); + /* + * chunkSize + */ m_reader.skipInt(); int lineNumber = m_reader.readInt(); - /*0xFFFFFFFF*/ m_reader.skipInt(); + /* + * 0xFFFFFFFF + */ m_reader.skipInt(); // Fake START_DOCUMENT event. if (chunkType == CHUNK_XML_START_TAG && event == -1) { @@ -874,8 +873,12 @@ public class AXmlResourceParser implements XmlResourceParser { int uri = m_reader.readInt(); m_namespaces.push(prefix, uri); } else { - /*prefix*/ m_reader.skipInt(); - /*uri*/ m_reader.skipInt(); + /* + * prefix + */ m_reader.skipInt(); + /* + * uri + */ m_reader.skipInt(); m_namespaces.pop(); } continue; @@ -888,7 +891,9 @@ public class AXmlResourceParser implements XmlResourceParser { if (chunkType == CHUNK_XML_START_TAG) { m_namespaceUri = m_reader.readInt(); m_name = m_reader.readInt(); - /*flags?*/ m_reader.skipInt(); + /* + * flags? + */ m_reader.skipInt(); int attributeCount = m_reader.readInt(); m_idAttribute = (attributeCount >>> 16) - 1; attributeCount &= 0xFFFF; @@ -903,7 +908,7 @@ public class AXmlResourceParser implements XmlResourceParser { m_namespaces.increaseDepth(); m_event = START_TAG; m_strings.touch(m_name, m_name); - for(int i = 0; i array.length) - max = array.length; - if(min < 0) - min = 0; - StringBuffer sb = new StringBuffer("["); - int i = min; - while(true) { - sb.append(array[i]); - i++; - if(i < max) { - sb.append(", "); - } else { - sb.append("]"); - break; - } - } - return sb.toString(); - } - - private boolean compareAttr(int[] attr1, int[] attr2) { - //TODO: sort Attrs - /* - * ATTRIBUTE_IX_VALUE_TYPE == TYPE_STRING : ATTRIBUTE_IX_VALUE_STRING - * : ATTRIBUTE_IX_NAMESPACE_URI - * ATTRIBUTE_IX_NAMESPACE_URI : ATTRIBUTE_IX_NAME - * id - * - */ - if(attr1[ATTRIBUTE_IX_VALUE_TYPE] == TypedValue.TYPE_STRING && - attr1[ATTRIBUTE_IX_VALUE_TYPE] == attr2[ATTRIBUTE_IX_VALUE_TYPE] && - //(m_strings.touch(attr1[ATTRIBUTE_IX_VALUE_STRING], m_name) || - // m_strings.touch(attr2[ATTRIBUTE_IX_VALUE_STRING], m_name)) && - //m_strings.touch(attr1[ATTRIBUTE_IX_VALUE_STRING], m_name) && - attr1[ATTRIBUTE_IX_VALUE_STRING] != attr2[ATTRIBUTE_IX_VALUE_STRING]) { - return (attr1[ATTRIBUTE_IX_VALUE_STRING] < attr2[ATTRIBUTE_IX_VALUE_STRING]); - } else if ((attr1[ATTRIBUTE_IX_NAMESPACE_URI] == attr2[ATTRIBUTE_IX_NAMESPACE_URI]) && (attr1[ATTRIBUTE_IX_NAMESPACE_URI] != -1) && - //(m_strings.touch(attr1[ATTRIBUTE_IX_NAME], m_name) || - // m_strings.touch(attr2[ATTRIBUTE_IX_NAME], m_name)) && - //m_strings.touch(attr1[ATTRIBUTE_IX_NAME], m_name) && - (attr1[ATTRIBUTE_IX_NAME] != attr2[ATTRIBUTE_IX_NAME])) { - return (attr1[ATTRIBUTE_IX_NAME] < attr2[ATTRIBUTE_IX_NAME]); - //} else if (attr1[ATTRIBUTE_IX_NAMESPACE_URI] < attr2[ATTRIBUTE_IX_NAMESPACE_URI]) { - // return true; - } else { - return false; - } - } - - private void sortAttrs() { - int attributeCount = m_attributes.length/ATTRIBUTE_LENGHT; - int tmp1[][] = new int[attributeCount][]; - int tmp2[] = null; - for (int i = 0; i < attributeCount;i++) { - tmp1[i] = new int[ATTRIBUTE_LENGHT+1]; - for(int j = 0; j < ATTRIBUTE_LENGHT; j++) { - tmp1[i][j] = m_attributes[i*ATTRIBUTE_LENGHT+j]; - } - tmp1[i][ATTRIBUTE_LENGHT] = i; - if(DBG) { - try { - if (dbgOut == null) { - dbgOut = new BufferedWriter(new FileWriter("C:\\res.log",false)); - } - dbgOut.write("Namespace: " + getAttributeNamespace (i) + - ", Name: " + getAttributeName (i)+ - ", Value: " + getAttributeValue (i) + ", Array: " + - formatArray(tmp1[i], 0, ATTRIBUTE_LENGHT) + "\n"); - dbgOut.flush(); - } catch (IOException e) { - e.printStackTrace(); - } + private static String formatArray(int[] array, int min, int max) { + if (max > array.length) { + max = array.length; + } + if (min < 0) { + min = 0; + } + StringBuffer sb = new StringBuffer("["); + int i = min; + while (true) { + sb.append(array[i]); + i++; + if (i < max) { + sb.append(", "); + } else { + sb.append("]"); + break; } } - for (int j = 1; j < attributeCount;j++) { - for (int i = 1; i < attributeCount;i++) { - if(compareAttr(tmp1[i], tmp1[i-1])) { - tmp2 = tmp1[i-1]; - tmp1[i-1] = tmp1[i]; + return sb.toString(); + } + + private boolean compareAttr(int[] attr1, int[] attr2) { + //TODO: sort Attrs + /* + * ATTRIBUTE_IX_VALUE_TYPE == TYPE_STRING : ATTRIBUTE_IX_VALUE_STRING : + * ATTRIBUTE_IX_NAMESPACE_URI ATTRIBUTE_IX_NAMESPACE_URI : + * ATTRIBUTE_IX_NAME id + * + */ + if (attr1[ATTRIBUTE_IX_VALUE_TYPE] == TypedValue.TYPE_STRING + && attr1[ATTRIBUTE_IX_VALUE_TYPE] == attr2[ATTRIBUTE_IX_VALUE_TYPE] + && //(m_strings.touch(attr1[ATTRIBUTE_IX_VALUE_STRING], m_name) || + // m_strings.touch(attr2[ATTRIBUTE_IX_VALUE_STRING], m_name)) && + //m_strings.touch(attr1[ATTRIBUTE_IX_VALUE_STRING], m_name) && + attr1[ATTRIBUTE_IX_VALUE_STRING] != attr2[ATTRIBUTE_IX_VALUE_STRING]) { + return (attr1[ATTRIBUTE_IX_VALUE_STRING] < attr2[ATTRIBUTE_IX_VALUE_STRING]); + } else if ((attr1[ATTRIBUTE_IX_NAMESPACE_URI] == attr2[ATTRIBUTE_IX_NAMESPACE_URI]) && (attr1[ATTRIBUTE_IX_NAMESPACE_URI] != -1) + && //(m_strings.touch(attr1[ATTRIBUTE_IX_NAME], m_name) || + // m_strings.touch(attr2[ATTRIBUTE_IX_NAME], m_name)) && + //m_strings.touch(attr1[ATTRIBUTE_IX_NAME], m_name) && + (attr1[ATTRIBUTE_IX_NAME] != attr2[ATTRIBUTE_IX_NAME])) { + return (attr1[ATTRIBUTE_IX_NAME] < attr2[ATTRIBUTE_IX_NAME]); + //} else if (attr1[ATTRIBUTE_IX_NAMESPACE_URI] < attr2[ATTRIBUTE_IX_NAMESPACE_URI]) { + // return true; + } else { + return false; + } + } + + private void sortAttrs() { + int attributeCount = m_attributes.length / ATTRIBUTE_LENGHT; + int tmp1[][] = new int[attributeCount][]; + int tmp2[] = null; + for (int i = 0; i < attributeCount; i++) { + tmp1[i] = new int[ATTRIBUTE_LENGHT + 1]; + for (int j = 0; j < ATTRIBUTE_LENGHT; j++) { + tmp1[i][j] = m_attributes[i * ATTRIBUTE_LENGHT + j]; + } + tmp1[i][ATTRIBUTE_LENGHT] = i; + if (DBG) { + try { + if (dbgOut == null) { + dbgOut = new BufferedWriter(new FileWriter("C:\\res.log", false)); + } + dbgOut.write("Namespace: " + getAttributeNamespace(i) + + ", Name: " + getAttributeName(i) + + ", Value: " + getAttributeValue(i) + ", Array: " + + formatArray(tmp1[i], 0, ATTRIBUTE_LENGHT) + "\n"); + dbgOut.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + for (int j = 1; j < attributeCount; j++) { + for (int i = 1; i < attributeCount; i++) { + if (compareAttr(tmp1[i], tmp1[i - 1])) { + tmp2 = tmp1[i - 1]; + tmp1[i - 1] = tmp1[i]; tmp1[i] = tmp2; } } } - for (int i = 0; i < attributeCount;i++) { - for(int j = 0; j < ATTRIBUTE_LENGHT; j++) { - m_attributes[i*ATTRIBUTE_LENGHT+j] = tmp1[i][j]; + for (int i = 0; i < attributeCount; i++) { + for (int j = 0; j < ATTRIBUTE_LENGHT; j++) { + m_attributes[i * ATTRIBUTE_LENGHT + j] = tmp1[i][j]; } } - } + } - private void setFirstError(AndrolibException error) { + private void setFirstError(AndrolibException error) { if (mFirstError == null) { mFirstError = error; } } - /////////////////////////////////// data /* - * All values are essentially indices, e.g. m_name is - * an index of name in m_strings. + * All values are essentially indices, e.g. m_name is an index of name in + * m_strings. */ private ExtDataInput m_reader; private ResAttrDecoder mAttrDecoder; private AndrolibException mFirstError; - private boolean m_operational = false; private StringBlock m_strings; private int[] m_resourceIDs; @@ -1048,28 +1056,24 @@ public class AXmlResourceParser implements XmlResourceParser { private int m_idAttribute; private int m_classAttribute; private int m_styleAttribute; - private final static Logger LOGGER = - Logger.getLogger(AXmlResourceParser.class.getName()); + Logger.getLogger(AXmlResourceParser.class.getName()); private static final String E_NOT_SUPPORTED = "Method is not supported."; - private static final int - ATTRIBUTE_IX_NAMESPACE_URI = 0, - ATTRIBUTE_IX_NAME = 1, - ATTRIBUTE_IX_VALUE_STRING = 2, - ATTRIBUTE_IX_VALUE_TYPE = 3, - ATTRIBUTE_IX_VALUE_DATA = 4, - ATTRIBUTE_LENGHT = 5; - - private static final int - CHUNK_AXML_FILE = 0x00080003, - CHUNK_RESOURCEIDS = 0x00080180, - CHUNK_XML_FIRST = 0x00100100, + private static final int ATTRIBUTE_IX_NAMESPACE_URI = 0, + ATTRIBUTE_IX_NAME = 1, + ATTRIBUTE_IX_VALUE_STRING = 2, + ATTRIBUTE_IX_VALUE_TYPE = 3, + ATTRIBUTE_IX_VALUE_DATA = 4, + ATTRIBUTE_LENGHT = 5; + private static final int CHUNK_AXML_FILE = 0x00080003, + CHUNK_RESOURCEIDS = 0x00080180, + CHUNK_XML_FIRST = 0x00100100, CHUNK_XML_START_NAMESPACE = 0x00100100, - CHUNK_XML_END_NAMESPACE = 0x00100101, - CHUNK_XML_START_TAG = 0x00100102, - CHUNK_XML_END_TAG = 0x00100103, - CHUNK_XML_TEXT = 0x00100104, - CHUNK_XML_LAST = 0x00100104; + CHUNK_XML_END_NAMESPACE = 0x00100101, + CHUNK_XML_START_TAG = 0x00100102, + CHUNK_XML_END_TAG = 0x00100103, + CHUNK_XML_TEXT = 0x00100104, + CHUNK_XML_LAST = 0x00100104; private Writer dbgOut = null; private final static boolean DBG = false; } \ No newline at end of file diff --git a/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java b/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java index 46226e39..7124f885 100644 --- a/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java +++ b/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java @@ -156,37 +156,24 @@ public final class ResXmlEncoders { return out.toString(); } - /** - * It searches for "%", but not "%%" nor "%(\d)+\$" - */ - private static List findNonPositionalSubstitutions(String str, +private static List findNonPositionalSubstitutions(String str, int max) { int pos = 0; - int pos2 = 0; int count = 0; int length = str.length(); List ret = new ArrayList(); - while((pos2 = (pos = str.indexOf('%', pos2)) + 1) != 0) { - if (pos2 == length) { + while((pos = str.indexOf('%', pos)) != -1) { + if (pos + 1 == length) { break; } - char c = str.charAt(pos2++); - if (c == '%') { - continue; - } - if (c >= '0' && c <= '9' && pos2 < length) { - do { - c = str.charAt(pos2++); - } while (c >= '0' && c <= '9' && pos2 < length); - if (c == '$') { - continue; + char c = str.charAt(pos + 1); + if (c >= 'a' && c <= 'z') { + ret.add(pos); + if (max != -1 && ++count >= max) { + break; } } - - ret.add(pos); - if (max != -1 && ++count >= max) { - break; - } + pos += 2; } return ret;