mirror of
https://github.com/revanced/Apktool.git
synced 2025-01-07 10:35: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
|
// compare resources.arsc package name to the one present in AndroidManifest
|
||||||
ResPackage resPackage = resTable.getCurrentResPackage();
|
ResPackage resPackage = resTable.getCurrentResPackage();
|
||||||
String pkgOriginal = resPackage.getName();
|
String pkgOriginal = resPackage.getName();
|
||||||
String packageRenamed = resTable.getPackageRenamed();
|
String pkgRenamed = resTable.getPackageRenamed();
|
||||||
|
|
||||||
resTable.setPackageId(resPackage.getId());
|
resTable.setPackageId(resPackage.getId());
|
||||||
resTable.setPackageOriginal(pkgOriginal);
|
resTable.setPackageOriginal(pkgOriginal);
|
||||||
|
|
||||||
// 1) Check if pkgOriginal is null (empty resources.arsc)
|
// 1) Check if pkgOriginal is null (empty resources.arsc)
|
||||||
// 2) Check if pkgOriginal === mPackageRenamed
|
// 2) Check if pkgRenamed is null
|
||||||
// 3) Check if pkgOriginal is ignored via IGNORED_PACKAGES
|
// 3) Check if pkgOriginal === mPackageRenamed
|
||||||
if (pkgOriginal == null || pkgOriginal.equalsIgnoreCase(packageRenamed)
|
// 4) Check if pkgOriginal is ignored via IGNORED_PACKAGES
|
||||||
|
if (pkgOriginal == null || pkgRenamed == null || pkgOriginal.equalsIgnoreCase(pkgRenamed)
|
||||||
|| (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) {
|
|| (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) {
|
||||||
LOGGER.info("Regular manifest package...");
|
LOGGER.info("Regular manifest package...");
|
||||||
} else {
|
} 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);
|
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
|
// 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.renameManifestPackage = renamed;
|
||||||
}
|
}
|
||||||
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);
|
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);
|
||||||
|
@ -43,16 +43,22 @@ public class ARSCHeader {
|
|||||||
try {
|
try {
|
||||||
type = in.readShort();
|
type = in.readShort();
|
||||||
} catch (EOFException ex) {
|
} 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);
|
return new ARSCHeader(type, in.readShort(), in.readInt(), start);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final static short TYPE_NONE = -1;
|
public void skipChunk(ExtDataInput in) throws IOException {
|
||||||
public final static short TYPE_STRING_POOL = 0x0001;
|
in.skipBytes(chunkSize - headerSize);
|
||||||
public final static short TYPE_TABLE = 0x0002;
|
}
|
||||||
public final static short TYPE_XML = 0x0003;
|
|
||||||
|
|
||||||
|
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_PACKAGE = 0x0200;
|
||||||
public final static short XML_TYPE_TYPE = 0x0201;
|
public final static short XML_TYPE_TYPE = 0x0201;
|
||||||
public final static short XML_TYPE_SPEC_TYPE = 0x0202;
|
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 = 0x0204;
|
||||||
public final static short XML_TYPE_OVERLAY_POLICY = 0x0205;
|
public final static short XML_TYPE_OVERLAY_POLICY = 0x0205;
|
||||||
public final static short XML_TYPE_STAGED_ALIAS = 0x0206;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ARSCDecoder {
|
public class ARSCDecoder {
|
||||||
@ -50,7 +47,7 @@ public class ARSCDecoder {
|
|||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken);
|
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken);
|
||||||
ResPackage[] pkgs = decoder.readTableHeader();
|
ResPackage[] pkgs = decoder.readResourceTable();
|
||||||
return new ARSCData(pkgs, decoder.mFlagsOffsets == null
|
return new ARSCData(pkgs, decoder.mFlagsOffsets == null
|
||||||
? null
|
? null
|
||||||
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]));
|
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]));
|
||||||
@ -74,19 +71,85 @@ public class ARSCDecoder {
|
|||||||
mKeepBroken = keepBroken;
|
mKeepBroken = keepBroken;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResPackage[] readTableHeader() throws IOException, AndrolibException {
|
private ResPackage[] readResourceTable() throws IOException, AndrolibException {
|
||||||
nextChunkCheckType(ARSCHeader.TYPE_TABLE);
|
Set<ResPackage> pkgs = new LinkedHashSet<>();
|
||||||
int packageCount = mIn.readInt();
|
ResTypeSpec typeSpec;
|
||||||
|
|
||||||
mTableStrings = StringBlock.read(mIn);
|
chunkLoop:
|
||||||
ResPackage[] packages = new ResPackage[packageCount];
|
for (;;) {
|
||||||
|
nextChunk();
|
||||||
|
|
||||||
nextChunk();
|
switch (mHeader.type) {
|
||||||
for (int i = 0; i < packageCount; i++) {
|
case ARSCHeader.RES_NULL_TYPE:
|
||||||
mTypeIdOffset = 0;
|
readUnknownChunk();
|
||||||
packages[i] = readTablePackage();
|
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 {
|
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");
|
LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/1728");
|
||||||
}
|
}
|
||||||
|
|
||||||
mTypeNames = StringBlock.read(mIn);
|
mTypeNames = StringBlock.readWithChunk(mIn);
|
||||||
mSpecNames = StringBlock.read(mIn);
|
mSpecNames = StringBlock.readWithChunk(mIn);
|
||||||
|
|
||||||
mResId = id << 24;
|
mResId = id << 24;
|
||||||
mPkg = new ResPackage(mResTable, id, name);
|
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;
|
return mPkg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,8 +205,6 @@ public class ARSCDecoder {
|
|||||||
packageName = mIn.readNullEndedString(128, true);
|
packageName = mIn.readNullEndedString(128, true);
|
||||||
LOGGER.info(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId));
|
LOGGER.info(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId));
|
||||||
}
|
}
|
||||||
|
|
||||||
nextChunk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readStagedAliasSpec() throws IOException {
|
private void readStagedAliasSpec() throws IOException {
|
||||||
@ -178,8 +213,6 @@ public class ARSCDecoder {
|
|||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
LOGGER.fine(String.format("Skipping staged alias stagedId (%h) finalId: %h", mIn.readInt(), mIn.readInt()));
|
LOGGER.fine(String.format("Skipping staged alias stagedId (%h) finalId: %h", mIn.readInt(), mIn.readInt()));
|
||||||
}
|
}
|
||||||
|
|
||||||
nextChunk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readOverlaySpec() throws AndrolibException, IOException {
|
private void readOverlaySpec() throws AndrolibException, IOException {
|
||||||
@ -187,8 +220,6 @@ public class ARSCDecoder {
|
|||||||
String name = mIn.readNullEndedString(256, true);
|
String name = mIn.readNullEndedString(256, true);
|
||||||
String actor = mIn.readNullEndedString(256, true);
|
String actor = mIn.readNullEndedString(256, true);
|
||||||
LOGGER.fine(String.format("Overlay name: \"%s\", actor: \"%s\")", name, actor));
|
LOGGER.fine(String.format("Overlay name: \"%s\", actor: \"%s\")", name, actor));
|
||||||
|
|
||||||
nextChunk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readOverlayPolicySpec() throws AndrolibException, IOException {
|
private void readOverlayPolicySpec() throws AndrolibException, IOException {
|
||||||
@ -199,48 +230,13 @@ public class ARSCDecoder {
|
|||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
LOGGER.fine(String.format("Skipping overlay (%h)", mIn.readInt()));
|
LOGGER.fine(String.format("Skipping overlay (%h)", mIn.readInt()));
|
||||||
}
|
}
|
||||||
|
|
||||||
nextChunk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readTableTypeSpec() throws AndrolibException, IOException {
|
private ResTypeSpec readTableSpecType() 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 {
|
|
||||||
checkChunkType(ARSCHeader.XML_TYPE_SPEC_TYPE);
|
checkChunkType(ARSCHeader.XML_TYPE_SPEC_TYPE);
|
||||||
int id = mIn.readUnsignedByte();
|
int id = mIn.readUnsignedByte();
|
||||||
mIn.skipBytes(3);
|
mIn.skipBytes(1); // reserved0
|
||||||
|
mIn.skipBytes(2); // reserved1
|
||||||
int entryCount = mIn.readInt();
|
int entryCount = mIn.readInt();
|
||||||
|
|
||||||
if (mFlagsOffsets != null) {
|
if (mFlagsOffsets != null) {
|
||||||
@ -250,6 +246,7 @@ public class ARSCDecoder {
|
|||||||
mIn.skipBytes(entryCount * 4); // flags
|
mIn.skipBytes(entryCount * 4); // flags
|
||||||
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
|
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
|
||||||
mPkg.addType(mTypeSpec);
|
mPkg.addType(mTypeSpec);
|
||||||
|
|
||||||
return mTypeSpec;
|
return mTypeSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,6 +308,12 @@ public class ARSCDecoder {
|
|||||||
readEntry(readEntryData());
|
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;
|
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 ExtDataInput mIn;
|
||||||
private final ResTable mResTable;
|
private final ResTable mResTable;
|
||||||
private final CountingInputStream mCountIn;
|
private final CountingInputStream mCountIn;
|
||||||
|
@ -20,6 +20,7 @@ import android.content.res.XmlResourceParser;
|
|||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.res.data.ResID;
|
import brut.androlib.res.data.ResID;
|
||||||
|
import brut.androlib.res.data.arsc.ARSCHeader;
|
||||||
import brut.androlib.res.data.axml.NamespaceStack;
|
import brut.androlib.res.data.axml.NamespaceStack;
|
||||||
import brut.androlib.res.xml.ResXmlEncoders;
|
import brut.androlib.res.xml.ResXmlEncoders;
|
||||||
import brut.util.ExtDataInput;
|
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 need to explicitly cast to DataInput as otherwise the constructor is ambiguous.
|
||||||
// We choose DataInput instead of InputStream as ExtDataInput wraps an InputStream in
|
// We choose DataInput instead of InputStream as ExtDataInput wraps an InputStream in
|
||||||
// a DataInputStream which is big-endian and ignores the little-endian behavior.
|
// 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
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (!m_operational) {
|
if (!isOperational) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_operational = false;
|
isOperational = false;
|
||||||
m_reader = null;
|
mIn = null;
|
||||||
m_strings = null;
|
mStringBlock = null;
|
||||||
m_resourceIDs = null;
|
mResourceIds = null;
|
||||||
m_namespaces.reset();
|
mNamespaces.reset();
|
||||||
resetEventInfo();
|
resetEventInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int next() throws XmlPullParserException, IOException {
|
public int next() throws XmlPullParserException, IOException {
|
||||||
if (m_reader == null) {
|
if (mIn == null) {
|
||||||
throw new XmlPullParserException("Parser is not opened.", this, null);
|
throw new XmlPullParserException("Parser is not opened.", this, null);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
doNext();
|
doNext();
|
||||||
return m_event;
|
return mEvent;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
close();
|
close();
|
||||||
throw e;
|
throw e;
|
||||||
@ -134,8 +135,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void require(int type, String namespace, String name)
|
public void require(int type, String namespace, String name) throws XmlPullParserException {
|
||||||
throws XmlPullParserException, IOException {
|
|
||||||
if (type != getEventType() || (namespace != null && !namespace.equals(getNamespace()))
|
if (type != getEventType() || (namespace != null && !namespace.equals(getNamespace()))
|
||||||
|| (name != null && !name.equals(getName()))) {
|
|| (name != null && !name.equals(getName()))) {
|
||||||
throw new XmlPullParserException(TYPES[type] + " is expected.", this, null);
|
throw new XmlPullParserException(TYPES[type] + " is expected.", this, null);
|
||||||
@ -144,33 +144,33 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDepth() {
|
public int getDepth() {
|
||||||
return m_namespaces.getDepth() - 1;
|
return mNamespaces.getDepth() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getEventType(){
|
public int getEventType(){
|
||||||
return m_event;
|
return mEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLineNumber() {
|
public int getLineNumber() {
|
||||||
return m_lineNumber;
|
return mLineNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
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 null;
|
||||||
}
|
}
|
||||||
return m_strings.getString(m_name);
|
return mStringBlock.getString(mNameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getText() {
|
public String getText() {
|
||||||
if (m_name == -1 || m_event != TEXT) {
|
if (mNameIndex == -1 || mEvent != TEXT) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return m_strings.getString(m_name);
|
return mStringBlock.getString(mNameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -188,13 +188,13 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getNamespace() {
|
public String getNamespace() {
|
||||||
return m_strings.getString(m_namespaceUri);
|
return mStringBlock.getString(mNamespaceIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPrefix() {
|
public String getPrefix() {
|
||||||
int prefix = m_namespaces.findPrefix(m_namespaceUri);
|
int prefix = mNamespaces.findPrefix(mNamespaceIndex);
|
||||||
return m_strings.getString(prefix);
|
return mStringBlock.getString(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -204,89 +204,89 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getNamespaceCount(int depth) {
|
public int getNamespaceCount(int depth) {
|
||||||
return m_namespaces.getAccumulatedCount(depth);
|
return mNamespaces.getAccumulatedCount(depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getNamespacePrefix(int pos) {
|
public String getNamespacePrefix(int pos) {
|
||||||
int prefix = m_namespaces.getPrefix(pos);
|
int prefix = mNamespaces.getPrefix(pos);
|
||||||
return m_strings.getString(prefix);
|
return mStringBlock.getString(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getNamespaceUri(int pos) {
|
public String getNamespaceUri(int pos) {
|
||||||
int uri = m_namespaces.getUri(pos);
|
int uri = mNamespaces.getUri(pos);
|
||||||
return m_strings.getString(uri);
|
return mStringBlock.getString(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClassAttribute() {
|
public String getClassAttribute() {
|
||||||
if (m_classAttribute == -1) {
|
if (mClassIndex == -1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int offset = getAttributeOffset(m_classAttribute);
|
int offset = getAttributeOffset(mClassIndex);
|
||||||
int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
int value = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
||||||
return m_strings.getString(value);
|
return mStringBlock.getString(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIdAttribute() {
|
public String getIdAttribute() {
|
||||||
if (m_idAttribute == -1) {
|
if (mIdIndex == -1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int offset = getAttributeOffset(m_idAttribute);
|
int offset = getAttributeOffset(mIdIndex);
|
||||||
int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
int value = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
||||||
return m_strings.getString(value);
|
return mStringBlock.getString(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getIdAttributeResourceValue(int defaultValue) {
|
public int getIdAttributeResourceValue(int defaultValue) {
|
||||||
if (m_idAttribute == -1) {
|
if (mIdIndex == -1) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
int offset = getAttributeOffset(m_idAttribute);
|
int offset = getAttributeOffset(mIdIndex);
|
||||||
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
if (valueType != TypedValue.TYPE_REFERENCE) {
|
if (valueType != TypedValue.TYPE_REFERENCE) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getStyleAttribute() {
|
public int getStyleAttribute() {
|
||||||
if (m_styleAttribute == -1) {
|
if (mStyleIndex == -1) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int offset = getAttributeOffset(m_styleAttribute);
|
int offset = getAttributeOffset(mStyleIndex);
|
||||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAttributeCount() {
|
public int getAttributeCount() {
|
||||||
if (m_event != START_TAG) {
|
if (mEvent != START_TAG) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return m_attributes.length / ATTRIBUTE_LENGTH;
|
return mAttributes.length / ATTRIBUTE_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttributeNamespace(int index) {
|
public String getAttributeNamespace(int index) {
|
||||||
int offset = getAttributeOffset(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) {
|
if (namespace == -1) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minifiers like removing the namespace, so we will default to default namespace
|
// 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.
|
// 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) {
|
if (value == null || value.length() == 0) {
|
||||||
ResID resId = new ResID(getAttributeNameResource(index));
|
ResID resId = new ResID(getAttributeNameResource(index));
|
||||||
if (resId.pkgId == PRIVATE_PKG_ID) {
|
if (resId.pkgId == PRIVATE_PKG_ID) {
|
||||||
value = getNonDefaultNamespaceUri(offset);
|
value = getNonDefaultNamespaceUri(offset);
|
||||||
} else {
|
} 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) {
|
private String getNonDefaultNamespaceUri(int offset) {
|
||||||
String prefix = m_strings.getString(m_namespaces.getPrefix(offset));
|
String prefix = mStringBlock.getString(mNamespaces.getPrefix(offset));
|
||||||
if (prefix != null) {
|
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.
|
// 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
|
@Override
|
||||||
public String getAttributePrefix(int index) {
|
public String getAttributePrefix(int index) {
|
||||||
int offset = getAttributeOffset(index);
|
int offset = getAttributeOffset(index);
|
||||||
int uri = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
|
int uri = mAttributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
|
||||||
int prefix = m_namespaces.findPrefix(uri);
|
int prefix = mNamespaces.findPrefix(uri);
|
||||||
if (prefix == -1) {
|
if (prefix == -1) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return m_strings.getString(prefix);
|
return mStringBlock.getString(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttributeName(int index) {
|
public String getAttributeName(int index) {
|
||||||
int offset = getAttributeOffset(index);
|
int offset = getAttributeOffset(index);
|
||||||
int name = m_attributes[offset + ATTRIBUTE_IX_NAME];
|
int name = mAttributes[offset + ATTRIBUTE_IX_NAME];
|
||||||
if (name == -1) {
|
if (name == -1) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
String resourceMapValue;
|
String resourceMapValue;
|
||||||
String stringBlockValue = m_strings.getString(name);
|
String stringBlockValue = mStringBlock.getString(name);
|
||||||
int resourceId = getAttributeNameResource(index);
|
int resourceId = getAttributeNameResource(index);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -358,35 +358,35 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
@Override
|
@Override
|
||||||
public int getAttributeNameResource(int index) {
|
public int getAttributeNameResource(int index) {
|
||||||
int offset = getAttributeOffset(index);
|
int offset = getAttributeOffset(index);
|
||||||
int name = m_attributes[offset + ATTRIBUTE_IX_NAME];
|
int name = mAttributes[offset + ATTRIBUTE_IX_NAME];
|
||||||
if (m_resourceIDs == null || name < 0 || name >= m_resourceIDs.length) {
|
if (mResourceIds == null || name < 0 || name >= mResourceIds.length) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return m_resourceIDs[name];
|
return mResourceIds[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAttributeValueType(int index) {
|
public int getAttributeValueType(int index) {
|
||||||
int offset = getAttributeOffset(index);
|
int offset = getAttributeOffset(index);
|
||||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
return mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAttributeValueData(int index) {
|
public int getAttributeValueData(int index) {
|
||||||
int offset = getAttributeOffset(index);
|
int offset = getAttributeOffset(index);
|
||||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttributeValue(int index) {
|
public String getAttributeValue(int index) {
|
||||||
int offset = getAttributeOffset(index);
|
int offset = getAttributeOffset(index);
|
||||||
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
int valueData = mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||||
int valueRaw = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
int valueRaw = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
||||||
|
|
||||||
if (mAttrDecoder != null) {
|
if (mAttrDecoder != null) {
|
||||||
try {
|
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 resourceMapValue = mAttrDecoder.decodeFromResourceId(valueData);
|
||||||
String value = stringBlockValue;
|
String value = stringBlockValue;
|
||||||
|
|
||||||
@ -431,9 +431,9 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
@Override
|
@Override
|
||||||
public float getAttributeFloatValue(int index, float defaultValue) {
|
public float getAttributeFloatValue(int index, float defaultValue) {
|
||||||
int offset = getAttributeOffset(index);
|
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) {
|
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 Float.intBitsToFloat(valueData);
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
@ -442,9 +442,9 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
@Override
|
@Override
|
||||||
public int getAttributeIntValue(int index, int defaultValue) {
|
public int getAttributeIntValue(int index, int defaultValue) {
|
||||||
int offset = getAttributeOffset(index);
|
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) {
|
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;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
@ -457,9 +457,9 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
@Override
|
@Override
|
||||||
public int getAttributeResourceValue(int index, int defaultValue) {
|
public int getAttributeResourceValue(int index, int defaultValue) {
|
||||||
int offset = getAttributeOffset(index);
|
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) {
|
if (valueType == TypedValue.TYPE_REFERENCE) {
|
||||||
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
@ -520,13 +520,11 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAttributeListValue(int index, String[] options, int defaultValue) {
|
public int getAttributeListValue(int index, String[] options, int defaultValue) {
|
||||||
// TODO implement
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) {
|
public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) {
|
||||||
// TODO implement
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,8 +585,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setProperty(String name, Object value)
|
public void setProperty(String name, Object value) throws XmlPullParserException {
|
||||||
throws XmlPullParserException {
|
|
||||||
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -598,34 +595,33 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFeature(String name, boolean value)
|
public void setFeature(String name, boolean value) throws XmlPullParserException {
|
||||||
throws XmlPullParserException {
|
|
||||||
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getAttributeOffset(int index) {
|
private int getAttributeOffset(int index) {
|
||||||
if (m_event != START_TAG) {
|
if (mEvent != START_TAG) {
|
||||||
throw new IndexOutOfBoundsException("Current event is not START_TAG.");
|
throw new IndexOutOfBoundsException("Current event is not START_TAG.");
|
||||||
}
|
}
|
||||||
int offset = index * ATTRIBUTE_LENGTH;
|
int offset = index * ATTRIBUTE_LENGTH;
|
||||||
if (offset >= m_attributes.length) {
|
if (offset >= mAttributes.length) {
|
||||||
throw new IndexOutOfBoundsException("Invalid attribute index (" + index + ").");
|
throw new IndexOutOfBoundsException("Invalid attribute index (" + index + ").");
|
||||||
}
|
}
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int findAttribute(String namespace, String attribute) {
|
private int findAttribute(String namespace, String attribute) {
|
||||||
if (m_strings == null || attribute == null) {
|
if (mStringBlock == null || attribute == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int name = m_strings.find(attribute);
|
int name = mStringBlock.find(attribute);
|
||||||
if (name == -1) {
|
if (name == -1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int uri = (namespace != null) ? m_strings.find(namespace) : -1;
|
int uri = (namespace != null) ? mStringBlock.find(namespace) : -1;
|
||||||
for (int o = 0; o != m_attributes.length; o += ATTRIBUTE_LENGTH) {
|
for (int o = 0; o != mAttributes.length; o += ATTRIBUTE_LENGTH) {
|
||||||
if (name == m_attributes[o + ATTRIBUTE_IX_NAME]
|
if (name == mAttributes[o + ATTRIBUTE_IX_NAME]
|
||||||
&& (uri == -1 || uri == m_attributes[o + ATTRIBUTE_IX_NAMESPACE_URI])) {
|
&& (uri == -1 || uri == mAttributes[o + ATTRIBUTE_IX_NAMESPACE_URI])) {
|
||||||
return o / ATTRIBUTE_LENGTH;
|
return o / ATTRIBUTE_LENGTH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -633,127 +629,135 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void resetEventInfo() {
|
private void resetEventInfo() {
|
||||||
m_event = -1;
|
mEvent = -1;
|
||||||
m_lineNumber = -1;
|
mLineNumber = -1;
|
||||||
m_name = -1;
|
mNameIndex = -1;
|
||||||
m_namespaceUri = -1;
|
mNamespaceIndex = -1;
|
||||||
m_attributes = null;
|
mAttributes = null;
|
||||||
m_idAttribute = -1;
|
mIdIndex = -1;
|
||||||
m_classAttribute = -1;
|
mClassIndex = -1;
|
||||||
m_styleAttribute = -1;
|
mStyleIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doNext() throws IOException {
|
private void doNext() throws IOException {
|
||||||
// Delayed initialization.
|
if (mStringBlock == null) {
|
||||||
if (m_strings == null) {
|
mIn.skipInt(); // XML Chunk AXML Type
|
||||||
m_reader.skipCheckInt(CHUNK_AXML_FILE, CHUNK_AXML_FILE_BROKEN);
|
mIn.skipInt(); // Chunk Size
|
||||||
|
|
||||||
// chunkSize
|
mStringBlock = StringBlock.readWithChunk(mIn);
|
||||||
m_reader.skipInt();
|
mNamespaces.increaseDepth();
|
||||||
m_strings = StringBlock.read(m_reader);
|
isOperational = true;
|
||||||
m_namespaces.increaseDepth();
|
|
||||||
m_operational = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_event == END_DOCUMENT) {
|
if (mEvent == END_DOCUMENT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int event = m_event;
|
int event = mEvent;
|
||||||
resetEventInfo();
|
resetEventInfo();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (m_decreaseDepth) {
|
if (m_decreaseDepth) {
|
||||||
m_decreaseDepth = false;
|
m_decreaseDepth = false;
|
||||||
m_namespaces.decreaseDepth();
|
mNamespaces.decreaseDepth();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fake END_DOCUMENT event.
|
// Fake END_DOCUMENT event.
|
||||||
if (event == END_TAG && m_namespaces.getDepth() == 1 && m_namespaces.getCurrentCount() == 0) {
|
if (event == END_TAG && mNamespaces.getDepth() == 1 && mNamespaces.getCurrentCount() == 0) {
|
||||||
m_event = END_DOCUMENT;
|
mEvent = END_DOCUMENT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int chunkType;
|
int chunkType;
|
||||||
if (event == START_DOCUMENT) {
|
if (event == START_DOCUMENT) {
|
||||||
// Fake event, see CHUNK_XML_START_TAG handler.
|
// Fake event, see CHUNK_XML_START_TAG handler.
|
||||||
chunkType = CHUNK_XML_START_TAG;
|
chunkType = ARSCHeader.RES_XML_START_ELEMENT_TYPE;
|
||||||
} else {
|
} else {
|
||||||
chunkType = m_reader.readInt();
|
chunkType = mIn.readShort();
|
||||||
|
mIn.skipShort(); // headerSize
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunkType == CHUNK_RESOURCEIDS) {
|
if (chunkType == ARSCHeader.RES_XML_RESOURCE_MAP_TYPE) {
|
||||||
int chunkSize = m_reader.readInt();
|
int chunkSize = mIn.readInt();
|
||||||
if (chunkSize < 8 || (chunkSize % 4) != 0) {
|
if (chunkSize < 8 || (chunkSize % 4) != 0) {
|
||||||
throw new IOException("Invalid resource ids size (" + chunkSize + ").");
|
throw new IOException("Invalid resource ids size (" + chunkSize + ").");
|
||||||
}
|
}
|
||||||
m_resourceIDs = m_reader.readIntArray(chunkSize / 4 - 2);
|
mResourceIds = mIn.readIntArray(chunkSize / 4 - 2);
|
||||||
continue;
|
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 + ").");
|
throw new IOException("Invalid chunk type (" + chunkType + ").");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fake START_DOCUMENT event.
|
// Fake START_DOCUMENT event.
|
||||||
if (chunkType == CHUNK_XML_START_TAG && event == -1) {
|
if (chunkType == ARSCHeader.RES_XML_START_ELEMENT_TYPE && event == -1) {
|
||||||
m_event = START_DOCUMENT;
|
mEvent = START_DOCUMENT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common header.
|
// Read remainder of ResXMLTree_node
|
||||||
/* chunkSize */m_reader.skipInt();
|
mIn.skipInt(); // chunkSize
|
||||||
int lineNumber = m_reader.readInt();
|
mLineNumber = mIn.readInt();
|
||||||
/* 0xFFFFFFFF */m_reader.skipInt();
|
mIn.skipInt(); // Optional XML Comment
|
||||||
|
|
||||||
if (chunkType == CHUNK_XML_START_NAMESPACE || chunkType == CHUNK_XML_END_NAMESPACE) {
|
if (chunkType == ARSCHeader.RES_XML_START_NAMESPACE_TYPE || chunkType == ARSCHeader.RES_XML_END_NAMESPACE_TYPE) {
|
||||||
if (chunkType == CHUNK_XML_START_NAMESPACE) {
|
if (chunkType == ARSCHeader.RES_XML_START_NAMESPACE_TYPE) {
|
||||||
int prefix = m_reader.readInt();
|
int prefix = mIn.readInt();
|
||||||
int uri = m_reader.readInt();
|
int uri = mIn.readInt();
|
||||||
m_namespaces.push(prefix, uri);
|
mNamespaces.push(prefix, uri);
|
||||||
} else {
|
} else {
|
||||||
/* prefix */m_reader.skipInt();
|
mIn.skipInt(); // prefix
|
||||||
/* uri */m_reader.skipInt();
|
mIn.skipInt(); // uri
|
||||||
m_namespaces.pop();
|
mNamespaces.pop();
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_lineNumber = lineNumber;
|
|
||||||
|
|
||||||
if (chunkType == CHUNK_XML_START_TAG) {
|
if (chunkType == ARSCHeader.RES_XML_START_ELEMENT_TYPE) {
|
||||||
m_namespaceUri = m_reader.readInt();
|
mNamespaceIndex = mIn.readInt();
|
||||||
m_name = m_reader.readInt();
|
mNameIndex = mIn.readInt();
|
||||||
/* flags? */m_reader.skipInt();
|
mIn.skipShort(); // attributeStart
|
||||||
int attributeCount = m_reader.readInt();
|
int attributeSize = mIn.readShort();
|
||||||
m_idAttribute = (attributeCount >>> 16) - 1;
|
int attributeCount = mIn.readShort();
|
||||||
attributeCount &= 0xFFFF;
|
mIdIndex = mIn.readShort();
|
||||||
m_classAttribute = m_reader.readInt();
|
mClassIndex = mIn.readShort();
|
||||||
m_styleAttribute = (m_classAttribute >>> 16) - 1;
|
mStyleIndex = mIn.readShort();
|
||||||
m_classAttribute = (m_classAttribute & 0xFFFF) - 1;
|
mAttributes = mIn.readIntArray(attributeCount * ATTRIBUTE_LENGTH);
|
||||||
m_attributes = m_reader.readIntArray(attributeCount * ATTRIBUTE_LENGTH);
|
for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < mAttributes.length; ) {
|
||||||
for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < m_attributes.length; ) {
|
mAttributes[i] = (mAttributes[i] >>> 24);
|
||||||
m_attributes[i] = (m_attributes[i] >>> 24);
|
|
||||||
i += ATTRIBUTE_LENGTH;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunkType == CHUNK_XML_END_TAG) {
|
if (chunkType == ARSCHeader.RES_XML_END_ELEMENT_TYPE) {
|
||||||
m_namespaceUri = m_reader.readInt();
|
mNamespaceIndex = mIn.readInt();
|
||||||
m_name = m_reader.readInt();
|
mNameIndex = mIn.readInt();
|
||||||
m_event = END_TAG;
|
mEvent = END_TAG;
|
||||||
m_decreaseDepth = true;
|
m_decreaseDepth = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunkType == CHUNK_XML_TEXT) {
|
if (chunkType == ARSCHeader.RES_XML_CDATA_TYPE) {
|
||||||
m_name = m_reader.readInt();
|
mNameIndex = mIn.readInt();
|
||||||
/* ? */m_reader.skipInt();
|
mIn.skipInt();
|
||||||
/* ? */m_reader.skipInt();
|
mIn.skipInt();
|
||||||
m_event = TEXT;
|
mEvent = TEXT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -765,40 +769,36 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExtDataInput m_reader;
|
private ExtDataInput mIn;
|
||||||
private ResAttrDecoder mAttrDecoder;
|
private ResAttrDecoder mAttrDecoder;
|
||||||
private AndrolibException mFirstError;
|
private AndrolibException mFirstError;
|
||||||
|
|
||||||
private boolean m_operational = false;
|
private boolean isOperational = false;
|
||||||
private StringBlock m_strings;
|
private StringBlock mStringBlock;
|
||||||
private int[] m_resourceIDs;
|
private int[] mResourceIds;
|
||||||
private final NamespaceStack m_namespaces = new NamespaceStack();
|
private final NamespaceStack mNamespaces = new NamespaceStack();
|
||||||
private final String android_ns = "http://schemas.android.com/apk/res/android";
|
|
||||||
private boolean m_decreaseDepth;
|
private boolean m_decreaseDepth;
|
||||||
|
|
||||||
// All values are essentially indices, e.g. m_name is an index of name in m_strings.
|
// All values are essentially indices, e.g. mNameIndex is an index of name in mStringBlock.
|
||||||
private int m_event;
|
private int mEvent;
|
||||||
private int m_lineNumber;
|
private int mLineNumber;
|
||||||
private int m_name;
|
private int mNameIndex;
|
||||||
private int m_namespaceUri;
|
private int mNamespaceIndex;
|
||||||
private int[] m_attributes;
|
private int[] mAttributes;
|
||||||
private int m_idAttribute;
|
private int mIdIndex;
|
||||||
private int m_classAttribute;
|
private int mClassIndex;
|
||||||
private int m_styleAttribute;
|
private int mStyleIndex;
|
||||||
|
|
||||||
private final static Logger LOGGER = Logger.getLogger(AXmlResourceParser.class.getName());
|
private final static Logger LOGGER = Logger.getLogger(AXmlResourceParser.class.getName());
|
||||||
private static final String E_NOT_SUPPORTED = "Method is not supported.";
|
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,
|
// ResXMLTree_attribute
|
||||||
CHUNK_RESOURCEIDS = 0x00080180, CHUNK_XML_FIRST = 0x00100100,
|
private static final int ATTRIBUTE_IX_NAMESPACE_URI = 0; // ns
|
||||||
CHUNK_XML_START_NAMESPACE = 0x00100100,
|
private static final int ATTRIBUTE_IX_NAME = 1; // name
|
||||||
CHUNK_XML_END_NAMESPACE = 0x00100101,
|
private static final int ATTRIBUTE_IX_VALUE_STRING = 2; // rawValue
|
||||||
CHUNK_XML_START_TAG = 0x00100102, CHUNK_XML_END_TAG = 0x00100103,
|
private static final int ATTRIBUTE_IX_VALUE_TYPE = 3; // (size/res0/dataType)
|
||||||
CHUNK_XML_TEXT = 0x00100104, CHUNK_XML_LAST = 0x00100104;
|
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;
|
private static final int PRIVATE_PKG_ID = 0x7F;
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,8 @@ public class ResFileDecoder {
|
|||||||
resFileMapping.put(inFilePath, outFilePath);
|
resFileMapping.put(inFilePath, outFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOGGER.fine("Decoding file: " + inFilePath + " to: " + outFilePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeName.equals("raw")) {
|
if (typeName.equals("raw")) {
|
||||||
decode(inDir, inFilePath, outDir, outFileName, "raw");
|
decode(inDir, inFilePath, outDir, outFileName, "raw");
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package brut.androlib.res.decoder;
|
package brut.androlib.res.decoder;
|
||||||
|
|
||||||
|
import brut.androlib.res.data.arsc.ARSCHeader;
|
||||||
import brut.androlib.res.xml.ResXmlEncoders;
|
import brut.androlib.res.xml.ResXmlEncoders;
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
@ -29,19 +30,15 @@ import java.util.List;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class StringBlock {
|
public class StringBlock {
|
||||||
|
public static StringBlock readWithChunk(ExtDataInput reader) throws IOException {
|
||||||
/**
|
reader.skipCheckShort(ARSCHeader.RES_STRING_POOL_TYPE);
|
||||||
* Reads whole (including chunk type) string block from stream. Stream must
|
reader.skipShort(); // headerSize
|
||||||
* 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);
|
|
||||||
int chunkSize = reader.readInt();
|
int chunkSize = reader.readInt();
|
||||||
|
|
||||||
|
return readWithoutChunk(reader, chunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StringBlock readWithoutChunk(ExtDataInput reader, int chunkSize) throws IOException {
|
||||||
// ResStringPool_header
|
// ResStringPool_header
|
||||||
int stringCount = reader.readInt();
|
int stringCount = reader.readInt();
|
||||||
int styleCount = reader.readInt();
|
int styleCount = reader.readInt();
|
||||||
@ -277,8 +274,5 @@ public class StringBlock {
|
|||||||
private final CharsetDecoder CESU8_DECODER = Charset.forName("CESU8").newDecoder();
|
private final CharsetDecoder CESU8_DECODER = Charset.forName("CESU8").newDecoder();
|
||||||
private static final Logger LOGGER = Logger.getLogger(StringBlock.class.getName());
|
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;
|
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 {
|
public int[] readIntArray(int length) throws IOException {
|
||||||
int[] array = new int[length];
|
int[] array = new int[length];
|
||||||
for(int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
array[i] = readInt();
|
array[i] = readInt();
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
@ -39,36 +39,20 @@ public class ExtDataInput extends DataInputDelegate {
|
|||||||
skipBytes(4);
|
skipBytes(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void skipCheckInt(int expected1, int expected2) throws IOException {
|
public void skipShort() throws IOException {
|
||||||
int got = readInt();
|
skipBytes(2);
|
||||||
if (got != expected1 && got != expected2) {
|
|
||||||
throw new IOException(String.format(
|
|
||||||
"Expected: 0x%08x or 0x%08x, got: 0x%08x", expected1, expected2, got));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void skipCheckShort(short expected) throws IOException {
|
public void skipCheckShort(short expected) throws IOException {
|
||||||
short got = readShort();
|
short got = readShort();
|
||||||
if (got != expected) {
|
if (got != expected) {
|
||||||
throw new IOException(String.format(
|
throw new IOException(String.format("Expected: 0x%08x, got: 0x%08x", expected, got));
|
||||||
"Expected: 0x%08x, got: 0x%08x", expected, got));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void skipCheckByte(byte expected) throws IOException {
|
public void skipCheckByte(byte expected) throws IOException {
|
||||||
byte got = readByte();
|
byte got = readByte();
|
||||||
if (got != expected) {
|
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));
|
throw new IOException(String.format("Expected: 0x%08x, got: 0x%08x", expected, got));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,10 +73,9 @@ public class ExtDataInput extends DataInputDelegate {
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String readNullEndedString(int length, boolean fixed)
|
public String readNullEndedString(int length, boolean fixed) throws IOException {
|
||||||
throws IOException {
|
|
||||||
StringBuilder string = new StringBuilder(16);
|
StringBuilder string = new StringBuilder(16);
|
||||||
while(length-- != 0) {
|
while (length-- != 0) {
|
||||||
short ch = readShort();
|
short ch = readShort();
|
||||||
if (ch == 0) {
|
if (ch == 0) {
|
||||||
break;
|
break;
|
||||||
|
Loading…
Reference in New Issue
Block a user