mirror of
https://github.com/revanced/Apktool.git
synced 2025-01-05 17:45:52 +01:00
ARSC/AXML Parser Rework (#3131)
* Supports ASRC with null renamed package. * Rework ASRC Chunk parser to a loop to break assumption of order of chunks * Break out unknown skips for alignment to ResourceTypes.h * Add verbose information for file skips * Add test for protected apk sample * Rework chunk parsing for StringBlock * Refactor AXML Parser to support proper header reading * Fix parsing if attribute size reported does not align to actual size
This commit is contained in:
parent
86340503ac
commit
bdbe1384bf
@ -190,19 +190,20 @@ public class ResourcesDecoder {
|
||||
// compare resources.arsc package name to the one present in AndroidManifest
|
||||
ResPackage resPackage = resTable.getCurrentResPackage();
|
||||
String pkgOriginal = resPackage.getName();
|
||||
String packageRenamed = resTable.getPackageRenamed();
|
||||
String pkgRenamed = resTable.getPackageRenamed();
|
||||
|
||||
resTable.setPackageId(resPackage.getId());
|
||||
resTable.setPackageOriginal(pkgOriginal);
|
||||
|
||||
// 1) Check if pkgOriginal is null (empty resources.arsc)
|
||||
// 2) Check if pkgOriginal === mPackageRenamed
|
||||
// 3) Check if pkgOriginal is ignored via IGNORED_PACKAGES
|
||||
if (pkgOriginal == null || pkgOriginal.equalsIgnoreCase(packageRenamed)
|
||||
// 2) Check if pkgRenamed is null
|
||||
// 3) Check if pkgOriginal === mPackageRenamed
|
||||
// 4) Check if pkgOriginal is ignored via IGNORED_PACKAGES
|
||||
if (pkgOriginal == null || pkgRenamed == null || pkgOriginal.equalsIgnoreCase(pkgRenamed)
|
||||
|| (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) {
|
||||
LOGGER.info("Regular manifest package...");
|
||||
} else {
|
||||
LOGGER.info("Renamed manifest package found! Replacing " + packageRenamed + " with " + pkgOriginal);
|
||||
LOGGER.info("Renamed manifest package found! Replacing " + pkgRenamed + " with " + pkgOriginal);
|
||||
ResXmlPatcher.renameManifestPackage(new File(filePath), pkgOriginal);
|
||||
}
|
||||
}
|
||||
|
@ -364,7 +364,7 @@ public class ResTable {
|
||||
}
|
||||
|
||||
// only put rename-manifest-package into apktool.yml, if the change will be required
|
||||
if (!renamed.equalsIgnoreCase(original)) {
|
||||
if (renamed != null && !renamed.equalsIgnoreCase(original)) {
|
||||
mApkInfo.packageInfo.renameManifestPackage = renamed;
|
||||
}
|
||||
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);
|
||||
|
@ -43,16 +43,22 @@ public class ARSCHeader {
|
||||
try {
|
||||
type = in.readShort();
|
||||
} catch (EOFException ex) {
|
||||
return new ARSCHeader(TYPE_NONE, 0, 0, countIn.getCount());
|
||||
return new ARSCHeader(RES_NONE_TYPE, 0, 0, countIn.getCount());
|
||||
}
|
||||
return new ARSCHeader(type, in.readShort(), in.readInt(), start);
|
||||
}
|
||||
|
||||
public final static short TYPE_NONE = -1;
|
||||
public final static short TYPE_STRING_POOL = 0x0001;
|
||||
public final static short TYPE_TABLE = 0x0002;
|
||||
public final static short TYPE_XML = 0x0003;
|
||||
public void skipChunk(ExtDataInput in) throws IOException {
|
||||
in.skipBytes(chunkSize - headerSize);
|
||||
}
|
||||
|
||||
public final static short RES_NONE_TYPE = -1;
|
||||
public final static short RES_NULL_TYPE = 0x0000;
|
||||
public final static short RES_STRING_POOL_TYPE = 0x0001;
|
||||
public final static short RES_TABLE_TYPE = 0x0002;
|
||||
public final static short RES_XML_TYPE = 0x0003;
|
||||
|
||||
// RES_TABLE_TYPE Chunks
|
||||
public final static short XML_TYPE_PACKAGE = 0x0200;
|
||||
public final static short XML_TYPE_TYPE = 0x0201;
|
||||
public final static short XML_TYPE_SPEC_TYPE = 0x0202;
|
||||
@ -60,4 +66,14 @@ public class ARSCHeader {
|
||||
public final static short XML_TYPE_OVERLAY = 0x0204;
|
||||
public final static short XML_TYPE_OVERLAY_POLICY = 0x0205;
|
||||
public final static short XML_TYPE_STAGED_ALIAS = 0x0206;
|
||||
|
||||
// RES_XML_TYPE Chunks
|
||||
public final static short RES_XML_FIRST_CHUNK_TYPE = 0x0100;
|
||||
public final static short RES_XML_START_NAMESPACE_TYPE = 0x0100;
|
||||
public final static short RES_XML_END_NAMESPACE_TYPE = 0x0101;
|
||||
public final static short RES_XML_START_ELEMENT_TYPE = 0x0102;
|
||||
public final static short RES_XML_END_ELEMENT_TYPE = 0x0103;
|
||||
public final static short RES_XML_CDATA_TYPE = 0x0104;
|
||||
public final static short RES_XML_LAST_CHUNK_TYPE = 0x017f;
|
||||
public final static short RES_XML_RESOURCE_MAP_TYPE = 0x0180;
|
||||
}
|
||||
|
@ -33,10 +33,7 @@ import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class ARSCDecoder {
|
||||
@ -50,7 +47,7 @@ public class ARSCDecoder {
|
||||
throws AndrolibException {
|
||||
try {
|
||||
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken);
|
||||
ResPackage[] pkgs = decoder.readTableHeader();
|
||||
ResPackage[] pkgs = decoder.readResourceTable();
|
||||
return new ARSCData(pkgs, decoder.mFlagsOffsets == null
|
||||
? null
|
||||
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]));
|
||||
@ -74,19 +71,85 @@ public class ARSCDecoder {
|
||||
mKeepBroken = keepBroken;
|
||||
}
|
||||
|
||||
private ResPackage[] readTableHeader() throws IOException, AndrolibException {
|
||||
nextChunkCheckType(ARSCHeader.TYPE_TABLE);
|
||||
int packageCount = mIn.readInt();
|
||||
private ResPackage[] readResourceTable() throws IOException, AndrolibException {
|
||||
Set<ResPackage> pkgs = new LinkedHashSet<>();
|
||||
ResTypeSpec typeSpec;
|
||||
|
||||
mTableStrings = StringBlock.read(mIn);
|
||||
ResPackage[] packages = new ResPackage[packageCount];
|
||||
chunkLoop:
|
||||
for (;;) {
|
||||
nextChunk();
|
||||
|
||||
nextChunk();
|
||||
for (int i = 0; i < packageCount; i++) {
|
||||
mTypeIdOffset = 0;
|
||||
packages[i] = readTablePackage();
|
||||
switch (mHeader.type) {
|
||||
case ARSCHeader.RES_NULL_TYPE:
|
||||
readUnknownChunk();
|
||||
break;
|
||||
case ARSCHeader.RES_STRING_POOL_TYPE:
|
||||
readStringPoolChunk();
|
||||
break;
|
||||
case ARSCHeader.RES_TABLE_TYPE:
|
||||
readTableChunk();
|
||||
break;
|
||||
|
||||
// Chunk types in RES_TABLE_TYPE
|
||||
case ARSCHeader.XML_TYPE_PACKAGE:
|
||||
mTypeIdOffset = 0;
|
||||
pkgs.add(readTablePackage());
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_TYPE:
|
||||
readTableType();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_SPEC_TYPE:
|
||||
typeSpec = readTableSpecType();
|
||||
addTypeSpec(typeSpec);
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_LIBRARY:
|
||||
readLibraryType();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_OVERLAY:
|
||||
readOverlaySpec();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_OVERLAY_POLICY:
|
||||
readOverlayPolicySpec();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_STAGED_ALIAS:
|
||||
readStagedAliasSpec();
|
||||
break;
|
||||
default:
|
||||
if (mHeader.type != ARSCHeader.RES_NONE_TYPE) {
|
||||
LOGGER.severe(String.format("Unknown chunk type: %04x", mHeader.type));
|
||||
}
|
||||
break chunkLoop;
|
||||
}
|
||||
}
|
||||
return packages;
|
||||
|
||||
if (mPkg.getResSpecCount() > 0) {
|
||||
addMissingResSpecs();
|
||||
}
|
||||
|
||||
// We've detected sparse resources, lets record this so we can rebuild in that same format (sparse/not)
|
||||
// with aapt2. aapt1 will ignore this.
|
||||
if (! mResTable.getSparseResources()) {
|
||||
mResTable.setSparseResources(true);
|
||||
}
|
||||
|
||||
return pkgs.toArray(new ResPackage[0]);
|
||||
}
|
||||
|
||||
private void readStringPoolChunk() throws IOException, AndrolibException {
|
||||
checkChunkType(ARSCHeader.RES_STRING_POOL_TYPE);
|
||||
mTableStrings = StringBlock.readWithoutChunk(mIn, mHeader.chunkSize);
|
||||
}
|
||||
|
||||
private void readTableChunk() throws IOException, AndrolibException {
|
||||
checkChunkType(ARSCHeader.RES_TABLE_TYPE);
|
||||
mIn.skipInt(); // packageCount
|
||||
}
|
||||
|
||||
private void readUnknownChunk() throws IOException, AndrolibException {
|
||||
checkChunkType(ARSCHeader.RES_NULL_TYPE);
|
||||
|
||||
LOGGER.warning("Skipping unknown chunk data of size " + mHeader.chunkSize);
|
||||
mHeader.skipChunk(mIn);
|
||||
}
|
||||
|
||||
private ResPackage readTablePackage() throws IOException, AndrolibException {
|
||||
@ -121,38 +184,12 @@ public class ARSCDecoder {
|
||||
LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/1728");
|
||||
}
|
||||
|
||||
mTypeNames = StringBlock.read(mIn);
|
||||
mSpecNames = StringBlock.read(mIn);
|
||||
mTypeNames = StringBlock.readWithChunk(mIn);
|
||||
mSpecNames = StringBlock.readWithChunk(mIn);
|
||||
|
||||
mResId = id << 24;
|
||||
mPkg = new ResPackage(mResTable, id, name);
|
||||
|
||||
nextChunk();
|
||||
|
||||
chunkLoop:
|
||||
for (;;) {
|
||||
switch (mHeader.type) {
|
||||
case ARSCHeader.XML_TYPE_TYPE:
|
||||
case ARSCHeader.XML_TYPE_SPEC_TYPE:
|
||||
readTableTypeSpec();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_LIBRARY:
|
||||
readLibraryType();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_OVERLAY:
|
||||
readOverlaySpec();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_OVERLAY_POLICY:
|
||||
readOverlayPolicySpec();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_STAGED_ALIAS:
|
||||
readStagedAliasSpec();
|
||||
break;
|
||||
default:
|
||||
break chunkLoop;
|
||||
}
|
||||
}
|
||||
|
||||
return mPkg;
|
||||
}
|
||||
|
||||
@ -168,8 +205,6 @@ public class ARSCDecoder {
|
||||
packageName = mIn.readNullEndedString(128, true);
|
||||
LOGGER.info(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId));
|
||||
}
|
||||
|
||||
nextChunk();
|
||||
}
|
||||
|
||||
private void readStagedAliasSpec() throws IOException {
|
||||
@ -178,8 +213,6 @@ public class ARSCDecoder {
|
||||
for (int i = 0; i < count; i++) {
|
||||
LOGGER.fine(String.format("Skipping staged alias stagedId (%h) finalId: %h", mIn.readInt(), mIn.readInt()));
|
||||
}
|
||||
|
||||
nextChunk();
|
||||
}
|
||||
|
||||
private void readOverlaySpec() throws AndrolibException, IOException {
|
||||
@ -187,8 +220,6 @@ public class ARSCDecoder {
|
||||
String name = mIn.readNullEndedString(256, true);
|
||||
String actor = mIn.readNullEndedString(256, true);
|
||||
LOGGER.fine(String.format("Overlay name: \"%s\", actor: \"%s\")", name, actor));
|
||||
|
||||
nextChunk();
|
||||
}
|
||||
|
||||
private void readOverlayPolicySpec() throws AndrolibException, IOException {
|
||||
@ -199,48 +230,13 @@ public class ARSCDecoder {
|
||||
for (int i = 0; i < count; i++) {
|
||||
LOGGER.fine(String.format("Skipping overlay (%h)", mIn.readInt()));
|
||||
}
|
||||
|
||||
nextChunk();
|
||||
}
|
||||
|
||||
private void readTableTypeSpec() throws AndrolibException, IOException {
|
||||
mTypeSpec = readSingleTableTypeSpec();
|
||||
addTypeSpec(mTypeSpec);
|
||||
|
||||
int type = nextChunk().type;
|
||||
ResTypeSpec resTypeSpec;
|
||||
|
||||
while (type == ARSCHeader.XML_TYPE_SPEC_TYPE) {
|
||||
resTypeSpec = readSingleTableTypeSpec();
|
||||
addTypeSpec(resTypeSpec);
|
||||
type = nextChunk().type;
|
||||
|
||||
// We've detected sparse resources, lets record this so we can rebuild in that same format (sparse/not)
|
||||
// with aapt2. aapt1 will ignore this.
|
||||
if (! mResTable.getSparseResources()) {
|
||||
mResTable.setSparseResources(true);
|
||||
}
|
||||
}
|
||||
|
||||
while (type == ARSCHeader.XML_TYPE_TYPE) {
|
||||
readTableType();
|
||||
|
||||
// skip "TYPE 8 chunks" and/or padding data at the end of this chunk
|
||||
if (mCountIn.getCount() < mHeader.endPosition) {
|
||||
LOGGER.warning("Unknown data detected. Skipping: " + (mHeader.endPosition - mCountIn.getCount()) + " byte(s)");
|
||||
mCountIn.skip(mHeader.endPosition - mCountIn.getCount());
|
||||
}
|
||||
|
||||
type = nextChunk().type;
|
||||
|
||||
addMissingResSpecs();
|
||||
}
|
||||
}
|
||||
|
||||
private ResTypeSpec readSingleTableTypeSpec() throws AndrolibException, IOException {
|
||||
private ResTypeSpec readTableSpecType() throws AndrolibException, IOException {
|
||||
checkChunkType(ARSCHeader.XML_TYPE_SPEC_TYPE);
|
||||
int id = mIn.readUnsignedByte();
|
||||
mIn.skipBytes(3);
|
||||
mIn.skipBytes(1); // reserved0
|
||||
mIn.skipBytes(2); // reserved1
|
||||
int entryCount = mIn.readInt();
|
||||
|
||||
if (mFlagsOffsets != null) {
|
||||
@ -250,6 +246,7 @@ public class ARSCDecoder {
|
||||
mIn.skipBytes(entryCount * 4); // flags
|
||||
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
|
||||
mPkg.addType(mTypeSpec);
|
||||
|
||||
return mTypeSpec;
|
||||
}
|
||||
|
||||
@ -311,6 +308,12 @@ public class ARSCDecoder {
|
||||
readEntry(readEntryData());
|
||||
}
|
||||
|
||||
// skip "TYPE 8 chunks" and/or padding data at the end of this chunk
|
||||
if (mCountIn.getCount() < mHeader.endPosition) {
|
||||
long bytesSkipped = mCountIn.skip(mHeader.endPosition - mCountIn.getCount());
|
||||
LOGGER.warning("Unknown data detected. Skipping: " + bytesSkipped + " byte(s)");
|
||||
}
|
||||
|
||||
return mType;
|
||||
}
|
||||
|
||||
@ -616,11 +619,6 @@ public class ARSCDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private void nextChunkCheckType(int expectedType) throws IOException, AndrolibException {
|
||||
nextChunk();
|
||||
checkChunkType(expectedType);
|
||||
}
|
||||
|
||||
private final ExtDataInput mIn;
|
||||
private final ResTable mResTable;
|
||||
private final CountingInputStream mCountIn;
|
||||
|
@ -20,6 +20,7 @@ import android.content.res.XmlResourceParser;
|
||||
import android.util.TypedValue;
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.androlib.res.data.ResID;
|
||||
import brut.androlib.res.data.arsc.ARSCHeader;
|
||||
import brut.androlib.res.data.axml.NamespaceStack;
|
||||
import brut.androlib.res.xml.ResXmlEncoders;
|
||||
import brut.util.ExtDataInput;
|
||||
@ -65,31 +66,31 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
// We need to explicitly cast to DataInput as otherwise the constructor is ambiguous.
|
||||
// We choose DataInput instead of InputStream as ExtDataInput wraps an InputStream in
|
||||
// a DataInputStream which is big-endian and ignores the little-endian behavior.
|
||||
m_reader = new ExtDataInput((DataInput) new LittleEndianDataInputStream(stream));
|
||||
mIn = new ExtDataInput((DataInput) new LittleEndianDataInputStream(stream));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!m_operational) {
|
||||
if (!isOperational) {
|
||||
return;
|
||||
}
|
||||
m_operational = false;
|
||||
m_reader = null;
|
||||
m_strings = null;
|
||||
m_resourceIDs = null;
|
||||
m_namespaces.reset();
|
||||
isOperational = false;
|
||||
mIn = null;
|
||||
mStringBlock = null;
|
||||
mResourceIds = null;
|
||||
mNamespaces.reset();
|
||||
resetEventInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int next() throws XmlPullParserException, IOException {
|
||||
if (m_reader == null) {
|
||||
if (mIn == null) {
|
||||
throw new XmlPullParserException("Parser is not opened.", this, null);
|
||||
}
|
||||
try {
|
||||
doNext();
|
||||
return m_event;
|
||||
return mEvent;
|
||||
} catch (IOException e) {
|
||||
close();
|
||||
throw e;
|
||||
@ -134,8 +135,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void require(int type, String namespace, String name)
|
||||
throws XmlPullParserException, IOException {
|
||||
public void require(int type, String namespace, String name) throws XmlPullParserException {
|
||||
if (type != getEventType() || (namespace != null && !namespace.equals(getNamespace()))
|
||||
|| (name != null && !name.equals(getName()))) {
|
||||
throw new XmlPullParserException(TYPES[type] + " is expected.", this, null);
|
||||
@ -144,33 +144,33 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
|
||||
@Override
|
||||
public int getDepth() {
|
||||
return m_namespaces.getDepth() - 1;
|
||||
return mNamespaces.getDepth() - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEventType(){
|
||||
return m_event;
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineNumber() {
|
||||
return m_lineNumber;
|
||||
return mLineNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
if (m_name == -1 || (m_event != START_TAG && m_event != END_TAG)) {
|
||||
if (mNameIndex == -1 || (mEvent != START_TAG && mEvent != END_TAG)) {
|
||||
return null;
|
||||
}
|
||||
return m_strings.getString(m_name);
|
||||
return mStringBlock.getString(mNameIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
if (m_name == -1 || m_event != TEXT) {
|
||||
if (mNameIndex == -1 || mEvent != TEXT) {
|
||||
return null;
|
||||
}
|
||||
return m_strings.getString(m_name);
|
||||
return mStringBlock.getString(mNameIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -188,13 +188,13 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return m_strings.getString(m_namespaceUri);
|
||||
return mStringBlock.getString(mNamespaceIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix() {
|
||||
int prefix = m_namespaces.findPrefix(m_namespaceUri);
|
||||
return m_strings.getString(prefix);
|
||||
int prefix = mNamespaces.findPrefix(mNamespaceIndex);
|
||||
return mStringBlock.getString(prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -204,89 +204,89 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
|
||||
@Override
|
||||
public int getNamespaceCount(int depth) {
|
||||
return m_namespaces.getAccumulatedCount(depth);
|
||||
return mNamespaces.getAccumulatedCount(depth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespacePrefix(int pos) {
|
||||
int prefix = m_namespaces.getPrefix(pos);
|
||||
return m_strings.getString(prefix);
|
||||
int prefix = mNamespaces.getPrefix(pos);
|
||||
return mStringBlock.getString(prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespaceUri(int pos) {
|
||||
int uri = m_namespaces.getUri(pos);
|
||||
return m_strings.getString(uri);
|
||||
int uri = mNamespaces.getUri(pos);
|
||||
return mStringBlock.getString(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassAttribute() {
|
||||
if (m_classAttribute == -1) {
|
||||
if (mClassIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
int offset = getAttributeOffset(m_classAttribute);
|
||||
int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
||||
return m_strings.getString(value);
|
||||
int offset = getAttributeOffset(mClassIndex);
|
||||
int value = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
||||
return mStringBlock.getString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdAttribute() {
|
||||
if (m_idAttribute == -1) {
|
||||
if (mIdIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
int offset = getAttributeOffset(m_idAttribute);
|
||||
int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
||||
return m_strings.getString(value);
|
||||
int offset = getAttributeOffset(mIdIndex);
|
||||
int value = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
||||
return mStringBlock.getString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIdAttributeResourceValue(int defaultValue) {
|
||||
if (m_idAttribute == -1) {
|
||||
if (mIdIndex == -1) {
|
||||
return defaultValue;
|
||||
}
|
||||
int offset = getAttributeOffset(m_idAttribute);
|
||||
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
int offset = getAttributeOffset(mIdIndex);
|
||||
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
if (valueType != TypedValue.TYPE_REFERENCE) {
|
||||
return defaultValue;
|
||||
}
|
||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStyleAttribute() {
|
||||
if (m_styleAttribute == -1) {
|
||||
if (mStyleIndex == -1) {
|
||||
return 0;
|
||||
}
|
||||
int offset = getAttributeOffset(m_styleAttribute);
|
||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
int offset = getAttributeOffset(mStyleIndex);
|
||||
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeCount() {
|
||||
if (m_event != START_TAG) {
|
||||
if (mEvent != START_TAG) {
|
||||
return -1;
|
||||
}
|
||||
return m_attributes.length / ATTRIBUTE_LENGTH;
|
||||
return mAttributes.length / ATTRIBUTE_LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeNamespace(int index) {
|
||||
int offset = getAttributeOffset(index);
|
||||
int namespace = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
|
||||
int namespace = mAttributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
|
||||
if (namespace == -1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Minifiers like removing the namespace, so we will default to default namespace
|
||||
// unless the pkgId of the resource is private. We will grab the non-standard one.
|
||||
String value = m_strings.getString(namespace);
|
||||
String value = mStringBlock.getString(namespace);
|
||||
|
||||
if (value == null || value.length() == 0) {
|
||||
ResID resId = new ResID(getAttributeNameResource(index));
|
||||
if (resId.pkgId == PRIVATE_PKG_ID) {
|
||||
value = getNonDefaultNamespaceUri(offset);
|
||||
} else {
|
||||
value = android_ns;
|
||||
value = "http://schemas.android.com/apk/res/android";
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,9 +294,9 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
}
|
||||
|
||||
private String getNonDefaultNamespaceUri(int offset) {
|
||||
String prefix = m_strings.getString(m_namespaces.getPrefix(offset));
|
||||
String prefix = mStringBlock.getString(mNamespaces.getPrefix(offset));
|
||||
if (prefix != null) {
|
||||
return m_strings.getString(m_namespaces.getUri(offset));
|
||||
return mStringBlock.getString(mNamespaces.getUri(offset));
|
||||
}
|
||||
|
||||
// If we are here. There is some clever obfuscation going on. Our reference points to the namespace are gone.
|
||||
@ -311,24 +311,24 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
@Override
|
||||
public String getAttributePrefix(int index) {
|
||||
int offset = getAttributeOffset(index);
|
||||
int uri = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
|
||||
int prefix = m_namespaces.findPrefix(uri);
|
||||
int uri = mAttributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
|
||||
int prefix = mNamespaces.findPrefix(uri);
|
||||
if (prefix == -1) {
|
||||
return "";
|
||||
}
|
||||
return m_strings.getString(prefix);
|
||||
return mStringBlock.getString(prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeName(int index) {
|
||||
int offset = getAttributeOffset(index);
|
||||
int name = m_attributes[offset + ATTRIBUTE_IX_NAME];
|
||||
int name = mAttributes[offset + ATTRIBUTE_IX_NAME];
|
||||
if (name == -1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String resourceMapValue;
|
||||
String stringBlockValue = m_strings.getString(name);
|
||||
String stringBlockValue = mStringBlock.getString(name);
|
||||
int resourceId = getAttributeNameResource(index);
|
||||
|
||||
try {
|
||||
@ -358,35 +358,35 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
@Override
|
||||
public int getAttributeNameResource(int index) {
|
||||
int offset = getAttributeOffset(index);
|
||||
int name = m_attributes[offset + ATTRIBUTE_IX_NAME];
|
||||
if (m_resourceIDs == null || name < 0 || name >= m_resourceIDs.length) {
|
||||
int name = mAttributes[offset + ATTRIBUTE_IX_NAME];
|
||||
if (mResourceIds == null || name < 0 || name >= mResourceIds.length) {
|
||||
return 0;
|
||||
}
|
||||
return m_resourceIDs[name];
|
||||
return mResourceIds[name];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeValueType(int index) {
|
||||
int offset = getAttributeOffset(index);
|
||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
return mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeValueData(int index) {
|
||||
int offset = getAttributeOffset(index);
|
||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeValue(int index) {
|
||||
int offset = getAttributeOffset(index);
|
||||
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
int valueRaw = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
||||
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
int valueData = mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
int valueRaw = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
||||
|
||||
if (mAttrDecoder != null) {
|
||||
try {
|
||||
String stringBlockValue = valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars(m_strings.getString(valueRaw));
|
||||
String stringBlockValue = valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars(mStringBlock.getString(valueRaw));
|
||||
String resourceMapValue = mAttrDecoder.decodeFromResourceId(valueData);
|
||||
String value = stringBlockValue;
|
||||
|
||||
@ -431,9 +431,9 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
@Override
|
||||
public float getAttributeFloatValue(int index, float defaultValue) {
|
||||
int offset = getAttributeOffset(index);
|
||||
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
if (valueType == TypedValue.TYPE_FLOAT) {
|
||||
int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
int valueData = mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
return Float.intBitsToFloat(valueData);
|
||||
}
|
||||
return defaultValue;
|
||||
@ -442,9 +442,9 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
@Override
|
||||
public int getAttributeIntValue(int index, int defaultValue) {
|
||||
int offset = getAttributeOffset(index);
|
||||
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
if (valueType >= TypedValue.TYPE_FIRST_INT && valueType <= TypedValue.TYPE_LAST_INT) {
|
||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
@ -457,9 +457,9 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
@Override
|
||||
public int getAttributeResourceValue(int index, int defaultValue) {
|
||||
int offset = getAttributeOffset(index);
|
||||
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||
if (valueType == TypedValue.TYPE_REFERENCE) {
|
||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
@ -520,13 +520,11 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
|
||||
@Override
|
||||
public int getAttributeListValue(int index, String[] options, int defaultValue) {
|
||||
// TODO implement
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) {
|
||||
// TODO implement
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -587,8 +585,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(String name, Object value)
|
||||
throws XmlPullParserException {
|
||||
public void setProperty(String name, Object value) throws XmlPullParserException {
|
||||
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
@ -598,34 +595,33 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeature(String name, boolean value)
|
||||
throws XmlPullParserException {
|
||||
public void setFeature(String name, boolean value) throws XmlPullParserException {
|
||||
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
private int getAttributeOffset(int index) {
|
||||
if (m_event != START_TAG) {
|
||||
if (mEvent != START_TAG) {
|
||||
throw new IndexOutOfBoundsException("Current event is not START_TAG.");
|
||||
}
|
||||
int offset = index * ATTRIBUTE_LENGTH;
|
||||
if (offset >= m_attributes.length) {
|
||||
if (offset >= mAttributes.length) {
|
||||
throw new IndexOutOfBoundsException("Invalid attribute index (" + index + ").");
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
private int findAttribute(String namespace, String attribute) {
|
||||
if (m_strings == null || attribute == null) {
|
||||
if (mStringBlock == null || attribute == null) {
|
||||
return -1;
|
||||
}
|
||||
int name = m_strings.find(attribute);
|
||||
int name = mStringBlock.find(attribute);
|
||||
if (name == -1) {
|
||||
return -1;
|
||||
}
|
||||
int uri = (namespace != null) ? m_strings.find(namespace) : -1;
|
||||
for (int o = 0; o != m_attributes.length; o += ATTRIBUTE_LENGTH) {
|
||||
if (name == m_attributes[o + ATTRIBUTE_IX_NAME]
|
||||
&& (uri == -1 || uri == m_attributes[o + ATTRIBUTE_IX_NAMESPACE_URI])) {
|
||||
int uri = (namespace != null) ? mStringBlock.find(namespace) : -1;
|
||||
for (int o = 0; o != mAttributes.length; o += ATTRIBUTE_LENGTH) {
|
||||
if (name == mAttributes[o + ATTRIBUTE_IX_NAME]
|
||||
&& (uri == -1 || uri == mAttributes[o + ATTRIBUTE_IX_NAMESPACE_URI])) {
|
||||
return o / ATTRIBUTE_LENGTH;
|
||||
}
|
||||
}
|
||||
@ -633,127 +629,135 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
}
|
||||
|
||||
private void resetEventInfo() {
|
||||
m_event = -1;
|
||||
m_lineNumber = -1;
|
||||
m_name = -1;
|
||||
m_namespaceUri = -1;
|
||||
m_attributes = null;
|
||||
m_idAttribute = -1;
|
||||
m_classAttribute = -1;
|
||||
m_styleAttribute = -1;
|
||||
mEvent = -1;
|
||||
mLineNumber = -1;
|
||||
mNameIndex = -1;
|
||||
mNamespaceIndex = -1;
|
||||
mAttributes = null;
|
||||
mIdIndex = -1;
|
||||
mClassIndex = -1;
|
||||
mStyleIndex = -1;
|
||||
}
|
||||
|
||||
private void doNext() throws IOException {
|
||||
// Delayed initialization.
|
||||
if (m_strings == null) {
|
||||
m_reader.skipCheckInt(CHUNK_AXML_FILE, CHUNK_AXML_FILE_BROKEN);
|
||||
if (mStringBlock == null) {
|
||||
mIn.skipInt(); // XML Chunk AXML Type
|
||||
mIn.skipInt(); // Chunk Size
|
||||
|
||||
// chunkSize
|
||||
m_reader.skipInt();
|
||||
m_strings = StringBlock.read(m_reader);
|
||||
m_namespaces.increaseDepth();
|
||||
m_operational = true;
|
||||
mStringBlock = StringBlock.readWithChunk(mIn);
|
||||
mNamespaces.increaseDepth();
|
||||
isOperational = true;
|
||||
}
|
||||
|
||||
if (m_event == END_DOCUMENT) {
|
||||
if (mEvent == END_DOCUMENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
int event = m_event;
|
||||
int event = mEvent;
|
||||
resetEventInfo();
|
||||
|
||||
while (true) {
|
||||
if (m_decreaseDepth) {
|
||||
m_decreaseDepth = false;
|
||||
m_namespaces.decreaseDepth();
|
||||
mNamespaces.decreaseDepth();
|
||||
}
|
||||
|
||||
// Fake END_DOCUMENT event.
|
||||
if (event == END_TAG && m_namespaces.getDepth() == 1 && m_namespaces.getCurrentCount() == 0) {
|
||||
m_event = END_DOCUMENT;
|
||||
if (event == END_TAG && mNamespaces.getDepth() == 1 && mNamespaces.getCurrentCount() == 0) {
|
||||
mEvent = END_DOCUMENT;
|
||||
break;
|
||||
}
|
||||
|
||||
int chunkType;
|
||||
if (event == START_DOCUMENT) {
|
||||
// Fake event, see CHUNK_XML_START_TAG handler.
|
||||
chunkType = CHUNK_XML_START_TAG;
|
||||
chunkType = ARSCHeader.RES_XML_START_ELEMENT_TYPE;
|
||||
} else {
|
||||
chunkType = m_reader.readInt();
|
||||
chunkType = mIn.readShort();
|
||||
mIn.skipShort(); // headerSize
|
||||
}
|
||||
|
||||
if (chunkType == CHUNK_RESOURCEIDS) {
|
||||
int chunkSize = m_reader.readInt();
|
||||
if (chunkType == ARSCHeader.RES_XML_RESOURCE_MAP_TYPE) {
|
||||
int chunkSize = mIn.readInt();
|
||||
if (chunkSize < 8 || (chunkSize % 4) != 0) {
|
||||
throw new IOException("Invalid resource ids size (" + chunkSize + ").");
|
||||
}
|
||||
m_resourceIDs = m_reader.readIntArray(chunkSize / 4 - 2);
|
||||
mResourceIds = mIn.readIntArray(chunkSize / 4 - 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunkType < CHUNK_XML_FIRST || chunkType > CHUNK_XML_LAST) {
|
||||
if (chunkType < ARSCHeader.RES_XML_FIRST_CHUNK_TYPE || chunkType > ARSCHeader.RES_XML_LAST_CHUNK_TYPE) {
|
||||
throw new IOException("Invalid chunk type (" + chunkType + ").");
|
||||
}
|
||||
|
||||
// Fake START_DOCUMENT event.
|
||||
if (chunkType == CHUNK_XML_START_TAG && event == -1) {
|
||||
m_event = START_DOCUMENT;
|
||||
if (chunkType == ARSCHeader.RES_XML_START_ELEMENT_TYPE && event == -1) {
|
||||
mEvent = START_DOCUMENT;
|
||||
break;
|
||||
}
|
||||
|
||||
// Common header.
|
||||
/* chunkSize */m_reader.skipInt();
|
||||
int lineNumber = m_reader.readInt();
|
||||
/* 0xFFFFFFFF */m_reader.skipInt();
|
||||
// Read remainder of ResXMLTree_node
|
||||
mIn.skipInt(); // chunkSize
|
||||
mLineNumber = mIn.readInt();
|
||||
mIn.skipInt(); // Optional XML Comment
|
||||
|
||||
if (chunkType == CHUNK_XML_START_NAMESPACE || chunkType == CHUNK_XML_END_NAMESPACE) {
|
||||
if (chunkType == CHUNK_XML_START_NAMESPACE) {
|
||||
int prefix = m_reader.readInt();
|
||||
int uri = m_reader.readInt();
|
||||
m_namespaces.push(prefix, uri);
|
||||
if (chunkType == ARSCHeader.RES_XML_START_NAMESPACE_TYPE || chunkType == ARSCHeader.RES_XML_END_NAMESPACE_TYPE) {
|
||||
if (chunkType == ARSCHeader.RES_XML_START_NAMESPACE_TYPE) {
|
||||
int prefix = mIn.readInt();
|
||||
int uri = mIn.readInt();
|
||||
mNamespaces.push(prefix, uri);
|
||||
} else {
|
||||
/* prefix */m_reader.skipInt();
|
||||
/* uri */m_reader.skipInt();
|
||||
m_namespaces.pop();
|
||||
mIn.skipInt(); // prefix
|
||||
mIn.skipInt(); // uri
|
||||
mNamespaces.pop();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
m_lineNumber = lineNumber;
|
||||
|
||||
if (chunkType == CHUNK_XML_START_TAG) {
|
||||
m_namespaceUri = m_reader.readInt();
|
||||
m_name = m_reader.readInt();
|
||||
/* flags? */m_reader.skipInt();
|
||||
int attributeCount = m_reader.readInt();
|
||||
m_idAttribute = (attributeCount >>> 16) - 1;
|
||||
attributeCount &= 0xFFFF;
|
||||
m_classAttribute = m_reader.readInt();
|
||||
m_styleAttribute = (m_classAttribute >>> 16) - 1;
|
||||
m_classAttribute = (m_classAttribute & 0xFFFF) - 1;
|
||||
m_attributes = m_reader.readIntArray(attributeCount * ATTRIBUTE_LENGTH);
|
||||
for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < m_attributes.length; ) {
|
||||
m_attributes[i] = (m_attributes[i] >>> 24);
|
||||
if (chunkType == ARSCHeader.RES_XML_START_ELEMENT_TYPE) {
|
||||
mNamespaceIndex = mIn.readInt();
|
||||
mNameIndex = mIn.readInt();
|
||||
mIn.skipShort(); // attributeStart
|
||||
int attributeSize = mIn.readShort();
|
||||
int attributeCount = mIn.readShort();
|
||||
mIdIndex = mIn.readShort();
|
||||
mClassIndex = mIn.readShort();
|
||||
mStyleIndex = mIn.readShort();
|
||||
mAttributes = mIn.readIntArray(attributeCount * ATTRIBUTE_LENGTH);
|
||||
for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < mAttributes.length; ) {
|
||||
mAttributes[i] = (mAttributes[i] >>> 24);
|
||||
i += ATTRIBUTE_LENGTH;
|
||||
}
|
||||
m_namespaces.increaseDepth();
|
||||
m_event = START_TAG;
|
||||
|
||||
int byteAttrSizeRead = (attributeCount * ATTRIBUTE_LENGTH) * 4;
|
||||
int byteAttrSizeReported = (attributeSize * attributeCount);
|
||||
|
||||
// Check for misleading chunk sizes
|
||||
if (byteAttrSizeRead < byteAttrSizeReported) {
|
||||
int bytesToSkip = byteAttrSizeReported - byteAttrSizeRead;
|
||||
mIn.skipBytes(bytesToSkip);
|
||||
LOGGER.fine("Skipping " + bytesToSkip + " unknown bytes in attributes area.");
|
||||
}
|
||||
|
||||
mNamespaces.increaseDepth();
|
||||
mEvent = START_TAG;
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunkType == CHUNK_XML_END_TAG) {
|
||||
m_namespaceUri = m_reader.readInt();
|
||||
m_name = m_reader.readInt();
|
||||
m_event = END_TAG;
|
||||
if (chunkType == ARSCHeader.RES_XML_END_ELEMENT_TYPE) {
|
||||
mNamespaceIndex = mIn.readInt();
|
||||
mNameIndex = mIn.readInt();
|
||||
mEvent = END_TAG;
|
||||
m_decreaseDepth = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunkType == CHUNK_XML_TEXT) {
|
||||
m_name = m_reader.readInt();
|
||||
/* ? */m_reader.skipInt();
|
||||
/* ? */m_reader.skipInt();
|
||||
m_event = TEXT;
|
||||
if (chunkType == ARSCHeader.RES_XML_CDATA_TYPE) {
|
||||
mNameIndex = mIn.readInt();
|
||||
mIn.skipInt();
|
||||
mIn.skipInt();
|
||||
mEvent = TEXT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -765,40 +769,36 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
}
|
||||
}
|
||||
|
||||
private ExtDataInput m_reader;
|
||||
private ExtDataInput mIn;
|
||||
private ResAttrDecoder mAttrDecoder;
|
||||
private AndrolibException mFirstError;
|
||||
|
||||
private boolean m_operational = false;
|
||||
private StringBlock m_strings;
|
||||
private int[] m_resourceIDs;
|
||||
private final NamespaceStack m_namespaces = new NamespaceStack();
|
||||
private final String android_ns = "http://schemas.android.com/apk/res/android";
|
||||
private boolean isOperational = false;
|
||||
private StringBlock mStringBlock;
|
||||
private int[] mResourceIds;
|
||||
private final NamespaceStack mNamespaces = new NamespaceStack();
|
||||
private boolean m_decreaseDepth;
|
||||
|
||||
// All values are essentially indices, e.g. m_name is an index of name in m_strings.
|
||||
private int m_event;
|
||||
private int m_lineNumber;
|
||||
private int m_name;
|
||||
private int m_namespaceUri;
|
||||
private int[] m_attributes;
|
||||
private int m_idAttribute;
|
||||
private int m_classAttribute;
|
||||
private int m_styleAttribute;
|
||||
// All values are essentially indices, e.g. mNameIndex is an index of name in mStringBlock.
|
||||
private int mEvent;
|
||||
private int mLineNumber;
|
||||
private int mNameIndex;
|
||||
private int mNamespaceIndex;
|
||||
private int[] mAttributes;
|
||||
private int mIdIndex;
|
||||
private int mClassIndex;
|
||||
private int mStyleIndex;
|
||||
|
||||
private final static Logger LOGGER = 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_LENGTH = 5;
|
||||
|
||||
private static final int CHUNK_AXML_FILE = 0x00080003, CHUNK_AXML_FILE_BROKEN = 0x00080001,
|
||||
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;
|
||||
// ResXMLTree_attribute
|
||||
private static final int ATTRIBUTE_IX_NAMESPACE_URI = 0; // ns
|
||||
private static final int ATTRIBUTE_IX_NAME = 1; // name
|
||||
private static final int ATTRIBUTE_IX_VALUE_STRING = 2; // rawValue
|
||||
private static final int ATTRIBUTE_IX_VALUE_TYPE = 3; // (size/res0/dataType)
|
||||
private static final int ATTRIBUTE_IX_VALUE_DATA = 4; // data
|
||||
private static final int ATTRIBUTE_LENGTH = 5;
|
||||
|
||||
private static final int PRIVATE_PKG_ID = 0x7F;
|
||||
}
|
||||
|
@ -62,6 +62,8 @@ public class ResFileDecoder {
|
||||
resFileMapping.put(inFilePath, outFilePath);
|
||||
}
|
||||
|
||||
LOGGER.fine("Decoding file: " + inFilePath + " to: " + outFilePath);
|
||||
|
||||
try {
|
||||
if (typeName.equals("raw")) {
|
||||
decode(inDir, inFilePath, outDir, outFileName, "raw");
|
||||
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
package brut.androlib.res.decoder;
|
||||
|
||||
import brut.androlib.res.data.arsc.ARSCHeader;
|
||||
import brut.androlib.res.xml.ResXmlEncoders;
|
||||
import brut.util.ExtDataInput;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
@ -29,19 +30,15 @@ import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class StringBlock {
|
||||
|
||||
/**
|
||||
* Reads whole (including chunk type) string block from stream. Stream must
|
||||
* be at the chunk type.
|
||||
* @param reader ExtDataInput
|
||||
* @return StringBlock
|
||||
*
|
||||
* @throws IOException Parsing resources.arsc error
|
||||
*/
|
||||
public static StringBlock read(ExtDataInput reader) throws IOException {
|
||||
reader.skipCheckChunkTypeInt(CHUNK_STRINGPOOL_TYPE, CHUNK_NULL_TYPE);
|
||||
public static StringBlock readWithChunk(ExtDataInput reader) throws IOException {
|
||||
reader.skipCheckShort(ARSCHeader.RES_STRING_POOL_TYPE);
|
||||
reader.skipShort(); // headerSize
|
||||
int chunkSize = reader.readInt();
|
||||
|
||||
return readWithoutChunk(reader, chunkSize);
|
||||
}
|
||||
|
||||
public static StringBlock readWithoutChunk(ExtDataInput reader, int chunkSize) throws IOException {
|
||||
// ResStringPool_header
|
||||
int stringCount = reader.readInt();
|
||||
int styleCount = reader.readInt();
|
||||
@ -277,8 +274,5 @@ public class StringBlock {
|
||||
private final CharsetDecoder CESU8_DECODER = Charset.forName("CESU8").newDecoder();
|
||||
private static final Logger LOGGER = Logger.getLogger(StringBlock.class.getName());
|
||||
|
||||
// ResChunk_header = header.type (0x0001) + header.headerSize (0x001C)
|
||||
private static final int CHUNK_STRINGPOOL_TYPE = 0x001C0001;
|
||||
private static final int CHUNK_NULL_TYPE = 0x00000000;
|
||||
private static final int UTF8_FLAG = 0x00000100;
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package brut.androlib.decode;
|
||||
|
||||
import brut.androlib.ApkDecoder;
|
||||
import brut.androlib.BaseTest;
|
||||
import brut.androlib.TestUtils;
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.util.OS;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ProtectedApkTest extends BaseTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
TestUtils.cleanFrameworkFile();
|
||||
sTmpDir = new ExtFile(OS.createTempDirectory());
|
||||
TestUtils.copyResourceDir(ProtectedApkTest.class, "decode/protected-chunks/", sTmpDir);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws BrutException {
|
||||
OS.rmdir(sTmpDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfDecodeWorksWithoutCrash() throws BrutException, IOException {
|
||||
String apk = "protected-v1.apk";
|
||||
|
||||
// decode protected-v1.apk
|
||||
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
|
||||
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
|
||||
|
||||
File outDir = new File(sTmpDir + File.separator + apk + ".out");
|
||||
apkDecoder.decode(outDir);
|
||||
|
||||
File stringsXml = new File(sTestOrigDir,"res/values/strings.xml");
|
||||
assertTrue(stringsXml.isFile());
|
||||
}
|
||||
}
|
Binary file not shown.
@ -29,7 +29,7 @@ public class ExtDataInput extends DataInputDelegate {
|
||||
|
||||
public int[] readIntArray(int length) throws IOException {
|
||||
int[] array = new int[length];
|
||||
for(int i = 0; i < length; i++) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
array[i] = readInt();
|
||||
}
|
||||
return array;
|
||||
@ -39,36 +39,20 @@ public class ExtDataInput extends DataInputDelegate {
|
||||
skipBytes(4);
|
||||
}
|
||||
|
||||
public void skipCheckInt(int expected1, int expected2) throws IOException {
|
||||
int got = readInt();
|
||||
if (got != expected1 && got != expected2) {
|
||||
throw new IOException(String.format(
|
||||
"Expected: 0x%08x or 0x%08x, got: 0x%08x", expected1, expected2, got));
|
||||
}
|
||||
public void skipShort() throws IOException {
|
||||
skipBytes(2);
|
||||
}
|
||||
|
||||
public void skipCheckShort(short expected) throws IOException {
|
||||
short got = readShort();
|
||||
if (got != expected) {
|
||||
throw new IOException(String.format(
|
||||
"Expected: 0x%08x, got: 0x%08x", expected, got));
|
||||
throw new IOException(String.format("Expected: 0x%08x, got: 0x%08x", expected, got));
|
||||
}
|
||||
}
|
||||
|
||||
public void skipCheckByte(byte expected) throws IOException {
|
||||
byte got = readByte();
|
||||
if (got != expected) {
|
||||
throw new IOException(String.format(
|
||||
"Expected: 0x%08x, got: 0x%08x", expected, got));
|
||||
}
|
||||
}
|
||||
|
||||
public void skipCheckChunkTypeInt(int expected, int possible) throws IOException {
|
||||
int got = readInt();
|
||||
|
||||
if (got == possible || got < expected) {
|
||||
skipCheckChunkTypeInt(expected, -1);
|
||||
} else if (got != expected) {
|
||||
throw new IOException(String.format("Expected: 0x%08x, got: 0x%08x", expected, got));
|
||||
}
|
||||
}
|
||||
@ -89,10 +73,9 @@ public class ExtDataInput extends DataInputDelegate {
|
||||
return total;
|
||||
}
|
||||
|
||||
public String readNullEndedString(int length, boolean fixed)
|
||||
throws IOException {
|
||||
public String readNullEndedString(int length, boolean fixed) throws IOException {
|
||||
StringBuilder string = new StringBuilder(16);
|
||||
while(length-- != 0) {
|
||||
while (length-- != 0) {
|
||||
short ch = readShort();
|
||||
if (ch == 0) {
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user