refactor: split child classes to own file for decoders (#3116)

This commit is contained in:
Connor Tumbleson 2023-07-02 19:44:20 -04:00 committed by GitHub
parent e85472fee8
commit ab6f1b416e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 513 additions and 406 deletions

View File

@ -17,15 +17,12 @@
package brut.androlib.res;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.CantFindFrameworkResException;
import brut.androlib.Config;
import brut.androlib.meta.MetaInfo;
import brut.androlib.meta.PackageInfo;
import brut.androlib.meta.VersionInfo;
import brut.androlib.res.data.*;
import brut.androlib.res.decoder.*;
import brut.androlib.res.decoder.ARSCDecoder.ARSCData;
import brut.androlib.res.decoder.ARSCDecoder.FlagsOffset;
import brut.androlib.res.util.ExtMXSerializer;
import brut.androlib.res.util.ExtXmlSerializer;
import brut.androlib.res.xml.ResValuesXmlSerializable;
@ -37,13 +34,8 @@ import org.apache.commons.io.IOUtils;
import org.xmlpull.v1.XmlSerializer;
import java.io.*;
import java.nio.file.Files;
import java.util.*;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
final public class AndrolibResources {
@ -53,8 +45,6 @@ final public class AndrolibResources {
private final static Logger LOGGER = Logger.getLogger(AndrolibResources.class.getName());
private File mFrameworkDirectory = null;
private ExtFile mFramework = null;
private String mMinSdkVersion = null;
@ -80,10 +70,6 @@ final public class AndrolibResources {
this.config = Config.getDefaultConfig();
}
public ResTable getResTable(ExtFile apkFile) throws AndrolibException {
return getResTable(apkFile, true);
}
public ResTable getResTable(ExtFile apkFile, boolean loadMainPkg)
throws AndrolibException {
ResTable resTable = new ResTable(this);

View File

@ -20,6 +20,8 @@ import brut.androlib.Config;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.CantFindFrameworkResException;
import brut.androlib.res.decoder.ARSCDecoder;
import brut.androlib.res.decoder.arsc.ARSCData;
import brut.androlib.res.decoder.arsc.FlagsOffset;
import brut.util.Jar;
import org.apache.commons.io.IOUtils;
@ -33,7 +35,6 @@ import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class Framework {
private final Config config;
private File mFrameworkDirectory = null;
@ -63,7 +64,7 @@ public class Framework {
in = zip.getInputStream(entry);
byte[] data = IOUtils.toByteArray(in);
ARSCDecoder.ARSCData arsc = ARSCDecoder.decode(new ByteArrayInputStream(data), true, true);
ARSCData arsc = ARSCDecoder.decode(new ByteArrayInputStream(data), true, true);
publicizeResources(data, arsc.getFlagsOffsets());
File outFile = new File(getFrameworkDirectory(), arsc
@ -140,8 +141,8 @@ public class Framework {
publicizeResources(arsc, ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true).getFlagsOffsets());
}
public void publicizeResources(byte[] arsc, ARSCDecoder.FlagsOffset[] flagsOffsets) {
for (ARSCDecoder.FlagsOffset flags : flagsOffsets) {
public void publicizeResources(byte[] arsc, FlagsOffset[] flagsOffsets) {
for (FlagsOffset flags : flagsOffsets) {
int offset = flags.offset + 3;
int end = offset + 4 * flags.count;
while (offset < end) {

View File

@ -20,13 +20,16 @@ import android.util.TypedValue;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.data.*;
import brut.androlib.res.data.value.*;
import brut.androlib.res.decoder.arsc.ARSCData;
import brut.androlib.res.decoder.arsc.ARSCHeader;
import brut.androlib.res.decoder.arsc.EntryData;
import brut.androlib.res.decoder.arsc.FlagsOffset;
import brut.util.Duo;
import brut.util.ExtDataInput;
import com.google.common.io.LittleEndianDataInputStream;
import org.apache.commons.io.input.CountingInputStream;
import java.io.DataInput;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
@ -50,7 +53,7 @@ public class ARSCDecoder {
ResPackage[] pkgs = decoder.readTableHeader();
return new ARSCData(pkgs, decoder.mFlagsOffsets == null
? null
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]), resTable);
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]));
} catch (IOException ex) {
throw new AndrolibException("Could not decode arsc file", ex);
}
@ -72,7 +75,7 @@ public class ARSCDecoder {
}
private ResPackage[] readTableHeader() throws IOException, AndrolibException {
nextChunkCheckType(Header.TYPE_TABLE);
nextChunkCheckType(ARSCHeader.TYPE_TABLE);
int packageCount = mIn.readInt();
mTableStrings = StringBlock.read(mIn);
@ -87,7 +90,7 @@ public class ARSCDecoder {
}
private ResPackage readTablePackage() throws IOException, AndrolibException {
checkChunkType(Header.XML_TYPE_PACKAGE);
checkChunkType(ARSCHeader.XML_TYPE_PACKAGE);
int id = mIn.readInt();
if (id == 0) {
@ -128,19 +131,19 @@ public class ARSCDecoder {
boolean flag = true;
while (flag) {
switch (mHeader.type) {
case Header.XML_TYPE_SPEC_TYPE:
case ARSCHeader.XML_TYPE_SPEC_TYPE:
readTableTypeSpec();
break;
case Header.XML_TYPE_LIBRARY:
case ARSCHeader.XML_TYPE_LIBRARY:
readLibraryType();
break;
case Header.XML_TYPE_OVERLAY:
case ARSCHeader.XML_TYPE_OVERLAY:
readOverlaySpec();
break;
case Header.XML_TYPE_OVERLAY_POLICY:
case ARSCHeader.XML_TYPE_OVERLAY_POLICY:
readOverlayPolicySpec();
break;
case Header.XML_TYPE_STAGED_ALIAS:
case ARSCHeader.XML_TYPE_STAGED_ALIAS:
readStagedAliasSpec();
break;
default:
@ -153,7 +156,7 @@ public class ARSCDecoder {
}
private void readLibraryType() throws AndrolibException, IOException {
checkChunkType(Header.XML_TYPE_LIBRARY);
checkChunkType(ARSCHeader.XML_TYPE_LIBRARY);
int libraryCount = mIn.readInt();
int packageId;
@ -165,7 +168,7 @@ public class ARSCDecoder {
LOGGER.info(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId));
}
while(nextChunk().type == Header.XML_TYPE_TYPE) {
while(nextChunk().type == ARSCHeader.XML_TYPE_TYPE) {
readTableTypeSpec();
}
}
@ -181,7 +184,7 @@ public class ARSCDecoder {
}
private void readOverlaySpec() throws AndrolibException, IOException {
checkChunkType(Header.XML_TYPE_OVERLAY);
checkChunkType(ARSCHeader.XML_TYPE_OVERLAY);
String name = mIn.readNullEndedString(256, true);
String actor = mIn.readNullEndedString(256, true);
LOGGER.fine(String.format("Overlay name: \"%s\", actor: \"%s\")", name, actor));
@ -190,7 +193,7 @@ public class ARSCDecoder {
}
private void readOverlayPolicySpec() throws AndrolibException, IOException {
checkChunkType(Header.XML_TYPE_OVERLAY_POLICY);
checkChunkType(ARSCHeader.XML_TYPE_OVERLAY_POLICY);
mIn.skipInt(); // policyFlags
int count = mIn.readInt();
@ -208,7 +211,7 @@ public class ARSCDecoder {
int type = nextChunk().type;
ResTypeSpec resTypeSpec;
while (type == Header.XML_TYPE_SPEC_TYPE) {
while (type == ARSCHeader.XML_TYPE_SPEC_TYPE) {
resTypeSpec = readSingleTableTypeSpec();
addTypeSpec(resTypeSpec);
type = nextChunk().type;
@ -220,7 +223,7 @@ public class ARSCDecoder {
}
}
while (type == Header.XML_TYPE_TYPE) {
while (type == ARSCHeader.XML_TYPE_TYPE) {
readTableType();
// skip "TYPE 8 chunks" and/or padding data at the end of this chunk
@ -236,7 +239,7 @@ public class ARSCDecoder {
}
private ResTypeSpec readSingleTableTypeSpec() throws AndrolibException, IOException {
checkChunkType(Header.XML_TYPE_SPEC_TYPE);
checkChunkType(ARSCHeader.XML_TYPE_SPEC_TYPE);
int id = mIn.readUnsignedByte();
mIn.skipBytes(3);
int entryCount = mIn.readInt();
@ -252,7 +255,7 @@ public class ARSCDecoder {
}
private ResType readTableType() throws IOException, AndrolibException {
checkChunkType(Header.XML_TYPE_TYPE);
checkChunkType(ARSCHeader.XML_TYPE_TYPE);
int typeId = mIn.readUnsignedByte() - mTypeIdOffset;
if (mResTypeSpecs.containsKey(typeId)) {
mResId = (0xff000000 & mResId) | mResTypeSpecs.get(typeId).getId() << 16;
@ -263,7 +266,7 @@ public class ARSCDecoder {
mIn.skipBytes(2); // reserved
int entryCount = mIn.readInt();
int entriesStart = mIn.readInt();
mMissingResSpecMap = new LinkedHashMap();
mMissingResSpecMap = new LinkedHashMap<>();
ResConfigFlags flags = readConfigFlags();
int position = (mHeader.startPosition + entriesStart) - (entryCount * 4);
@ -279,7 +282,7 @@ public class ARSCDecoder {
LOGGER.fine("Sparse type flags detected: " + mTypeSpec.getName());
}
HashMap<Integer, Integer> entryOffsetMap = new LinkedHashMap();
HashMap<Integer, Integer> entryOffsetMap = new LinkedHashMap<>();
for (int i = 0; i < entryCount; i++) {
if ((typeFlags & 0x01) != 0) {
entryOffsetMap.put(mIn.readUnsignedShort(), mIn.readUnsignedShort());
@ -596,15 +599,15 @@ public class ARSCDecoder {
}
}
private void removeResSpec(ResResSpec spec) throws AndrolibException {
private void removeResSpec(ResResSpec spec) {
if (mPkg.hasResSpec(spec.getId())) {
mPkg.removeResSpec(spec);
mTypeSpec.removeResSpec(spec);
}
}
private Header nextChunk() throws IOException {
return mHeader = Header.read(mIn, mCountIn);
private ARSCHeader nextChunk() throws IOException {
return mHeader = ARSCHeader.read(mIn, mCountIn);
}
private void checkChunkType(int expectedType) throws AndrolibException {
@ -625,7 +628,7 @@ public class ARSCDecoder {
private final List<FlagsOffset> mFlagsOffsets;
private final boolean mKeepBroken;
private Header mHeader;
private ARSCHeader mHeader;
private StringBlock mTableStrings;
private StringBlock mTypeNames;
private StringBlock mSpecNames;
@ -641,113 +644,7 @@ public class ARSCDecoder {
private final static short ENTRY_FLAG_PUBLIC = 0x0002;
private final static short ENTRY_FLAG_WEAK = 0x0004;
public static class Header {
public final short type;
public final int headerSize;
public final int chunkSize;
public final int startPosition;
public final int endPosition;
public Header(short type, int headerSize, int chunkSize, int headerStart) {
this.type = type;
this.headerSize = headerSize;
this.chunkSize = chunkSize;
this.startPosition = headerStart;
this.endPosition = headerStart + chunkSize;
}
public static Header read(ExtDataInput in, CountingInputStream countIn) throws IOException {
short type;
int start = countIn.getCount();
try {
type = in.readShort();
} catch (EOFException ex) {
return new Header(TYPE_NONE, 0, 0, countIn.getCount());
}
return new Header(type, in.readShort(), in.readInt(), start);
}
public final static short TYPE_NONE = -1;
public final static short TYPE_STRING_POOL = 0x0001;
public final static short TYPE_TABLE = 0x0002;
public final static short TYPE_XML = 0x0003;
public final static short XML_TYPE_PACKAGE = 0x0200;
public final static short XML_TYPE_TYPE = 0x0201;
public final static short XML_TYPE_SPEC_TYPE = 0x0202;
public final static short XML_TYPE_LIBRARY = 0x0203;
public final static short XML_TYPE_OVERLAY = 0x0204;
public final static short XML_TYPE_OVERLAY_POLICY = 0x0205;
public final static short XML_TYPE_STAGED_ALIAS = 0x0206;
}
public static class FlagsOffset {
public final int offset;
public final int count;
public FlagsOffset(int offset, int count) {
this.offset = offset;
this.count = count;
}
}
private class EntryData {
public short mFlags;
public int mSpecNamesId;
public ResValue mValue;
}
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
private static final int KNOWN_CONFIG_BYTES = 56;
public static class ARSCData {
public ARSCData(ResPackage[] packages, FlagsOffset[] flagsOffsets, ResTable resTable) {
mPackages = packages;
mFlagsOffsets = flagsOffsets;
mResTable = resTable;
}
public FlagsOffset[] getFlagsOffsets() {
return mFlagsOffsets;
}
public ResPackage[] getPackages() {
return mPackages;
}
public ResPackage getOnePackage() throws AndrolibException {
if (mPackages.length <= 0) {
throw new AndrolibException("Arsc file contains zero packages");
} else if (mPackages.length != 1) {
int id = findPackageWithMostResSpecs();
LOGGER.info("Arsc file contains multiple packages. Using package "
+ mPackages[id].getName() + " as default.");
return mPackages[id];
}
return mPackages[0];
}
public int findPackageWithMostResSpecs() {
int count = mPackages[0].getResSpecCount();
int id = 0;
for (int i = 0; i < mPackages.length; i++) {
if (mPackages[i].getResSpecCount() >= count) {
count = mPackages[i].getResSpecCount();
id = i;
}
}
return id;
}
public ResTable getResTable() {
return mResTable;
}
private final ResPackage[] mPackages;
private final FlagsOffset[] mFlagsOffsets;
private final ResTable mResTable;
}
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
}

View File

@ -20,6 +20,7 @@ import android.content.res.XmlResourceParser;
import android.util.TypedValue;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.data.ResID;
import brut.androlib.res.decoder.axml.NamespaceStack;
import brut.androlib.res.xml.ResXmlEncoders;
import brut.util.ExtDataInput;
import com.google.common.io.LittleEndianDataInputStream;
@ -39,8 +40,6 @@ import java.util.logging.Logger;
* open(), close(), or failed call to next(). (2) Closed state, which
* parser obtains after open(), close(), or failed call to next(). In
* this state methods return invalid values or throw exceptions.
*
* <p>TODO: * check all methods in closed state
*/
public class AXmlResourceParser implements XmlResourceParser {
@ -48,11 +47,6 @@ public class AXmlResourceParser implements XmlResourceParser {
resetEventInfo();
}
public AXmlResourceParser(InputStream stream) {
this();
open(stream);
}
public AndrolibException getFirstError() {
return mFirstError;
}
@ -88,7 +82,6 @@ public class AXmlResourceParser implements XmlResourceParser {
resetEventInfo();
}
// ///////////////////////////////// iteration
@Override
public int next() throws XmlPullParserException, IOException {
if (m_reader == null) {
@ -155,7 +148,7 @@ public class AXmlResourceParser implements XmlResourceParser {
}
@Override
public int getEventType() throws XmlPullParserException {
public int getEventType(){
return m_event;
}
@ -226,7 +219,6 @@ public class AXmlResourceParser implements XmlResourceParser {
return m_strings.getString(uri);
}
// ///////////////////////////////// attributes
@Override
public String getClassAttribute() {
if (m_classAttribute == -1) {
@ -541,7 +533,6 @@ public class AXmlResourceParser implements XmlResourceParser {
return false;
}
// ///////////////////////////////// dummies
@Override
public void setInput(InputStream stream, String inputEncoding) {
open(stream);
@ -605,189 +596,6 @@ public class AXmlResourceParser implements XmlResourceParser {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
// /////////////////////////////////////////// implementation
/**
* Namespace stack, holds prefix+uri pairs, as well as depth information.
* All information is stored in one int[] array. Array consists of depth
* frames: Data=DepthFrame*; DepthFrame=Count+[Prefix+Uri]*+Count;
* Count='count of Prefix+Uri pairs'; Yes, count is stored twice, to enable
* bottom-up traversal. increaseDepth adds depth frame, decreaseDepth
* removes it. push/pop operations operate only in current depth frame.
* decreaseDepth removes any remaining (not pop'ed) namespace pairs. findXXX
* methods search all depth frames starting from the last namespace pair of
* current depth frame. All functions that operate with int, use -1 as
* 'invalid value'.
*
* !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
*
*/
private static final class NamespaceStack {
public NamespaceStack() {
m_data = new int[32];
}
public void reset() {
m_dataLength = 0;
m_depth = 0;
}
public int getCurrentCount() {
if (m_dataLength == 0) {
return 0;
}
int offset = m_dataLength - 1;
return m_data[offset];
}
public int getAccumulatedCount(int depth) {
if (m_dataLength == 0 || depth < 0) {
return 0;
}
if (depth > m_depth) {
depth = m_depth;
}
int accumulatedCount = 0;
int offset = 0;
for (; depth != 0; --depth) {
int count = m_data[offset];
accumulatedCount += count;
offset += (2 + count * 2);
}
return accumulatedCount;
}
public void push(int prefix, int uri) {
if (m_depth == 0) {
increaseDepth();
}
ensureDataCapacity(2);
int offset = m_dataLength - 1;
int count = m_data[offset];
m_data[offset - 1 - count * 2] = count + 1;
m_data[offset] = prefix;
m_data[offset + 1] = uri;
m_data[offset + 2] = count + 1;
m_dataLength += 2;
}
public boolean pop() {
if (m_dataLength == 0) {
return false;
}
int offset = m_dataLength - 1;
int count = m_data[offset];
if (count == 0) {
return false;
}
count -= 1;
offset -= 2;
m_data[offset] = count;
offset -= (1 + count * 2);
m_data[offset] = count;
m_dataLength -= 2;
return true;
}
public int getPrefix(int index) {
return get(index, true);
}
public int getUri(int index) {
return get(index, false);
}
public int findPrefix(int uri) {
return find(uri, false);
}
public int getDepth() {
return m_depth;
}
public void increaseDepth() {
ensureDataCapacity(2);
int offset = m_dataLength;
m_data[offset] = 0;
m_data[offset + 1] = 0;
m_dataLength += 2;
m_depth += 1;
}
public void decreaseDepth() {
if (m_dataLength == 0) {
return;
}
int offset = m_dataLength - 1;
int count = m_data[offset];
if ((offset - 1 - count * 2) == 0) {
return;
}
m_dataLength -= 2 + count * 2;
m_depth -= 1;
}
private void ensureDataCapacity(int capacity) {
int available = (m_data.length - m_dataLength);
if (available > capacity) {
return;
}
int newLength = (m_data.length + available) * 2;
int[] newData = new int[newLength];
System.arraycopy(m_data, 0, newData, 0, m_dataLength);
m_data = newData;
}
private int find(int prefixOrUri, boolean prefix) {
if (m_dataLength == 0) {
return -1;
}
int offset = m_dataLength - 1;
for (int i = m_depth; i != 0; --i) {
int count = m_data[offset];
offset -= 2;
for (; count != 0; --count) {
if (prefix) {
if (m_data[offset] == prefixOrUri) {
return m_data[offset + 1];
}
} else {
if (m_data[offset + 1] == prefixOrUri) {
return m_data[offset];
}
}
offset -= 2;
}
}
return -1;
}
private int get(int index, boolean prefix) {
if (m_dataLength == 0 || index < 0) {
return -1;
}
int offset = 0;
for (int i = m_depth; i != 0; --i) {
int count = m_data[offset];
if (index >= count) {
index -= count;
offset += (2 + count * 2);
continue;
}
offset += (1 + index * 2);
if (!prefix) {
offset += 1;
}
return m_data[offset];
}
return -1;
}
private int[] m_data;
private int m_dataLength;
private int m_depth;
}
private int getAttributeOffset(int index) {
if (m_event != START_TAG) {
throw new IndexOutOfBoundsException("Current event is not START_TAG.");
@ -833,9 +641,7 @@ public class AXmlResourceParser implements XmlResourceParser {
if (m_strings == null) {
m_reader.skipCheckInt(CHUNK_AXML_FILE, CHUNK_AXML_FILE_BROKEN);
/*
* chunkSize
*/
// chunkSize
m_reader.skipInt();
m_strings = StringBlock.read(m_reader);
m_namespaces.increaseDepth();
@ -952,11 +758,6 @@ public class AXmlResourceParser implements XmlResourceParser {
}
}
// ///////////////////////////////// data
/*
* All values are essentially indices, e.g. m_name is an index of name in
* m_strings.
*/
private ExtDataInput m_reader;
private ResAttrDecoder mAttrDecoder;
private AndrolibException mFirstError;
@ -967,6 +768,8 @@ public class AXmlResourceParser implements XmlResourceParser {
private final NamespaceStack m_namespaces = new NamespaceStack();
private final String android_ns = "http://schemas.android.com/apk/res/android";
private boolean m_decreaseDepth;
// All values are essentially indices, e.g. m_name is an index of name in m_strings.
private int m_event;
private int m_lineNumber;
private int m_name;

View File

@ -18,6 +18,8 @@ package brut.androlib.res.decoder;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.CantFind9PatchChunkException;
import brut.androlib.res.decoder.ninepatch.NinePatchData;
import brut.androlib.res.decoder.ninepatch.OpticalInset;
import brut.util.ExtDataInput;
import org.apache.commons.io.IOUtils;
@ -63,7 +65,7 @@ public class Res9patchStreamDecoder implements ResStreamDecoder {
im2.createGraphics().drawImage(im, 1, 1, w, h, null);
}
NinePatch np = getNinePatch(data);
NinePatchData np = getNinePatch(data);
drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight);
drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom);
@ -122,11 +124,11 @@ public class Res9patchStreamDecoder implements ResStreamDecoder {
}
}
private NinePatch getNinePatch(byte[] data) throws AndrolibException,
private NinePatchData getNinePatch(byte[] data) throws AndrolibException,
IOException {
ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data));
find9patchChunk(di, NP_CHUNK_TYPE);
return NinePatch.decode(di);
return NinePatchData.decode(di);
}
private OpticalInset getOpticalInset(byte[] data) throws AndrolibException,
@ -169,57 +171,4 @@ public class Res9patchStreamDecoder implements ResStreamDecoder {
private static final int OI_CHUNK_TYPE = 0x6e704c62; // npLb
private static final int NP_COLOR = 0xff000000;
private static final int OI_COLOR = 0xffff0000;
private static class NinePatch {
public final int padLeft, padRight, padTop, padBottom;
public final int[] xDivs, yDivs;
public NinePatch(int padLeft, int padRight, int padTop, int padBottom,
int[] xDivs, int[] yDivs) {
this.padLeft = padLeft;
this.padRight = padRight;
this.padTop = padTop;
this.padBottom = padBottom;
this.xDivs = xDivs;
this.yDivs = yDivs;
}
public static NinePatch decode(ExtDataInput di) throws IOException {
di.skipBytes(1); // wasDeserialized
byte numXDivs = di.readByte();
byte numYDivs = di.readByte();
di.skipBytes(1); // numColors
di.skipBytes(8); // xDivs/yDivs offset
int padLeft = di.readInt();
int padRight = di.readInt();
int padTop = di.readInt();
int padBottom = di.readInt();
di.skipBytes(4); // colorsOffset
int[] xDivs = di.readIntArray(numXDivs);
int[] yDivs = di.readIntArray(numYDivs);
return new NinePatch(padLeft, padRight, padTop, padBottom, xDivs, yDivs);
}
}
private static class OpticalInset {
public final int layoutBoundsLeft, layoutBoundsTop, layoutBoundsRight, layoutBoundsBottom;
public OpticalInset(int layoutBoundsLeft, int layoutBoundsTop,
int layoutBoundsRight, int layoutBoundsBottom) {
this.layoutBoundsLeft = layoutBoundsLeft;
this.layoutBoundsTop = layoutBoundsTop;
this.layoutBoundsRight = layoutBoundsRight;
this.layoutBoundsBottom = layoutBoundsBottom;
}
public static OpticalInset decode(ExtDataInput di) throws IOException {
int layoutBoundsLeft = Integer.reverseBytes(di.readInt());
int layoutBoundsTop = Integer.reverseBytes(di.readInt());
int layoutBoundsRight = Integer.reverseBytes(di.readInt());
int layoutBoundsBottom = Integer.reverseBytes(di.readInt());
return new OpticalInset(layoutBoundsLeft, layoutBoundsTop,
layoutBoundsRight, layoutBoundsBottom);
}
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.res.decoder.arsc;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.data.ResPackage;
import java.util.logging.Logger;
public class ARSCData {
private final ResPackage[] mPackages;
private final FlagsOffset[] mFlagsOffsets;
public ARSCData(ResPackage[] packages, FlagsOffset[] flagsOffsets) {
mPackages = packages;
mFlagsOffsets = flagsOffsets;
}
public FlagsOffset[] getFlagsOffsets() {
return mFlagsOffsets;
}
public ResPackage[] getPackages() {
return mPackages;
}
public ResPackage getOnePackage() throws AndrolibException {
if (mPackages.length == 0) {
throw new AndrolibException("Arsc file contains zero packages");
} else if (mPackages.length != 1) {
int id = findPackageWithMostResSpecs();
LOGGER.info("Arsc file contains multiple packages. Using package "
+ mPackages[id].getName() + " as default.");
return mPackages[id];
}
return mPackages[0];
}
public int findPackageWithMostResSpecs() {
int count = mPackages[0].getResSpecCount();
int id = 0;
for (int i = 0; i < mPackages.length; i++) {
if (mPackages[i].getResSpecCount() >= count) {
count = mPackages[i].getResSpecCount();
id = i;
}
}
return id;
}
private static final Logger LOGGER = Logger.getLogger(ARSCData.class.getName());
}

View File

@ -0,0 +1,63 @@
/*
* 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.res.decoder.arsc;
import brut.util.ExtDataInput;
import org.apache.commons.io.input.CountingInputStream;
import java.io.EOFException;
import java.io.IOException;
public class ARSCHeader {
public final short type;
public final int headerSize;
public final int chunkSize;
public final int startPosition;
public final int endPosition;
public ARSCHeader(short type, int headerSize, int chunkSize, int headerStart) {
this.type = type;
this.headerSize = headerSize;
this.chunkSize = chunkSize;
this.startPosition = headerStart;
this.endPosition = headerStart + chunkSize;
}
public static ARSCHeader read(ExtDataInput in, CountingInputStream countIn) throws IOException {
short type;
int start = countIn.getCount();
try {
type = in.readShort();
} catch (EOFException ex) {
return new ARSCHeader(TYPE_NONE, 0, 0, countIn.getCount());
}
return new ARSCHeader(type, in.readShort(), in.readInt(), start);
}
public final static short TYPE_NONE = -1;
public final static short TYPE_STRING_POOL = 0x0001;
public final static short TYPE_TABLE = 0x0002;
public final static short TYPE_XML = 0x0003;
public final static short XML_TYPE_PACKAGE = 0x0200;
public final static short XML_TYPE_TYPE = 0x0201;
public final static short XML_TYPE_SPEC_TYPE = 0x0202;
public final static short XML_TYPE_LIBRARY = 0x0203;
public final static short XML_TYPE_OVERLAY = 0x0204;
public final static short XML_TYPE_OVERLAY_POLICY = 0x0205;
public final static short XML_TYPE_STAGED_ALIAS = 0x0206;
}

View File

@ -0,0 +1,25 @@
/*
* 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.res.decoder.arsc;
import brut.androlib.res.data.value.ResValue;
public class EntryData {
public short mFlags;
public int mSpecNamesId;
public ResValue mValue;
}

View File

@ -0,0 +1,27 @@
/*
* 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.res.decoder.arsc;
public class FlagsOffset {
public final int offset;
public final int count;
public FlagsOffset(int offset, int count) {
this.offset = offset;
this.count = count;
}
}

View File

@ -0,0 +1,197 @@
/*
* 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.res.decoder.axml;
/**
* Namespace stack, holds prefix+uri pairs, as well as depth information.
* All information is stored in one int[] array. Array consists of depth
* frames: Data=DepthFrame*; DepthFrame=Count+[Prefix+Uri]*+Count;
* Count='count of Prefix+Uri pairs'; Yes, count is stored twice, to enable
* bottom-up traversal. increaseDepth adds depth frame, decreaseDepth
* removes it. push/pop operations operate only in current depth frame.
* decreaseDepth removes any remaining (not pop'ed) namespace pairs. findXXX
* methods search all depth frames starting from the last namespace pair of
* current depth frame. All functions that operate with int, use -1 as
* 'invalid value'.
* <p>
* !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
*/
public final class NamespaceStack {
private int[] m_data;
private int m_dataLength;
private int m_depth;
public NamespaceStack() {
m_data = new int[32];
}
public void reset() {
m_dataLength = 0;
m_depth = 0;
}
public int getCurrentCount() {
if (m_dataLength == 0) {
return 0;
}
int offset = m_dataLength - 1;
return m_data[offset];
}
public int getAccumulatedCount(int depth) {
if (m_dataLength == 0 || depth < 0) {
return 0;
}
if (depth > m_depth) {
depth = m_depth;
}
int accumulatedCount = 0;
int offset = 0;
for (; depth != 0; --depth) {
int count = m_data[offset];
accumulatedCount += count;
offset += (2 + count * 2);
}
return accumulatedCount;
}
public void push(int prefix, int uri) {
if (m_depth == 0) {
increaseDepth();
}
ensureDataCapacity(2);
int offset = m_dataLength - 1;
int count = m_data[offset];
m_data[offset - 1 - count * 2] = count + 1;
m_data[offset] = prefix;
m_data[offset + 1] = uri;
m_data[offset + 2] = count + 1;
m_dataLength += 2;
}
public boolean pop() {
if (m_dataLength == 0) {
return false;
}
int offset = m_dataLength - 1;
int count = m_data[offset];
if (count == 0) {
return false;
}
count -= 1;
offset -= 2;
m_data[offset] = count;
offset -= (1 + count * 2);
m_data[offset] = count;
m_dataLength -= 2;
return true;
}
public int getPrefix(int index) {
return get(index, true);
}
public int getUri(int index) {
return get(index, false);
}
public int findPrefix(int uri) {
return find(uri, false);
}
public int getDepth() {
return m_depth;
}
public void increaseDepth() {
ensureDataCapacity(2);
int offset = m_dataLength;
m_data[offset] = 0;
m_data[offset + 1] = 0;
m_dataLength += 2;
m_depth += 1;
}
public void decreaseDepth() {
if (m_dataLength == 0) {
return;
}
int offset = m_dataLength - 1;
int count = m_data[offset];
if ((offset - 1 - count * 2) == 0) {
return;
}
m_dataLength -= 2 + count * 2;
m_depth -= 1;
}
private void ensureDataCapacity(int capacity) {
int available = (m_data.length - m_dataLength);
if (available > capacity) {
return;
}
int newLength = (m_data.length + available) * 2;
int[] newData = new int[newLength];
System.arraycopy(m_data, 0, newData, 0, m_dataLength);
m_data = newData;
}
private int find(int prefixOrUri, boolean prefix) {
if (m_dataLength == 0) {
return -1;
}
int offset = m_dataLength - 1;
for (int i = m_depth; i != 0; --i) {
int count = m_data[offset];
offset -= 2;
for (; count != 0; --count) {
if (prefix) {
if (m_data[offset] == prefixOrUri) {
return m_data[offset + 1];
}
} else {
if (m_data[offset + 1] == prefixOrUri) {
return m_data[offset];
}
}
offset -= 2;
}
}
return -1;
}
private int get(int index, boolean prefix) {
if (m_dataLength == 0 || index < 0) {
return -1;
}
int offset = 0;
for (int i = m_depth; i != 0; --i) {
int count = m_data[offset];
if (index >= count) {
index -= count;
offset += (2 + count * 2);
continue;
}
offset += (1 + index * 2);
if (!prefix) {
offset += 1;
}
return m_data[offset];
}
return -1;
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.res.decoder.ninepatch;
import brut.util.ExtDataInput;
import java.io.IOException;
public class NinePatchData
{
public final int padLeft, padRight, padTop, padBottom;
public final int[] xDivs, yDivs;
public NinePatchData(int padLeft, int padRight, int padTop, int padBottom, int[] xDivs, int[] yDivs) {
this.padLeft = padLeft;
this.padRight = padRight;
this.padTop = padTop;
this.padBottom = padBottom;
this.xDivs = xDivs;
this.yDivs = yDivs;
}
public static NinePatchData decode(ExtDataInput di) throws IOException {
di.skipBytes(1); // wasDeserialized
byte numXDivs = di.readByte();
byte numYDivs = di.readByte();
di.skipBytes(1); // numColors
di.skipBytes(8); // xDivs/yDivs offset
int padLeft = di.readInt();
int padRight = di.readInt();
int padTop = di.readInt();
int padBottom = di.readInt();
di.skipBytes(4); // colorsOffset
int[] xDivs = di.readIntArray(numXDivs);
int[] yDivs = di.readIntArray(numYDivs);
return new NinePatchData(padLeft, padRight, padTop, padBottom, xDivs, yDivs);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.res.decoder.ninepatch;
import brut.util.ExtDataInput;
import java.io.IOException;
public class OpticalInset {
public final int layoutBoundsLeft, layoutBoundsTop, layoutBoundsRight, layoutBoundsBottom;
public OpticalInset(int layoutBoundsLeft, int layoutBoundsTop, int layoutBoundsRight, int layoutBoundsBottom) {
this.layoutBoundsLeft = layoutBoundsLeft;
this.layoutBoundsTop = layoutBoundsTop;
this.layoutBoundsRight = layoutBoundsRight;
this.layoutBoundsBottom = layoutBoundsBottom;
}
public static OpticalInset decode(ExtDataInput di) throws IOException {
int layoutBoundsLeft = Integer.reverseBytes(di.readInt());
int layoutBoundsTop = Integer.reverseBytes(di.readInt());
int layoutBoundsRight = Integer.reverseBytes(di.readInt());
int layoutBoundsBottom = Integer.reverseBytes(di.readInt());
return new OpticalInset(layoutBoundsLeft, layoutBoundsTop, layoutBoundsRight, layoutBoundsBottom);
}
}