From abac69975493ffb8300e62f8d60eedae8ad2ce8b Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Sun, 2 Jul 2023 17:44:06 -0400 Subject: [PATCH] refactor: split child classes to own file for decoders --- .../brut/androlib/res/AndrolibResources.java | 14 -- .../java/brut/androlib/res/Framework.java | 9 +- .../androlib/res/decoder/ARSCDecoder.java | 157 +++---------- .../res/decoder/AXmlResourceParser.java | 207 +----------------- .../res/decoder/Res9patchStreamDecoder.java | 61 +----- .../androlib/res/decoder/arsc/ARSCData.java | 68 ++++++ .../androlib/res/decoder/arsc/ARSCHeader.java | 63 ++++++ .../androlib/res/decoder/arsc/EntryData.java | 25 +++ .../res/decoder/arsc/FlagsOffset.java | 27 +++ .../res/decoder/axml/NamespaceStack.java | 197 +++++++++++++++++ .../res/decoder/ninepatch/NinePatchData.java | 52 +++++ .../res/decoder/ninepatch/OpticalInset.java | 39 ++++ 12 files changed, 513 insertions(+), 406 deletions(-) create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCData.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCHeader.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/EntryData.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/FlagsOffset.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/axml/NamespaceStack.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/NinePatchData.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/OpticalInset.java diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java index c75950e9cc..90573f18a3 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java @@ -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.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 @@ public 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); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/Framework.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/Framework.java index ddf1882ccc..10e88a13a1 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/Framework.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/Framework.java @@ -20,6 +20,8 @@ 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.ZipOutputStream; public class Framework { - private final Config config; private File mFrameworkDirectory = null; @@ -63,7 +64,7 @@ public void installFramework(File frameFile, String tag) 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 void publicizeResources(byte[] arsc) throws AndrolibException { 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) { diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java index 2c30a34b46..ea8c775a50 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java @@ -20,13 +20,16 @@ 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 static ARSCData decode(InputStream arscStream, boolean findFlagsOffsets, 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 @@ private ARSCDecoder(InputStream arscStream, ResTable resTable, boolean storeFlag } 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 @@ private ResPackage[] readTableHeader() throws IOException, AndrolibException { } 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 @@ private ResPackage readTablePackage() throws IOException, AndrolibException { 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 @@ private ResPackage readTablePackage() throws IOException, AndrolibException { } 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 @@ private void readLibraryType() throws AndrolibException, IOException { 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 @@ private void readStagedAliasSpec() throws IOException { } 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 @@ private void readOverlaySpec() throws AndrolibException, IOException { } 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 @@ private void readTableTypeSpec() throws AndrolibException, IOException { 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 @@ private void readTableTypeSpec() throws AndrolibException, IOException { } } - 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 @@ private void readTableTypeSpec() throws AndrolibException, IOException { } 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 @@ private ResTypeSpec readSingleTableTypeSpec() throws AndrolibException, IOExcept } 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 @@ private ResType readTableType() throws IOException, AndrolibException { 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 @@ private ResType readTableType() throws IOException, AndrolibException { LOGGER.fine("Sparse type flags detected: " + mTypeSpec.getName()); } - HashMap entryOffsetMap = new LinkedHashMap(); + HashMap entryOffsetMap = new LinkedHashMap<>(); for (int i = 0; i < entryCount; i++) { if ((typeFlags & 0x01) != 0) { entryOffsetMap.put(mIn.readUnsignedShort(), mIn.readUnsignedShort()); @@ -596,15 +599,15 @@ private void addMissingResSpecs() throws AndrolibException { } } - 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 @@ private void nextChunkCheckType(int expectedType) throws IOException, AndrolibEx private final List mFlagsOffsets; private final boolean mKeepBroken; - private Header mHeader; + private ARSCHeader mHeader; private StringBlock mTableStrings; private StringBlock mTypeNames; private StringBlock mSpecNames; @@ -641,113 +644,7 @@ private void nextChunkCheckType(int expectedType) throws IOException, AndrolibEx 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()); } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java index 10c609d252..877fa50412 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java @@ -20,6 +20,7 @@ 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 @@ * 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. - * - *

TODO: * check all methods in closed state */ public class AXmlResourceParser implements XmlResourceParser { @@ -48,11 +47,6 @@ public AXmlResourceParser() { resetEventInfo(); } - public AXmlResourceParser(InputStream stream) { - this(); - open(stream); - } - public AndrolibException getFirstError() { return mFirstError; } @@ -88,7 +82,6 @@ public void close() { resetEventInfo(); } - // ///////////////////////////////// iteration @Override public int next() throws XmlPullParserException, IOException { if (m_reader == null) { @@ -155,7 +148,7 @@ public int getDepth() { } @Override - public int getEventType() throws XmlPullParserException { + public int getEventType(){ return m_event; } @@ -226,7 +219,6 @@ public String getNamespaceUri(int pos) { return m_strings.getString(uri); } - // ///////////////////////////////// attributes @Override public String getClassAttribute() { if (m_classAttribute == -1) { @@ -541,7 +533,6 @@ public boolean isAttributeDefault(int index) { return false; } - // ///////////////////////////////// dummies @Override public void setInput(InputStream stream, String inputEncoding) { open(stream); @@ -605,189 +596,6 @@ public void setFeature(String name, boolean value) 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 @@ private void doNext() throws IOException { 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 @@ private void setFirstError(AndrolibException error) { } } - // ///////////////////////////////// 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 @@ private void setFirstError(AndrolibException error) { 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; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java index 719be8e9bb..0d717d9628 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java @@ -18,6 +18,8 @@ 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 void decode(InputStream in, OutputStream out) throws AndrolibException { 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 void decode(InputStream in, OutputStream out) throws AndrolibException { } } - 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 @@ private void drawVLine(BufferedImage im, int x, int y1, int y2) { 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); - } - } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCData.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCData.java new file mode 100644 index 0000000000..cca63faf11 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCData.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * 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()); +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCHeader.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCHeader.java new file mode 100644 index 0000000000..50a35c3a95 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCHeader.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * 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; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/EntryData.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/EntryData.java new file mode 100644 index 0000000000..5aaef9426a --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/EntryData.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * 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; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/FlagsOffset.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/FlagsOffset.java new file mode 100644 index 0000000000..281d68ee82 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/FlagsOffset.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * 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; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/axml/NamespaceStack.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/axml/NamespaceStack.java new file mode 100644 index 0000000000..407f584bfb --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/axml/NamespaceStack.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * 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'. + *

+ * !! 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; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/NinePatchData.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/NinePatchData.java new file mode 100644 index 0000000000..c34a546e90 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/NinePatchData.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * 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); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/OpticalInset.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/OpticalInset.java new file mode 100644 index 0000000000..8cf8a00b55 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/OpticalInset.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * 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); + } +}