Correctly read sparse ResourceTables.

Prior to this change, APKs usually went Package -> TypeSpec -> Config (all) -> Entries.
Reading all configs under that TypeSpec. Now we have packages that go
Package -> TypeSpec -> Config (single) -> Entries.

So we have to read this correctly to make sure we can correctly decode sparse and packed
Resource tables.
This commit is contained in:
Connor Tumbleson 2015-12-14 07:03:09 -06:00
parent 7e803aeac9
commit eabb7d819b
5 changed files with 107 additions and 25 deletions

View File

@ -156,6 +156,10 @@ public class ResPackage {
return mSynthesizedRes.contains(resId);
}
public void removeResSpec(ResResSpec spec) throws AndrolibException {
mResSpecs.remove(spec.getId());
}
public void addResSpec(ResResSpec spec) throws AndrolibException {
if (mResSpecs.put(spec.getId(), spec) != null) {
throw new AndrolibException("Multiple resource specs: " + spec);
@ -179,6 +183,9 @@ public class ResPackage {
public void addResource(ResResource res) {
}
public void removeResource(ResResource res) {
}
public void addSynthesizedRes(int resId) {
mSynthesizedRes.add(new ResID(resId));
}

View File

@ -101,6 +101,10 @@ public class ResResSpec {
return mType;
}
public boolean isDummyResSpec() {
return getName().startsWith("APKTOOL_DUMMY_");
}
public void addResource(ResResource res) throws AndrolibException {
addResource(res, false);
}
@ -114,6 +118,11 @@ public class ResResSpec {
}
}
public void removeResource(ResResource res) throws AndrolibException {
ResConfigFlags flags = res.getConfig().getFlags();
mResources.remove(flags);
}
@Override
public String toString() {
return mId.toString() + " " + mType.toString() + "/" + mName;

View File

@ -56,6 +56,11 @@ public class ResType {
addResource(res, false);
}
public void removeResource(ResResource res) throws AndrolibException {
ResResSpec spec = res.getResSpec();
mResources.remove(spec);
}
public void addResource(ResResource res, boolean overwrite)
throws AndrolibException {
ResResSpec spec = res.getResSpec();

View File

@ -30,16 +30,29 @@ public final class ResTypeSpec {
private final ResTable mResTable;
private final ResPackage mPackage;
public ResTypeSpec(String name, ResTable resTable, ResPackage package_) {
private final byte mId;
private final int mEntryCount;
public ResTypeSpec(String name, ResTable resTable, ResPackage package_, byte id, int entryCount) {
this.mName = name;
this.mResTable = resTable;
this.mPackage = package_;
this.mId = id;
this.mEntryCount = entryCount;
}
public String getName() {
return mName;
}
public byte getId() {
return mId;
}
public int getEntryCount() {
return mEntryCount;
}
public boolean isString() {
return mName.equalsIgnoreCase("string");
}
@ -57,6 +70,10 @@ public final class ResTypeSpec {
return spec;
}
public void removeResSpec(ResResSpec spec) throws AndrolibException {
mResSpecs.remove(spec.getName());
}
public void addResSpec(ResResSpec spec) throws AndrolibException {
if (mResSpecs.put(spec.getName(), spec) != null) {
throw new AndrolibException(String.format(

View File

@ -138,39 +138,59 @@ public class ARSCDecoder {
}
private ResTypeSpec readTableTypeSpec() throws AndrolibException, IOException {
mTypeSpec = readSingleTableTypeSpec();
addTypeSpec(mTypeSpec);
int type = nextChunk().type;
ResTypeSpec resTypeSpec;
while (type == Header.TYPE_SPEC_TYPE) {
resTypeSpec = readSingleTableTypeSpec();
addTypeSpec(resTypeSpec);
type = nextChunk().type;
}
while (type == Header.TYPE_TYPE) {
readTableType();
type = nextChunk().type;
addMissingResSpecs();
}
return mTypeSpec;
}
private ResTypeSpec readSingleTableTypeSpec() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_SPEC_TYPE);
byte id = mIn.readByte();
mIn.skipBytes(3);
int entryCount = mIn.readInt();
mMissingResSpecs = new boolean[entryCount];
Arrays.fill(mMissingResSpecs, true);
if (mFlagsOffsets != null) {
mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount));
}
/* flags */mIn.skipBytes(entryCount * 4);
mResId = (0xff000000 & mResId) | id << 16;
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg);
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
mPkg.addType(mTypeSpec);
while (nextChunk().type == Header.TYPE_TYPE) {
readTableType();
}
addMissingResSpecs();
return mTypeSpec;
}
private ResType readTableType() throws IOException, AndrolibException {
checkChunkType(Header.TYPE_TYPE);
/* typeId */mIn.skipBytes(1);
byte typeId = mIn.readByte();
if (mResTypeSpecs.containsKey(typeId)) {
mResId = (0xff000000 & mResId) | mResTypeSpecs.get(typeId).getId() << 16;
mTypeSpec = mResTypeSpecs.get(typeId);
}
/* res0, res1 */mIn.skipBytes(3);
int entryCount = mIn.readInt();
/* entriesStart */mIn.skipInt();
mMissingResSpecs = new boolean[entryCount];
Arrays.fill(mMissingResSpecs, true);
ResConfigFlags flags = readConfigFlags();
int[] entryOffsets = mIn.readIntArray(entryCount);
@ -214,6 +234,14 @@ public class ARSCDecoder {
ResResSpec spec;
if (mPkg.hasResSpec(resId)) {
spec = mPkg.getResSpec(resId);
if (spec.isDummyResSpec()) {
removeResSpec(spec);
spec = new ResResSpec(resId, mSpecNames.getString(specNamesId), mPkg, mTypeSpec);
mPkg.addResSpec(spec);
mTypeSpec.addResSpec(spec);
}
} else {
spec = new ResResSpec(resId, mSpecNames.getString(specNamesId), mPkg, mTypeSpec);
mPkg.addResSpec(spec);
@ -386,6 +414,10 @@ public class ARSCDecoder {
return string.toString();
}
private void addTypeSpec(ResTypeSpec resTypeSpec) {
mResTypeSpecs.put(resTypeSpec.getId(), resTypeSpec);
}
private void addMissingResSpecs() throws AndrolibException {
int resId = mResId & 0xffff0000;
@ -395,19 +427,30 @@ public class ARSCDecoder {
}
ResResSpec spec = new ResResSpec(new ResID(resId | i), String.format("APKTOOL_DUMMY_%04x", i), mPkg, mTypeSpec);
mPkg.addResSpec(spec);
mTypeSpec.addResSpec(spec);
if (mType == null) {
mType = mPkg.getOrCreateConfig(new ResConfigFlags());
// If we already have this resID dont add it again.
if (! mPkg.hasResSpec(new ResID(resId | i))) {
mPkg.addResSpec(spec);
mTypeSpec.addResSpec(spec);
if (mType == null) {
mType = mPkg.getOrCreateConfig(new ResConfigFlags());
}
ResValue value = new ResBoolValue(false, 0, null);
ResResource res = new ResResource(mType, spec, value);
mPkg.addResource(res);
mType.addResource(res);
spec.addResource(res);
}
}
}
ResValue value = new ResBoolValue(false, 0, null);
ResResource res = new ResResource(mType, spec, value);
mPkg.addResource(res);
mType.addResource(res);
spec.addResource(res);
private void removeResSpec(ResResSpec spec) throws AndrolibException {
if (mPkg.hasResSpec(spec.getId())) {
mPkg.removeResSpec(spec);
mTypeSpec.removeResSpec(spec);
}
}
@ -443,6 +486,7 @@ public class ARSCDecoder {
private ResType mType;
private int mResId;
private boolean[] mMissingResSpecs;
private HashMap<Byte, ResTypeSpec> mResTypeSpecs = new HashMap<>();
private final static short ENTRY_FLAG_COMPLEX = 0x0001;