mirror of
https://github.com/revanced/Apktool.git
synced 2025-01-06 01:55:53 +01:00
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:
parent
7e803aeac9
commit
eabb7d819b
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user