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

View File

@ -101,6 +101,10 @@ public class ResResSpec {
return mType; return mType;
} }
public boolean isDummyResSpec() {
return getName().startsWith("APKTOOL_DUMMY_");
}
public void addResource(ResResource res) throws AndrolibException { public void addResource(ResResource res) throws AndrolibException {
addResource(res, false); 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 @Override
public String toString() { public String toString() {
return mId.toString() + " " + mType.toString() + "/" + mName; return mId.toString() + " " + mType.toString() + "/" + mName;

View File

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

View File

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

View File

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