
net.dongliu.apk.parser.parser.ResourceTableParser Maven / Gradle / Ivy
The newest version!
package net.dongliu.apk.parser.parser;
import net.dongliu.apk.parser.exception.ParserException;
import net.dongliu.apk.parser.io.TellableInputStream;
import net.dongliu.apk.parser.struct.*;
import net.dongliu.apk.parser.struct.resource.*;
import net.dongliu.apk.parser.utils.ParseUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
/**
* parse android resource table file.
* see http://justanapplication.wordpress.com/category/android/android-resources/
*
* @author dongliu
*/
public class ResourceTableParser {
/**
* By default the data in Chunks is in little-endian byte order both at runtime and when stored in files.
*/
private ByteOrder byteOrder = ByteOrder.LITTLE;
private StringPool stringPool;
private TellableInputStream in;
private ResourceTable resourceTable;
private ChunkHeader chunkHeader;
private Set locales;
public ResourceTableParser(InputStream in) {
this.in = new TellableInputStream(in, byteOrder);
this.locales = new HashSet();
}
/**
* parse resource table file.
*
* @throws IOException
*/
public void parse() throws IOException {
try {
// read resource file header.
chunkHeader = readChunkHeader();
ParseUtils.checkChunkType(ChunkType.TABLE, chunkHeader.chunkType);
ResourceTableHeader resourceTableHeader = (ResourceTableHeader) chunkHeader;
// read string pool chunk
chunkHeader = readChunkHeader();
ParseUtils.checkChunkType(ChunkType.STRING_POOL, chunkHeader.chunkType);
stringPool = ParseUtils.readStringPool(in, (StringPoolHeader) chunkHeader);
resourceTable = new ResourceTable();
resourceTable.stringPool = stringPool;
chunkHeader = readChunkHeader();
for (int i = 0; i < resourceTableHeader.packageCount; i++) {
ResourcePackage resourcePackage = readPackage((PackageHeader) chunkHeader);
resourceTable.addPackage(resourcePackage);
}
} finally {
in.close();
}
}
// read one package
private ResourcePackage readPackage(PackageHeader packageHeader) throws IOException {
//read packageHeader
ResourcePackage resourcePackage = new ResourcePackage(packageHeader);
long beginPos = in.tell();
// read type string pool
if (packageHeader.typeStrings > 0) {
in.advanceIfNotRearch(beginPos + packageHeader.typeStrings - packageHeader.headerSize);
chunkHeader = readChunkHeader();
ParseUtils.checkChunkType(ChunkType.STRING_POOL, chunkHeader.chunkType);
resourcePackage.typeStringPool = ParseUtils.readStringPool(in, (StringPoolHeader) chunkHeader);
}
//read key string pool
if (packageHeader.keyStrings > 0) {
in.advanceIfNotRearch(beginPos + packageHeader.keyStrings - packageHeader.headerSize);
chunkHeader = readChunkHeader();
ParseUtils.checkChunkType(ChunkType.STRING_POOL, chunkHeader.chunkType);
resourcePackage.keyStringPool = ParseUtils.readStringPool(in, (StringPoolHeader) chunkHeader);
}
boolean flag = true;
do {
try {
chunkHeader = readChunkHeader();
} catch (IOException e) {
//TODO: better way to detect eof
break;
}
switch (chunkHeader.chunkType) {
case ChunkType.TABLE_TYPE_SPEC:
long typeSpecChunkBegin = in.tell();
TypeSpecHeader typeSpecHeader = (TypeSpecHeader) chunkHeader;
long[] entryFlags = new long[(int) typeSpecHeader.entryCount];
for (int i = 0; i < typeSpecHeader.entryCount; i++) {
entryFlags[i] = in.readUInt();
}
TypeSpec typeSpec = new TypeSpec(typeSpecHeader);
typeSpec.entryFlags = entryFlags;
//id start from 1
typeSpec.name = resourcePackage.typeStringPool.get(typeSpecHeader.id - 1);
resourcePackage.addTypeSpec(typeSpec);
in.advanceIfNotRearch(typeSpecChunkBegin + typeSpecHeader.chunkSize -
typeSpecHeader.headerSize);
break;
case ChunkType.TABLE_TYPE:
long typeChunkBegin = in.tell();
TypeHeader typeHeader = (TypeHeader) chunkHeader;
// read offsets table
long[] offsets = new long[(int) typeHeader.entryCount];
for (int i = 0; i < typeHeader.entryCount; i++) {
offsets[i] = in.readUInt();
}
long entryPos = typeChunkBegin + typeHeader.entriesStart - typeHeader.headerSize;
in.advanceIfNotRearch(entryPos);
// read Resource Entries
ResourceEntry[] resourceEntries = new ResourceEntry[offsets.length];
for (int i = 0; i < offsets.length; i++) {
if (offsets[i] != TypeHeader.NO_ENTRY) {
in.advanceIfNotRearch(entryPos + offsets[i]);
resourceEntries[i] = readResourceEntry(resourcePackage.keyStringPool);
} else {
resourceEntries[i] = null;
}
}
Type type = new Type(typeHeader);
type.name = resourcePackage.typeStringPool.get(typeHeader.id - 1);
type.resourceEntries = resourceEntries;
resourcePackage.addType(type);
locales.add(type.locale);
in.advanceIfNotRearch(typeChunkBegin + typeHeader.chunkSize - typeHeader.headerSize);
break;
case ChunkType.TABLE_PACKAGE:
flag = false;
break;
default:
throw new ParserException("unexpected chunk type:" + chunkHeader.chunkType);
}
} while (flag);
return resourcePackage;
}
private ResourceEntry readResourceEntry(StringPool keyStringPool) throws IOException {
long beginPos = in.tell();
ResourceEntry resourceEntry = new ResourceEntry();
// size is always 8(simple), or 16(complex)
resourceEntry.size = in.readUShort();
resourceEntry.flags = in.readUShort();
long keyRef = in.readInt();
resourceEntry.key = keyStringPool.get((int) keyRef);
if ((resourceEntry.flags & ResourceEntry.FLAG_COMPLEX) != 0) {
ResourceMapEntry resourceMapEntry = new ResourceMapEntry(resourceEntry);
// Resource identifier of the parent mapping, or 0 if there is none.
resourceMapEntry.parent = in.readUInt();
resourceMapEntry.count = in.readUInt();
in.advanceIfNotRearch(beginPos + resourceEntry.size);
//An individual complex Resource entry comprises an entry immediately followed by one or more fields.
ResourceTableMap[] resourceTableMaps = new ResourceTableMap[(int) resourceMapEntry.count];
for (int i = 0; i < resourceMapEntry.count; i++) {
resourceTableMaps[i] = readResourceTableMap();
}
resourceMapEntry.resourceTableMaps = resourceTableMaps;
return resourceMapEntry;
} else {
in.advanceIfNotRearch(beginPos + resourceEntry.size);
resourceEntry.value = ParseUtils.readResValue(in, stringPool);
return resourceEntry;
}
}
private ResourceTableMap readResourceTableMap() throws IOException {
//TODO: to be implemented.
ResourceTableMap resourceTableMap = new ResourceTableMap();
resourceTableMap.nameRef = in.readUInt();
resourceTableMap.resValue = ParseUtils.readResValue(in, stringPool);
if ((resourceTableMap.nameRef & 0x02000000) != 0) {
//read arrays
parseArrays(resourceTableMap);
} else if ((resourceTableMap.nameRef & 0x01000000) != 0) {
// read attrs
parseAttrs(resourceTableMap);
} else {
}
return resourceTableMap;
}
private void parseArrays(ResourceTableMap resourceTableMap) {
}
private void parseAttrs(ResourceTableMap resourceTableMap) {
switch ((int) resourceTableMap.nameRef) {
case ResourceTableMap.MapAttr.TYPE:
// String name = "attr";
// String format;
// int i = Integer.parseInt(resourceTableMap.resValue.data);
// switch (i) {
// case ResourceTableMap.AttributeType.BOOLEAN:
// format = "bool";
// }
// break;
default:
//resourceTableMap.data = "attr:" + resourceTableMap.nameRef;
}
}
private ChunkHeader readChunkHeader() throws IOException {
int chunkType = in.readUShort();
int headSize = in.readUShort();
long chunkSize = in.readUInt();
switch (chunkType) {
case ChunkType.TABLE:
ResourceTableHeader resourceTableHeader = new ResourceTableHeader(chunkType,
headSize, chunkSize);
resourceTableHeader.packageCount = in.readUInt();
return resourceTableHeader;
case ChunkType.STRING_POOL:
StringPoolHeader stringPoolHeader = new StringPoolHeader(chunkType, headSize,
chunkSize);
stringPoolHeader.stringCount = in.readUInt();
stringPoolHeader.styleCount = in.readUInt();
stringPoolHeader.flags = in.readUInt();
stringPoolHeader.stringsStart = in.readUInt();
stringPoolHeader.stylesStart = in.readUInt();
return stringPoolHeader;
case ChunkType.TABLE_PACKAGE:
PackageHeader packageHeader = new PackageHeader(chunkType, headSize, chunkSize);
packageHeader.id = in.readUInt();
packageHeader.name = ParseUtils.readStringUTF16(in, 128);
packageHeader.typeStrings = in.readUInt();
packageHeader.lastPublicType = in.readUInt();
packageHeader.keyStrings = in.readUInt();
packageHeader.lastPublicKey = in.readUInt();
return packageHeader;
case ChunkType.TABLE_TYPE_SPEC:
TypeSpecHeader typeSpecHeader = new TypeSpecHeader(chunkType, headSize, chunkSize);
typeSpecHeader.id = in.readUByte();
typeSpecHeader.res0 = in.readUByte();
typeSpecHeader.res1 = in.readUShort();
typeSpecHeader.entryCount = in.readUInt();
return typeSpecHeader;
case ChunkType.TABLE_TYPE:
TypeHeader typeHeader = new TypeHeader(chunkType, headSize, chunkSize);
typeHeader.id = in.readUByte();
typeHeader.res0 = in.readUByte();
typeHeader.res1 = in.readUShort();
typeHeader.entryCount = in.readUInt();
typeHeader.entriesStart = in.readUInt();
typeHeader.config = readResTableConfig();
return typeHeader;
case ChunkType.NULL:
in.skip((int) (chunkSize - headSize));
default:
throw new ParserException("Unexpected chunk Type:" + Integer.toHexString(chunkType));
}
}
private ResTableConfig readResTableConfig() throws IOException {
long beginPos = in.tell();
ResTableConfig config = new ResTableConfig();
long size = in.readUInt();
in.skip(4);
//read locale
config.language = in.readChars(2);
config.country = in.readChars(2);
long endPos = in.tell();
in.skip((int) (size - (endPos - beginPos)));
return config;
}
public ResourceTable getResourceTable() {
return resourceTable;
}
public Set getLocales() {
return this.locales;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy