From 4db49ab3476b231b41f4ec26dbf055038f62fb2e Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 19 Sep 2012 20:27:35 -0500 Subject: [PATCH] Adding brut.apktool :/ --- brut.apktool | 1 - brut.apktool/LICENSE | 202 +++ brut.apktool/NOTICE | 11 + brut.apktool/NOTICE-smali | 98 ++ .../src/main/java/brut/apktool/Main.java | 333 +++++ .../content/res/XmlResourceParser.java | 36 + .../main/java/android/util/AttributeSet.java | 49 + .../main/java/android/util/TypedValue.java | 263 ++++ .../src/main/java/brut/androlib/Androlib.java | 575 +++++++++ .../java/brut/androlib/AndrolibException.java | 39 + .../main/java/brut/androlib/ApkDecoder.java | 275 ++++ .../java/brut/androlib/ApktoolProperties.java | 76 ++ .../androlib/err/CantFind9PatchChunk.java | 40 + .../err/CantFindFrameworkResException.java | 40 + .../androlib/err/InFileNotFoundException.java | 40 + .../androlib/err/OutDirExistsException.java | 40 + .../brut/androlib/err/UndefinedResObject.java | 39 + .../java/brut/androlib/java/AndrolibJava.java | 34 + .../brut/androlib/mod/IndentingWriter.java | 29 + .../main/java/brut/androlib/mod/SmaliMod.java | 93 ++ .../brut/androlib/res/AndrolibResources.java | 583 +++++++++ .../brut/androlib/res/ResSmaliUpdater.java | 167 +++ .../brut/androlib/res/data/ResConfig.java | 74 ++ .../androlib/res/data/ResConfigFlags.java | 443 +++++++ .../java/brut/androlib/res/data/ResID.java | 70 + .../brut/androlib/res/data/ResPackage.java | 220 ++++ .../brut/androlib/res/data/ResResSpec.java | 125 ++ .../brut/androlib/res/data/ResResource.java | 64 + .../java/brut/androlib/res/data/ResTable.java | 137 ++ .../java/brut/androlib/res/data/ResType.java | 70 + .../brut/androlib/res/data/ResValuesFile.java | 90 ++ .../res/data/value/ResArrayValue.java | 87 ++ .../brut/androlib/res/data/value/ResAttr.java | 175 +++ .../androlib/res/data/value/ResBagValue.java | 64 + .../androlib/res/data/value/ResBoolValue.java | 37 + .../res/data/value/ResColorValue.java | 31 + .../res/data/value/ResDimenValue.java | 34 + .../androlib/res/data/value/ResEnumAttr.java | 84 ++ .../androlib/res/data/value/ResFileValue.java | 42 + .../androlib/res/data/value/ResFlagsAttr.java | 160 +++ .../res/data/value/ResFloatValue.java | 37 + .../res/data/value/ResFractionValue.java | 34 + .../androlib/res/data/value/ResIdValue.java | 35 + .../androlib/res/data/value/ResIntValue.java | 46 + .../res/data/value/ResPluralsValue.java | 81 ++ .../res/data/value/ResReferenceValue.java | 68 + .../res/data/value/ResScalarValue.java | 132 ++ .../res/data/value/ResStringValue.java | 66 + .../res/data/value/ResStyleValue.java | 74 ++ .../androlib/res/data/value/ResValue.java | 24 + .../res/data/value/ResValueFactory.java | 101 ++ .../androlib/res/decoder/ARSCDecoder.java | 440 +++++++ .../res/decoder/AXmlResourceParser.java | 1013 +++++++++++++++ .../res/decoder/Res9patchStreamDecoder.java | 139 ++ .../androlib/res/decoder/ResAttrDecoder.java | 55 + .../androlib/res/decoder/ResFileDecoder.java | 145 +++ .../res/decoder/ResRawStreamDecoder.java | 37 + .../res/decoder/ResStreamDecoder.java | 29 + .../decoder/ResStreamDecoderContainer.java | 48 + .../androlib/res/decoder/StringBlock.java | 347 +++++ .../res/decoder/XmlPullStreamDecoder.java | 125 ++ .../java/brut/androlib/res/util/ExtFile.java | 63 + .../androlib/res/util/ExtMXSerializer.java | 79 ++ .../androlib/res/util/ExtXmlSerializer.java | 35 + .../res/xml/ResValuesXmlSerializable.java | 30 + .../androlib/res/xml/ResXmlEncodable.java | 27 + .../brut/androlib/res/xml/ResXmlEncoders.java | 202 +++ .../java/brut/androlib/src/DebugInjector.java | 227 ++++ .../brut/androlib/src/DexFileBuilder.java | 85 ++ .../java/brut/androlib/src/SmaliBuilder.java | 116 ++ .../java/brut/androlib/src/SmaliDecoder.java | 62 + .../main/java/brut/androlib/src/TypeName.java | 209 +++ .../ledatastream/LEDataInputStream.java | 319 +++++ .../xmlpull/mxp1_serializer/MXSerializer.java | 1144 +++++++++++++++++ .../brut/androlib/apktool.properties | 2 + .../brut/androlib/BuildAndDecodeTest.java | 177 +++ .../test/java/brut/androlib/TestUtils.java | 138 ++ .../brut/apktool/testapp/AndroidManifest.xml | 3 + .../brut/apktool/testapp/apktool.yml | 6 + .../testapp/res/values-mcc001/arrays.xml | 23 + .../testapp/res/values-mcc001/bools.xml | 5 + .../testapp/res/values-mcc001/colors.xml | 4 + .../testapp/res/values-mcc001/dimens.xml | 9 + .../apktool/testapp/res/values-mcc001/ids.xml | 5 + .../testapp/res/values-mcc001/integers.xml | 6 + .../testapp/res/values-mcc001/strings.xml | 27 + .../testapp/res/values-mcc002/arrays.xml | 7 + .../testapp/res/values-mcc002/strings.xml | 6 + .../testapp/res/values-mcc003/bools.xml | 4 + .../testapp/res/values-mcc003/integers.xml | 4 + .../testapp/res/values-mcc003/strings.xml | 5 + .../strings.xml | 4 + .../apktool/testapp/res/values/strings.xml | 4 + .../brut/apktool/testapp/res/xml/literals.xml | 16 + .../apktool/testapp/res/xml/references.xml | 6 + .../src/templates/apache2.0-header.txt | 14 + 96 files changed, 11218 insertions(+), 1 deletion(-) delete mode 160000 brut.apktool create mode 100644 brut.apktool/LICENSE create mode 100644 brut.apktool/NOTICE create mode 100644 brut.apktool/NOTICE-smali create mode 100644 brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java create mode 100644 brut.apktool/apktool-lib/src/main/java/android/content/res/XmlResourceParser.java create mode 100644 brut.apktool/apktool-lib/src/main/java/android/util/AttributeSet.java create mode 100644 brut.apktool/apktool-lib/src/main/java/android/util/TypedValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/AndrolibException.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/ApktoolProperties.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/err/CantFind9PatchChunk.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/err/CantFindFrameworkResException.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/err/InFileNotFoundException.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/err/OutDirExistsException.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/err/UndefinedResObject.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/java/AndrolibJava.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/mod/IndentingWriter.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/mod/SmaliMod.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResSmaliUpdater.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfig.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResID.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResPackage.java create mode 100755 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResSpec.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResource.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResType.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResValuesFile.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResArrayValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResAttr.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResBagValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResBoolValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResColorValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResDimenValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResEnumAttr.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFileValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFlagsAttr.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFloatValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFractionValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResIdValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResIntValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResPluralsValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResReferenceValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResScalarValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResStringValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResStyleValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValue.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResAttrDecoder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResFileDecoder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResRawStreamDecoder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResStreamDecoder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResStreamDecoderContainer.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/XmlPullStreamDecoder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtFile.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtMXSerializer.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtXmlSerializer.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResValuesXmlSerializable.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncodable.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/src/DebugInjector.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/src/DexFileBuilder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/src/SmaliBuilder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/src/SmaliDecoder.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/src/TypeName.java create mode 100644 brut.apktool/apktool-lib/src/main/java/com/mindprod/ledatastream/LEDataInputStream.java create mode 100644 brut.apktool/apktool-lib/src/main/java/org/xmlpull/mxp1_serializer/MXSerializer.java create mode 100644 brut.apktool/apktool-lib/src/main/resources/brut/androlib/apktool.properties create mode 100644 brut.apktool/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java create mode 100644 brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/AndroidManifest.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/apktool.yml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/arrays.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/bools.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/colors.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/dimens.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/ids.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/integers.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/arrays.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/strings.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/bools.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/integers.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/strings.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc004-mnc4-en-rUS-sw100dp-w200dp-h300dp-xlarge-long-land-desk-night-xhdpi-finger-keyssoft-12key-navhidden-dpad/strings.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values/strings.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/xml/literals.xml create mode 100644 brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/xml/references.xml create mode 100644 brut.apktool/src/templates/apache2.0-header.txt diff --git a/brut.apktool b/brut.apktool deleted file mode 160000 index f7d1e37..0000000 --- a/brut.apktool +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f7d1e37031f32285ac368e38b21da4fc7574a99d diff --git a/brut.apktool/LICENSE b/brut.apktool/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/brut.apktool/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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. diff --git a/brut.apktool/NOTICE b/brut.apktool/NOTICE new file mode 100644 index 0000000..68fd595 --- /dev/null +++ b/brut.apktool/NOTICE @@ -0,0 +1,11 @@ +apktool +Copyright 2011 Ryszard Wiśniewski + +This product includes software developed by: + + * Ryszard Wiśniewski (brut.alll@gmail.com) + * JesusFreke (http://code.google.com/p/smali/) + * Dmitry Skiba (http://code.google.com/p/android4me/) + * Tahseen Ur Rehman (http://code.google.com/p/radixtree/) + * Android Open Source Project (http://source.android.com/) + * The Apache Software Foundation (http://www.apache.org/) diff --git a/brut.apktool/NOTICE-smali b/brut.apktool/NOTICE-smali new file mode 100644 index 0000000..ab56216 --- /dev/null +++ b/brut.apktool/NOTICE-smali @@ -0,0 +1,98 @@ +The majority of smali/baksmali is written and copyrighted by me (Ben Gruver) +and released under the following license: + +******************************************************************************* +Copyright (c) 2010 Ben Gruver (JesusFreke) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +******************************************************************************* + + +Various portions of the code are taken from the Android Open Source Project, +and are used in accordance with the following license: + +******************************************************************************* +Copyright (C) 2007 The Android Open Source Project + +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 + + http://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. +******************************************************************************* + + +The smali mojo plugin is (very loosely) based on an unknown mojo plugin with +the following license: + +******************************************************************************* +Copyright 2001-2005 The Apache Software Foundation. + +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 + + http://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. +******************************************************************************* + + +The RadixTree implementation in the "util" project is taken from +http://code.google.com/p/radixtree/ (version .3), and is used with minor +modifications in accordance with the following license: + +******************************************************************************* +The MIT License + +Copyright (c) 2008 Tahseen Ur Rehman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +******************************************************************************* diff --git a/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java b/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java new file mode 100644 index 0000000..64b8a4a --- /dev/null +++ b/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java @@ -0,0 +1,333 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.apktool; + +import brut.androlib.Androlib; +import brut.androlib.AndrolibException; +import brut.androlib.ApkDecoder; +import brut.androlib.ApktoolProperties; +import brut.androlib.err.CantFindFrameworkResException; +import brut.androlib.err.InFileNotFoundException; +import brut.androlib.err.OutDirExistsException; +import brut.androlib.res.util.ExtFile; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.logging.*; + +/** + * @author Ryszard Wiśniewski + */ +public class Main { + public static void main(String[] args) + throws IOException, AndrolibException, InterruptedException { + try { + Verbosity verbosity = Verbosity.NORMAL; + int i; + for (i = 0; i < args.length; i++) { + String opt = args[i]; + if (! opt.startsWith("-")) { + break; + } + if ("-v".equals(opt) || "--verbose".equals(opt)) { + if (verbosity != Verbosity.NORMAL) { + throw new InvalidArgsError(); + } + verbosity = Verbosity.VERBOSE; + } else if ("-q".equals(opt) || "--quiet".equals(opt)) { + if (verbosity != Verbosity.NORMAL) { + throw new InvalidArgsError(); + } + verbosity = Verbosity.QUIET; + } else { + throw new InvalidArgsError(); + } + } + setupLogging(verbosity); + + if (args.length <= i) { + throw new InvalidArgsError(); + } + String cmd = args[i]; + args = Arrays.copyOfRange(args, i + 1, args.length); + + if ("d".equals(cmd) || "decode".equals(cmd)) { + cmdDecode(args); + } else if ("b".equals(cmd) || "build".equals(cmd)) { + cmdBuild(args); + } else if ("if".equals(cmd) || "install-framework".equals(cmd)) { + cmdInstallFramework(args); + } else if ("publicize-resources".equals(cmd)) { + cmdPublicizeResources(args); + } else { + throw new InvalidArgsError(); + } + } catch (InvalidArgsError ex) { + usage(); + System.exit(1); + } + } + + private static void cmdDecode(String[] args) throws InvalidArgsError, + AndrolibException { + ApkDecoder decoder = new ApkDecoder(); + + int i; + for (i = 0; i < args.length; i++) { + String opt = args[i]; + if (! opt.startsWith("-")) { + break; + } + if ("-s".equals(opt) || "--no-src".equals(opt)) { + decoder.setDecodeSources(ApkDecoder.DECODE_SOURCES_NONE); + } else if ("-d".equals(opt) || "--debug".equals(opt)) { + decoder.setDebugMode(true); + } else if ("-b".equals(opt) || "--no-debug-info".equals(opt)) { + decoder.setBaksmaliDebugMode(false); + } else if ("-t".equals(opt) || "--frame-tag".equals(opt)) { + i++; + if (i >= args.length) { + throw new InvalidArgsError(); + } + decoder.setFrameworkTag(args[i]); + } else if ("-f".equals(opt) || "--force".equals(opt)) { + decoder.setForceDelete(true); + } else if ("-r".equals(opt) || "--no-res".equals(opt)) { + decoder.setDecodeResources(ApkDecoder.DECODE_RESOURCES_NONE); + } else if ("--keep-broken-res".equals(opt)) { + decoder.setKeepBrokenResources(true); + } else { + throw new InvalidArgsError(); + } + } + + String outName = null; + if (args.length == i + 2) { + outName = args[i + 1]; + } else if (args.length == i + 1) { + outName = args[i]; + outName = outName.endsWith(".apk") ? + outName.substring(0, outName.length() - 4) : outName + ".out"; + outName = new File(outName).getName(); + } else { + throw new InvalidArgsError(); + } + File outDir = new File(outName); + decoder.setOutDir(outDir); + decoder.setApkFile(new File(args[i])); + + try { + decoder.decode(); + } catch (OutDirExistsException ex) { + System.out.println( + "Destination directory (" + outDir.getAbsolutePath() + ") " + + "already exists. Use -f switch if you want to overwrite it."); + System.exit(1); + } catch (InFileNotFoundException ex) { + System.out.println( + "Input file (" + args[i] + ") " + + "was not found or was not readable."); + System.exit(1); + } catch (CantFindFrameworkResException ex) { + System.out.println( + "Can't find framework resources for package of id: " + + String.valueOf(ex.getPkgId()) + ". You must install proper " + + "framework files, see project website for more info."); + System.exit(1); + } + } + + private static void cmdBuild(String[] args) throws InvalidArgsError, + AndrolibException { + + // hold all the fields + HashMap flags = new HashMap(); + flags.put("forceBuildAll", false); + flags.put("debug", false); + flags.put("verbose", false); + flags.put("injectOriginal", false); + flags.put("framework", false); + flags.put("update", false); + + int i; + int skip = 0; + ExtFile mOrigApk = null; + for (i = 0; i < args.length; i++) { + String opt = args[i]; + if (! opt.startsWith("-")) { + break; + } + if ("-f".equals(opt) || "--force-all".equals(opt)) { + flags.put("forceBuildAll", true); + } else if ("-d".equals(opt) || "--debug".equals(opt)) { + flags.put("debug", true); + } else if ("-v".equals(opt) || "--verbose".equals(opt)) { + flags.put("verbose", true); + } else if ("-o".equals(opt) || "--original".equals(opt)) { + if (args.length >= 4) { + throw new InvalidArgsError(); + } else { + flags.put("injectOriginal", true); + mOrigApk = new ExtFile(args[i + 1]); + skip = 1; + } + } else { + throw new InvalidArgsError(); + } + } + + String appDirName; + File outFile = null; + switch (args.length - i - skip) { + case 0: + appDirName = "."; + break; + case 2: + outFile = new File(args[i + 1 + skip]); + case 1: + appDirName = args[i + skip]; + break; + default: + throw new InvalidArgsError(); + } + + new Androlib().build(new File(appDirName), outFile, flags, mOrigApk); + } + + private static void cmdInstallFramework(String[] args) + throws AndrolibException { + String tag = null; + switch (args.length) { + case 2: + tag = args[1]; + case 1: + new Androlib().installFramework(new File(args[0]), tag); + return; + } + + throw new InvalidArgsError(); + } + + private static void cmdPublicizeResources(String[] args) + throws InvalidArgsError, AndrolibException { + if (args.length != 1) { + throw new InvalidArgsError(); + } + + new Androlib().publicizeResources(new File(args[0])); + } + + private static void usage() { + System.out.println( + "Apktool v" + Androlib.getVersion() + " - a tool for reengineering Android apk files\n" + + "Copyright 2010 Ryszard Wiśniewski \n" + + "with smali v" + ApktoolProperties.get("smaliVersion") + + ", and baksmali v" + ApktoolProperties.get("baksmaliVersion") + "\n" + + "Updated by iBotPeaches \n" + + "Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)\n" + + "\n" + + "Usage: apktool [-q|--quiet OR -v|--verbose] COMMAND [...]\n" + + "\n" + + "COMMANDs are:\n" + + "\n" + + " d[ecode] [OPTS] []\n" + + " Decode to .\n" + + "\n" + + " OPTS:\n" + + "\n" + + " -s, --no-src\n" + + " Do not decode sources.\n" + + " -r, --no-res\n" + + " Do not decode resources.\n" + + " -d, --debug\n" + + " Decode in debug mode. Check project page for more info.\n" + + " -b, --no-debug-info\n" + + " Baksmali -- don't write out debug info (.local, .param, .line, etc.)\n" + + " -f, --force\n" + + " Force delete destination directory.\n" + + " -t , --frame-tag \n" + + " Try to use framework files tagged by .\n" + + " --keep-broken-res\n" + + " Use if there was an error and some resources were dropped, e.g.:\n" + + " \"Invalid config flags detected. Dropping resources\", but you\n" + + " want to decode them anyway, even with errors. You will have to\n" + + " fix them manually before building." + + "\n\n" + + " b[uild] [OPTS] [] []\n" + + " Build an apk from already decoded application located in .\n" + + "\n" + + " It will automatically detect, whether files was changed and perform\n" + + " needed steps only.\n" + + "\n" + + " If you omit then current directory will be used.\n" + + " If you omit then /dist/\n" + + " will be used.\n" + + "\n" + + " OPTS:\n" + + "\n" + + " -f, --force-all\n" + + " Skip changes detection and build all files.\n" + + " -d, --debug\n" + + " Build in debug mode. Check project page for more info.\n" + + " -o, --original\n" + + " Build resources into original APK. Retains signature." + + "\n" + + " if|install-framework []\n" + + " Install framework file to your system.\n" + + "\n" + + "For additional info, see: https://github.com/iBotPeaches/brut.apktool" + + "\n" + + "For smali/baksmali info, see: http://code.google.com/p/smali/" + ); + } + + private static void setupLogging(Verbosity verbosity) { + Logger logger = Logger.getLogger(""); + for (Handler handler : logger.getHandlers()) { + logger.removeHandler(handler); + } + if (verbosity == Verbosity.QUIET) { + return; + } + + Handler handler = new ConsoleHandler(); + logger.addHandler(handler); + + if (verbosity == Verbosity.VERBOSE) { + handler.setLevel(Level.ALL); + logger.setLevel(Level.ALL); + } else { + handler.setFormatter(new Formatter() { + @Override + public String format(LogRecord record) { + return record.getLevel().toString().charAt(0) + ": " + + record.getMessage() + + System.getProperty("line.separator"); + } + }); + } + } + + private static enum Verbosity { + NORMAL, VERBOSE, QUIET; + } + + static class InvalidArgsError extends AndrolibException { + + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/android/content/res/XmlResourceParser.java b/brut.apktool/apktool-lib/src/main/java/android/content/res/XmlResourceParser.java new file mode 100644 index 0000000..c59e6d4 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/android/content/res/XmlResourceParser.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 + * + * http://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 android.content.res; + +import org.xmlpull.v1.XmlPullParser; + +import android.util.AttributeSet; + +/** + * The XML parsing interface returned for an XML resource. This is a standard + * XmlPullParser interface, as well as an extended AttributeSet interface and + * an additional close() method on this interface for the client to indicate + * when it is done reading the resource. + */ +public interface XmlResourceParser extends XmlPullParser, AttributeSet { + /** + * Close this interface to the resource. Calls on the interface are no + * longer value after this call. + */ + public void close(); +} + diff --git a/brut.apktool/apktool-lib/src/main/java/android/util/AttributeSet.java b/brut.apktool/apktool-lib/src/main/java/android/util/AttributeSet.java new file mode 100644 index 0000000..1da422f --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/android/util/AttributeSet.java @@ -0,0 +1,49 @@ +/* + * Copyright 2008 Android4ME + * + * 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 + * + * http://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 android.util; + +/** + * @author Dmitry Skiba + * + */ +public interface AttributeSet { + int getAttributeCount(); + String getAttributeName(int index); + String getAttributeValue(int index); + String getPositionDescription(); + int getAttributeNameResource(int index); + int getAttributeListValue(int index,String options[],int defaultValue); + boolean getAttributeBooleanValue(int index,boolean defaultValue); + int getAttributeResourceValue(int index,int defaultValue); + int getAttributeIntValue(int index,int defaultValue); + int getAttributeUnsignedIntValue(int index,int defaultValue); + float getAttributeFloatValue(int index,float defaultValue); + String getIdAttribute(); + String getClassAttribute(); + int getIdAttributeResourceValue(int index); + int getStyleAttribute(); + String getAttributeValue(String namespace, String attribute); + int getAttributeListValue(String namespace,String attribute,String options[],int defaultValue); + boolean getAttributeBooleanValue(String namespace,String attribute,boolean defaultValue); + int getAttributeResourceValue(String namespace,String attribute,int defaultValue); + int getAttributeIntValue(String namespace,String attribute,int defaultValue); + int getAttributeUnsignedIntValue(String namespace,String attribute,int defaultValue); + float getAttributeFloatValue(String namespace,String attribute,float defaultValue); + + //TODO: remove + int getAttributeValueType(int index); + int getAttributeValueData(int index); +} \ No newline at end of file diff --git a/brut.apktool/apktool-lib/src/main/java/android/util/TypedValue.java b/brut.apktool/apktool-lib/src/main/java/android/util/TypedValue.java new file mode 100644 index 0000000..d7b5295 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/android/util/TypedValue.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 + * + * http://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 android.util; + +/** + * Container for a dynamically typed data value. Primarily used with + * {@link android.content.res.Resources} for holding resource values. + */ +public class TypedValue { + /** The value contains no data. */ + public static final int TYPE_NULL = 0x00; + + /** The data field holds a resource identifier. */ + public static final int TYPE_REFERENCE = 0x01; + /** The data field holds an attribute resource + * identifier (referencing an attribute in the current theme + * style, not a resource entry). */ + public static final int TYPE_ATTRIBUTE = 0x02; + /** The string field holds string data. In addition, if + * data is non-zero then it is the string block + * index of the string and assetCookie is the set of + * assets the string came from. */ + public static final int TYPE_STRING = 0x03; + /** The data field holds an IEEE 754 floating point number. */ + public static final int TYPE_FLOAT = 0x04; + /** The data field holds a complex number encoding a + * dimension value. */ + public static final int TYPE_DIMENSION = 0x05; + /** The data field holds a complex number encoding a fraction + * of a container. */ + public static final int TYPE_FRACTION = 0x06; + + /** Identifies the start of plain integer values. Any type value + * from this to {@link #TYPE_LAST_INT} means the + * data field holds a generic integer value. */ + public static final int TYPE_FIRST_INT = 0x10; + + /** The data field holds a number that was + * originally specified in decimal. */ + public static final int TYPE_INT_DEC = 0x10; + /** The data field holds a number that was + * originally specified in hexadecimal (0xn). */ + public static final int TYPE_INT_HEX = 0x11; + /** The data field holds 0 or 1 that was originally + * specified as "false" or "true". */ + public static final int TYPE_INT_BOOLEAN = 0x12; + + /** Identifies the start of integer values that were specified as + * color constants (starting with '#'). */ + public static final int TYPE_FIRST_COLOR_INT = 0x1c; + + /** The data field holds a color that was originally + * specified as #aarrggbb. */ + public static final int TYPE_INT_COLOR_ARGB8 = 0x1c; + /** The data field holds a color that was originally + * specified as #rrggbb. */ + public static final int TYPE_INT_COLOR_RGB8 = 0x1d; + /** The data field holds a color that was originally + * specified as #argb. */ + public static final int TYPE_INT_COLOR_ARGB4 = 0x1e; + /** The data field holds a color that was originally + * specified as #rgb. */ + public static final int TYPE_INT_COLOR_RGB4 = 0x1f; + + /** Identifies the end of integer values that were specified as color + * constants. */ + public static final int TYPE_LAST_COLOR_INT = 0x1f; + + /** Identifies the end of plain integer values. */ + public static final int TYPE_LAST_INT = 0x1f; + + /* ------------------------------------------------------------ */ + + /** Complex data: bit location of unit information. */ + public static final int COMPLEX_UNIT_SHIFT = 0; + /** Complex data: mask to extract unit information (after shifting by + * {@link #COMPLEX_UNIT_SHIFT}). This gives us 16 possible types, as + * defined below. */ + public static final int COMPLEX_UNIT_MASK = 0xf; + + /** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */ + public static final int COMPLEX_UNIT_PX = 0; + /** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent + * Pixels. */ + public static final int COMPLEX_UNIT_DIP = 1; + /** {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel. */ + public static final int COMPLEX_UNIT_SP = 2; + /** {@link #TYPE_DIMENSION} complex unit: Value is in points. */ + public static final int COMPLEX_UNIT_PT = 3; + /** {@link #TYPE_DIMENSION} complex unit: Value is in inches. */ + public static final int COMPLEX_UNIT_IN = 4; + /** {@link #TYPE_DIMENSION} complex unit: Value is in millimeters. */ + public static final int COMPLEX_UNIT_MM = 5; + + /** {@link #TYPE_FRACTION} complex unit: A basic fraction of the overall + * size. */ + public static final int COMPLEX_UNIT_FRACTION = 0; + /** {@link #TYPE_FRACTION} complex unit: A fraction of the parent size. */ + public static final int COMPLEX_UNIT_FRACTION_PARENT = 1; + + /** Complex data: where the radix information is, telling where the decimal + * place appears in the mantissa. */ + public static final int COMPLEX_RADIX_SHIFT = 4; + /** Complex data: mask to extract radix information (after shifting by + * {@link #COMPLEX_RADIX_SHIFT}). This give us 4 possible fixed point + * representations as defined below. */ + public static final int COMPLEX_RADIX_MASK = 0x3; + + /** Complex data: the mantissa is an integral number -- i.e., 0xnnnnnn.0 */ + public static final int COMPLEX_RADIX_23p0 = 0; + /** Complex data: the mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn */ + public static final int COMPLEX_RADIX_16p7 = 1; + /** Complex data: the mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn */ + public static final int COMPLEX_RADIX_8p15 = 2; + /** Complex data: the mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn */ + public static final int COMPLEX_RADIX_0p23 = 3; + + /** Complex data: bit location of mantissa information. */ + public static final int COMPLEX_MANTISSA_SHIFT = 8; + /** Complex data: mask to extract mantissa information (after shifting by + * {@link #COMPLEX_MANTISSA_SHIFT}). This gives us 23 bits of precision; + * the top bit is the sign. */ + public static final int COMPLEX_MANTISSA_MASK = 0xffffff; + + /* ------------------------------------------------------------ */ + + /** + * If {@link #density} is equal to this value, then the density should be + * treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}. + */ + public static final int DENSITY_DEFAULT = 0; + + /** + * If {@link #density} is equal to this value, then there is no density + * associated with the resource and it should not be scaled. + */ + public static final int DENSITY_NONE = 0xffff; + + /* ------------------------------------------------------------ */ + + /** The type held by this value, as defined by the constants here. + * This tells you how to interpret the other fields in the object. */ + public int type; + + private static final float MANTISSA_MULT = + 1.0f / (1<>TypedValue.COMPLEX_RADIX_SHIFT) + & TypedValue.COMPLEX_RADIX_MASK]; + } + + private static final String[] DIMENSION_UNIT_STRS = new String[] { + "px", "dip", "sp", "pt", "in", "mm" + }; + private static final String[] FRACTION_UNIT_STRS = new String[] { + "%", "%p" + }; + + /** + * Perform type conversion as per {@link #coerceToString()} on an + * explicitly supplied type and data. + * + * @param type The data type identifier. + * @param data The data value. + * + * @return String The coerced string value. If the value is + * null or the type is not known, null is returned. + */ + public static final String coerceToString(int type, int data) + { + switch (type) { + case TYPE_NULL: + return null; + case TYPE_REFERENCE: + return "@" + data; + case TYPE_ATTRIBUTE: + return "?" + data; + case TYPE_FLOAT: + return Float.toString(Float.intBitsToFloat(data)); + case TYPE_DIMENSION: + return Float.toString(complexToFloat(data)) + DIMENSION_UNIT_STRS[ + (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK]; + case TYPE_FRACTION: + return Float.toString(complexToFloat(data)*100) + FRACTION_UNIT_STRS[ + (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK]; + case TYPE_INT_HEX: + return "0x" + Integer.toHexString(data); + case TYPE_INT_BOOLEAN: + return data != 0 ? "true" : "false"; + } + + if (type >= TYPE_FIRST_COLOR_INT && type <= TYPE_LAST_COLOR_INT) { + String res =String.format("%08x", data); + char[] vals = res.toCharArray(); + switch (type) { + default: + case TYPE_INT_COLOR_ARGB8://#AaRrGgBb + break; + case TYPE_INT_COLOR_RGB8://#FFRrGgBb->#RrGgBb + res = res.substring(2); + break; + case TYPE_INT_COLOR_ARGB4://#AARRGGBB->#ARGB + res = new StringBuffer().append(vals[0]).append(vals[2]).append(vals[4]).append(vals[6]).toString(); + break; + case TYPE_INT_COLOR_RGB4://#FFRRGGBB->#RGB + res = new StringBuffer().append(vals[2]).append(vals[4]).append(vals[6]).toString(); + break; + } + return "#" + res; + } else if (type >= TYPE_FIRST_INT && type <= TYPE_LAST_INT) { + String res; + switch (type) { + default: + case TYPE_INT_DEC: + res = Integer.toString(data); + break; + //defined before + /*case TYPE_INT_HEX: + res = "0x" + Integer.toHexString(data); + break; + case TYPE_INT_BOOLEAN: + res = (data != 0) ? "true":"false"; + break;*/ + } + return res; + } + + return null; + } + +}; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java new file mode 100644 index 0000000..d4c22fb --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java @@ -0,0 +1,575 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.err.InFileNotFoundException; +import brut.androlib.java.AndrolibJava; +import brut.androlib.res.AndrolibResources; +import brut.androlib.res.data.ResPackage; +import brut.androlib.res.data.ResTable; +import brut.androlib.res.util.ExtFile; +import brut.androlib.src.SmaliBuilder; +import brut.androlib.src.SmaliDecoder; +import brut.common.BrutException; +import brut.directory.*; +import brut.util.BrutIO; +import brut.util.OS; +import java.io.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import net.lingala.zip4j.core.ZipFile; +import net.lingala.zip4j.exception.ZipException; +import net.lingala.zip4j.model.ZipParameters; +import net.lingala.zip4j.util.Zip4jConstants; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +/** + * @author Ryszard Wiśniewski + */ +public class Androlib { + private final AndrolibResources mAndRes = new AndrolibResources(); + + public ResTable getResTable(ExtFile apkFile) throws AndrolibException { + return mAndRes.getResTable(apkFile, true); + } + + public ResTable getResTable(ExtFile apkFile, boolean loadMainPkg) throws AndrolibException { + return mAndRes.getResTable(apkFile, loadMainPkg); + } + + public void decodeSourcesRaw(ExtFile apkFile, File outDir, boolean debug) + throws AndrolibException { + try { + if (debug) { + LOGGER.warning("Debug mode not available."); + } + Directory apk = apkFile.getDirectory(); + LOGGER.info("Copying raw classes.dex file..."); + apkFile.getDirectory().copyToDir(outDir, "classes.dex"); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public void decodeSourcesSmali(File apkFile, File outDir, boolean debug, boolean bakdeb) + throws AndrolibException { + try { + File smaliDir = new File(outDir, SMALI_DIRNAME); + OS.rmdir(smaliDir); + smaliDir.mkdirs(); + LOGGER.info("Baksmaling..."); + SmaliDecoder.decode(apkFile, smaliDir, debug, bakdeb); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + + public void decodeSourcesJava(ExtFile apkFile, File outDir, boolean debug) + throws AndrolibException { + LOGGER.info("Decoding Java sources..."); + new AndrolibJava().decode(apkFile, outDir); + } + + public void decodeManifestRaw(ExtFile apkFile, File outDir) + throws AndrolibException { + try { + Directory apk = apkFile.getDirectory(); + LOGGER.info("Copying raw manifest..."); + apkFile.getDirectory().copyToDir(outDir, APK_MANIFEST_FILENAMES); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public void decodeManifestFull(ExtFile apkFile, File outDir, + ResTable resTable) throws AndrolibException { + mAndRes.decodeManifest(resTable, apkFile, outDir); + } + + public void decodeResourcesRaw(ExtFile apkFile, File outDir) + throws AndrolibException { + try { + Directory apk = apkFile.getDirectory(); + LOGGER.info("Copying raw resources..."); + apkFile.getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public void decodeResourcesFull(ExtFile apkFile, File outDir, + ResTable resTable) throws AndrolibException { + mAndRes.decode(resTable, apkFile, outDir); + } + + public void decodeRawFiles(ExtFile apkFile, File outDir) + throws AndrolibException { + LOGGER.info("Copying assets and libs..."); + try { + Directory in = apkFile.getDirectory(); + if (in.containsDir("assets")) { + in.copyToDir(outDir, "assets"); + } + if (in.containsDir("lib")) { + in.copyToDir(outDir, "lib"); + } + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public void writeMetaFile(File mOutDir, Map meta) + throws AndrolibException { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); +// options.setIndent(4); + Yaml yaml = new Yaml(options); + + FileWriter writer = null; + try { + writer = new FileWriter(new File(mOutDir, "apktool.yml")); + yaml.dump(meta, writer); + } catch (IOException ex) { + throw new AndrolibException(ex); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException ex) {} + } + } + } + + public Map readMetaFile(ExtFile appDir) + throws AndrolibException { + InputStream in = null; + try { + in = appDir.getDirectory().getFileInput("apktool.yml"); + Yaml yaml = new Yaml(); + return (Map) yaml.load(in); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ex) {} + } + } + } + + public void build(File appDir, File outFile, + HashMap flags, ExtFile origApk) throws AndrolibException { + build(new ExtFile(appDir), outFile, flags, origApk); + } + + public void build(ExtFile appDir, File outFile, + HashMap flags, ExtFile origApk) throws AndrolibException { + Map meta = readMetaFile(appDir); + Object t1 = meta.get("isFrameworkApk"); + flags.put("framework", t1 == null ? false : (Boolean) t1); + mAndRes.setSdkInfo((Map) meta.get("sdkInfo")); + + // check the orig apk + if (flags.get("injectOriginal")) { + if (!origApk.isFile() || !origApk.canRead()) { + throw new InFileNotFoundException(); + } else { + mOrigApkFile = origApk; + } + } + + if (outFile == null) { + String outFileName = (String) meta.get("apkFileName"); + outFile = new File(appDir, "dist" + File.separator + + (outFileName == null ? "out.apk" : outFileName)); + } + + new File(appDir, APK_DIRNAME).mkdirs(); + buildSources(appDir, flags); + buildResources(appDir, flags, + (Map) meta.get("usesFramework")); + buildLib(appDir, flags); + buildApk(appDir, outFile,flags); + } + + public void buildSources(File appDir, HashMap flags) + throws AndrolibException { + if (! buildSourcesRaw(appDir, flags) + && ! buildSourcesSmali(appDir, flags) + && ! buildSourcesJava(appDir, flags) + ) { + LOGGER.warning("Could not find sources"); + } + } + + public boolean buildSourcesRaw(File appDir, + HashMap flags) throws AndrolibException { + try { + File working = new File(appDir, "classes.dex"); + if (! working.exists()) { + return false; + } + if (flags.get("debug")) { + LOGGER.warning("Debug mode not available."); + } + File stored = new File(appDir, APK_DIRNAME + "/classes.dex"); + if (flags.get("forceBuildAll") || isModified(working, stored)) { + LOGGER.info("Copying classes.dex file..."); + BrutIO.copyAndClose(new FileInputStream(working), + new FileOutputStream(stored)); + } + return true; + } catch (IOException ex) { + throw new AndrolibException(ex); + } + } + + public boolean buildSourcesSmali(File appDir, + HashMap flags) throws AndrolibException { + ExtFile smaliDir = new ExtFile(appDir, "smali"); + if (! smaliDir.exists()) { + return false; + } + File dex = new File(appDir, APK_DIRNAME + "/classes.dex"); + if (! flags.get("forceBuildAll")) { + LOGGER.info("Checking whether sources has changed..."); + } + if (flags.get("forceBuildAll") || isModified(smaliDir, dex)) { + LOGGER.info("Smaling..."); + dex.delete(); + SmaliBuilder.build(smaliDir, dex, flags); + } + return true; + } + + public boolean buildSourcesJava(File appDir, + HashMap flags) throws AndrolibException { + File javaDir = new File(appDir, "src"); + if (! javaDir.exists()) { + return false; + } + File dex = new File(appDir, APK_DIRNAME + "/classes.dex"); + if (! flags.get("forceBuildAll")) { + LOGGER.info("Checking whether sources has changed..."); + } + if (flags.get("forceBuildAll") || isModified(javaDir, dex)) { + LOGGER.info("Building java sources..."); + dex.delete(); + new AndrolibJava().build(javaDir, dex); + } + return true; + } + + public void buildResources(ExtFile appDir, HashMap flags, + Map usesFramework) + throws AndrolibException { + if (! buildResourcesRaw(appDir, flags) + && ! buildResourcesFull(appDir, flags, usesFramework) + && ! buildManifest(appDir, flags, usesFramework)) { + LOGGER.warning("Could not find resources"); + } + } + + public boolean buildResourcesRaw(ExtFile appDir, HashMap flags) + throws AndrolibException { + try { + if (! new File(appDir, "resources.arsc").exists()) { + return false; + } + File apkDir = new File(appDir, APK_DIRNAME); + if (! flags.get("forceBuildAll")) { + LOGGER.info("Checking whether resources has changed..."); + } + if (flags.get("forceBuildAll") || isModified( + newFiles(APK_RESOURCES_FILENAMES, appDir), + newFiles(APK_RESOURCES_FILENAMES, apkDir))) { + LOGGER.info("Copying raw resources..."); + appDir.getDirectory() + .copyToDir(apkDir, APK_RESOURCES_FILENAMES); + } + return true; + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public boolean buildResourcesFull(File appDir, HashMap flags, + Map usesFramework) + throws AndrolibException { + try { + if (! new File(appDir, "res").exists()) { + return false; + } + if (! flags.get("forceBuildAll")) { + LOGGER.info("Checking whether resources has changed..."); + } + File apkDir = new File(appDir, APK_DIRNAME); + if (flags.get("forceBuildAll") || isModified( + newFiles(APP_RESOURCES_FILENAMES, appDir), + newFiles(APK_RESOURCES_FILENAMES, apkDir))) { + LOGGER.info("Building resources..."); + + File apkFile = File.createTempFile("APKTOOL", null); + apkFile.delete(); + + File ninePatch = new File(appDir, "9patch"); + if (! ninePatch.exists()) { + ninePatch = null; + } + mAndRes.aaptPackage( + apkFile, + new File(appDir, "AndroidManifest.xml"), + new File(appDir, "res"), + ninePatch, null, parseUsesFramework(usesFramework), + flags + ); + + Directory tmpDir = new ExtFile(apkFile).getDirectory(); + tmpDir.copyToDir(apkDir, + tmpDir.containsDir("res") ? APK_RESOURCES_FILENAMES : + APK_RESOURCES_WITHOUT_RES_FILENAMES); + } + return true; + } catch (IOException ex) { + throw new AndrolibException(ex); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public boolean buildManifestRaw(ExtFile appDir, HashMap flags) + throws AndrolibException { + try { + File apkDir = new File(appDir, APK_DIRNAME); + LOGGER.info("Copying raw AndroidManifest.xml..."); + appDir.getDirectory() + .copyToDir(apkDir, APK_MANIFEST_FILENAMES); + return true; + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public boolean buildManifest(ExtFile appDir, HashMap flags, + Map usesFramework) + throws AndrolibException { + try { + if (! new File(appDir, "AndroidManifest.xml").exists()) { + return false; + } + if (! flags.get("forceBuildAll")) { + LOGGER.info("Checking whether resources has changed..."); + } + File apkDir = new File(appDir, APK_DIRNAME); + if (flags.get("forceBuildAll") || isModified( + newFiles(APK_MANIFEST_FILENAMES, appDir), + newFiles(APK_MANIFEST_FILENAMES, apkDir))) { + LOGGER.info("Building AndroidManifest.xml..."); + + File apkFile = File.createTempFile("APKTOOL", null); + apkFile.delete(); + + File ninePatch = new File(appDir, "9patch"); + if (! ninePatch.exists()) { + ninePatch = null; + } + + mAndRes.aaptPackage( + apkFile, + new File(appDir, "AndroidManifest.xml"), + null, + ninePatch, null, parseUsesFramework(usesFramework), + flags + ); + + Directory tmpDir = new ExtFile(apkFile).getDirectory(); + tmpDir.copyToDir(apkDir, APK_MANIFEST_FILENAMES); + } + return true; + } catch (IOException ex) { + throw new AndrolibException(ex); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } catch (AndrolibException ex) { + LOGGER.warning("Parse AndroidManifest.xml failed, treat it as raw file."); + return buildManifestRaw(appDir, flags); + } + } + + public void buildLib(File appDir, HashMap flags) + throws AndrolibException { + File working = new File(appDir, "lib"); + if (! working.exists()) { + return; + } + File stored = new File(appDir, APK_DIRNAME + "/lib"); + if (flags.get("forceBuildAll") || isModified(working, stored)) { + LOGGER.info("Copying libs..."); + try { + OS.rmdir(stored); + OS.cpdir(working, stored); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + } + + public void buildApk(File appDir, File outApk, HashMap flags) + throws AndrolibException { + LOGGER.info("Building apk file..."); + if (outApk.exists()) { + outApk.delete(); + } else { + File outDir = outApk.getParentFile(); + if (outDir != null && ! outDir.exists()) { + outDir.mkdirs(); + } + } + File assetDir = new File(appDir, "assets"); + if (! assetDir.exists()) { + assetDir = null; + } + mAndRes.aaptPackage(outApk, null, null, + new File(appDir, APK_DIRNAME), assetDir, null, flags); + + /* check for re-insert */ + if (flags.get("injectOriginal")) { + try { + LOGGER.info("Building resources into original apk file..."); + ZipFile editOrig = new ZipFile(mOrigApkFile.getAbsoluteFile()); + + // no compression levels, paras + ZipParameters parameters = new ZipParameters(); + parameters.setCompressionMethod(Zip4jConstants.COMP_STORE); + parameters.setCompressionLevel(0); + parameters.setIncludeRootFolder(true); + parameters.setRootFolderInZip("/"); + + // add res folder + editOrig.addFolder(new File(appDir, APK_DIRNAME + "/res").getAbsolutePath(), parameters); + + // add assets, if there + if (assetDir != null) { + //editOrig.addFolder(new File(appDir, APK_DIRNAME + "/assets").getAbsolutePath(), parameters); + } + + // add resources.arsc + parameters.setFileNameInZip("resources.arsc"); + // editOrig.addFile(new File(appDir, "resources.arsc"), parameters); + } catch(ZipException ex) { + throw new AndrolibException(ex); + } + } + } + + public void publicizeResources(File arscFile) throws AndrolibException { + mAndRes.publicizeResources(arscFile); + } + + public void installFramework(File frameFile, String tag) + throws AndrolibException { + mAndRes.installFramework(frameFile, tag); + } + + public boolean isFrameworkApk(ResTable resTable) { + for (ResPackage pkg : resTable.listMainPackages()) { + if (pkg.getId() < 64) { + return true; + } + } + return false; + } + + public static String getVersion() { + String version = ApktoolProperties.get("application.version"); + return version.endsWith("-SNAPSHOT") ? + version.substring(0, version.length() - 9) + '.' + + ApktoolProperties.get("git.commit.id.abbrev") + : version; + } + + private File[] parseUsesFramework(Map usesFramework) + throws AndrolibException { + if (usesFramework == null) { + return null; + } + + List ids = (List) usesFramework.get("ids"); + if (ids == null || ids.isEmpty()) { + return null; + } + + String tag = (String) usesFramework.get("tag"); + File[] files = new File[ids.size()]; + int i = 0; + for (int id : ids) { + files[i++] = mAndRes.getFrameworkApk(id, tag); + } + return files; + } + + private boolean isModified(File working, File stored) { + if (! stored.exists()) { + return true; + } + return BrutIO.recursiveModifiedTime(working) > + BrutIO.recursiveModifiedTime(stored); + } + + private boolean isModified(File[] working, File[] stored) { + for (int i = 0; i < stored.length; i++) { + if (! stored[i].exists()) { + return true; + } + } + return BrutIO.recursiveModifiedTime(working) > + BrutIO.recursiveModifiedTime(stored); + } + + private File[] newFiles(String[] names, File dir) { + File[] files = new File[names.length]; + for (int i = 0; i < names.length; i++) { + files[i] = new File(dir, names[i]); + } + return files; + } + + public void setApkFile(File apkFile) { + mOrigApkFile = new ExtFile(apkFile); + } + + + private ExtFile mOrigApkFile = null; + + private final static Logger LOGGER = + Logger.getLogger(Androlib.class.getName()); + + private final static String SMALI_DIRNAME = "smali"; + private final static String APK_DIRNAME = "build/apk"; + private final static String[] APK_RESOURCES_FILENAMES = + new String[]{"resources.arsc", "AndroidManifest.xml", "res"}; + private final static String[] APK_RESOURCES_WITHOUT_RES_FILENAMES = + new String[]{"resources.arsc", "AndroidManifest.xml"}; + private final static String[] APP_RESOURCES_FILENAMES = + new String[]{"AndroidManifest.xml", "res"}; + private final static String[] APK_MANIFEST_FILENAMES = + new String[]{"AndroidManifest.xml"}; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AndrolibException.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AndrolibException.java new file mode 100644 index 0000000..d160134 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AndrolibException.java @@ -0,0 +1,39 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.common.BrutException; + +/** + * @author Ryszard Wiśniewski + */ +public class AndrolibException extends BrutException { + public AndrolibException() { + } + + public AndrolibException(String message) { + super(message); + } + + public AndrolibException(String message, Throwable cause) { + super(message, cause); + } + + public AndrolibException(Throwable cause) { + super(cause); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java new file mode 100644 index 0000000..e1ca29a --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java @@ -0,0 +1,275 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.err.InFileNotFoundException; +import brut.androlib.err.OutDirExistsException; +import brut.androlib.res.AndrolibResources; +import brut.androlib.res.data.ResPackage; +import brut.androlib.res.data.ResTable; +import brut.androlib.res.util.ExtFile; +import brut.common.BrutException; +import brut.directory.DirectoryException; +import brut.util.OS; +import java.io.File; +import java.util.*; + +/** + * @author Ryszard Wiśniewski + */ +public class ApkDecoder { + public ApkDecoder() { + this(new Androlib()); + } + + public ApkDecoder(Androlib androlib) { + mAndrolib = androlib; + } + + public ApkDecoder(File apkFile) { + this(apkFile, new Androlib()); + } + + public ApkDecoder(File apkFile, Androlib androlib) { + mAndrolib = androlib; + setApkFile(apkFile); + } + + public void setApkFile(File apkFile) { + mApkFile = new ExtFile(apkFile); + mResTable = null; + } + + public void setOutDir(File outDir) throws AndrolibException { + mOutDir = outDir; + } + + public void decode() throws AndrolibException { + File outDir = getOutDir(); + + if (! mForceDelete && outDir.exists()) { + throw new OutDirExistsException(); + } + + if (! mApkFile.isFile() || ! mApkFile.canRead() ) { + throw new InFileNotFoundException(); + } + + try { + OS.rmdir(outDir); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + outDir.mkdirs(); + + if (hasSources()) { + switch (mDecodeSources) { + case DECODE_SOURCES_NONE: + mAndrolib.decodeSourcesRaw(mApkFile, outDir, mDebug); + break; + case DECODE_SOURCES_SMALI: + mAndrolib.decodeSourcesSmali(mApkFile, outDir, mDebug, mBakDeb); + break; + case DECODE_SOURCES_JAVA: + mAndrolib.decodeSourcesJava(mApkFile, outDir, mDebug); + break; + } + } + + if (hasResources()) { + switch (mDecodeResources) { + case DECODE_RESOURCES_NONE: + mAndrolib.decodeResourcesRaw(mApkFile, outDir); + break; + case DECODE_RESOURCES_FULL: + mAndrolib.decodeResourcesFull(mApkFile, outDir, + getResTable()); + break; + } + } else { + // if there's no resources.asrc, decode the manifest without looking up + // attribute references + if (hasManifest()) { + switch (mDecodeResources) { + case DECODE_RESOURCES_NONE: + mAndrolib.decodeManifestRaw(mApkFile, outDir); + break; + case DECODE_RESOURCES_FULL: + mAndrolib.decodeManifestFull(mApkFile, outDir, + getResTable()); + break; + } + } + } + + mAndrolib.decodeRawFiles(mApkFile, outDir); + writeMetaFile(); + } + + public void setDecodeSources(short mode) throws AndrolibException { + if (mode != DECODE_SOURCES_NONE && mode != DECODE_SOURCES_SMALI + && mode != DECODE_SOURCES_JAVA) { + throw new AndrolibException("Invalid decode sources mode: " + mode); + } + mDecodeSources = mode; + } + + public void setDecodeResources(short mode) throws AndrolibException { + if (mode != DECODE_RESOURCES_NONE && mode != DECODE_RESOURCES_FULL) { + throw new AndrolibException("Invalid decode resources mode"); + } + mDecodeResources = mode; + } + + public void setDebugMode(boolean debug) { + mDebug = debug; + } + + public void setBaksmaliDebugMode(boolean bakdeb) { + mBakDeb = bakdeb; + } + + public void setForceDelete(boolean forceDelete) { + mForceDelete = forceDelete; + } + + public void setFrameworkTag(String tag) throws AndrolibException { + mFrameTag = tag; + if (mResTable != null) { + getResTable().setFrameTag(tag); + } + } + + public void setKeepBrokenResources(boolean keepBrokenResources) { + mKeepBrokenResources = keepBrokenResources; + } + + public ResTable getResTable() throws AndrolibException { + if (mResTable == null) { + boolean hasResources = hasResources(); + boolean hasManifest = hasManifest(); + if (! (hasManifest || hasResources)) { + throw new AndrolibException( + "Apk doesn't contain either AndroidManifest.xml file or resources.arsc file"); + } + AndrolibResources.sKeepBroken = mKeepBrokenResources; + mResTable = mAndrolib.getResTable(mApkFile, hasResources); + mResTable.setFrameTag(mFrameTag); + } + return mResTable; + } + + public boolean hasSources() throws AndrolibException { + try { + return mApkFile.getDirectory().containsFile("classes.dex"); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public boolean hasManifest() throws AndrolibException { + try { + return mApkFile.getDirectory().containsFile("AndroidManifest.xml"); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public boolean hasResources() throws AndrolibException { + try { + return mApkFile.getDirectory().containsFile("resources.arsc"); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public final static short DECODE_SOURCES_NONE = 0x0000; + public final static short DECODE_SOURCES_SMALI = 0x0001; + public final static short DECODE_SOURCES_JAVA = 0x0002; + + public final static short DECODE_RESOURCES_NONE = 0x0100; + public final static short DECODE_RESOURCES_FULL = 0x0101; + + + private File getOutDir() throws AndrolibException { + if (mOutDir == null) { + throw new AndrolibException("Out dir not set"); + } + return mOutDir; + } + + private void writeMetaFile() throws AndrolibException { + Map meta = new LinkedHashMap(); + meta.put("version", Androlib.getVersion()); + meta.put("apkFileName", mApkFile.getName()); + + if (mDecodeResources != DECODE_RESOURCES_NONE && (hasManifest() || hasResources())) { + meta.put("isFrameworkApk", + Boolean.valueOf(mAndrolib.isFrameworkApk(getResTable()))); + putUsesFramework(meta); + putSdkInfo(meta); + } + + mAndrolib.writeMetaFile(mOutDir, meta); + } + + private void putUsesFramework(Map meta) + throws AndrolibException { + Set pkgs = getResTable().listFramePackages(); + if (pkgs.isEmpty()) { + return; + } + + Integer[] ids = new Integer[pkgs.size()]; + int i = 0; + for (ResPackage pkg : pkgs) { + ids[i++] = pkg.getId(); + } + Arrays.sort(ids); + + Map uses = new LinkedHashMap(); + uses.put("ids", ids); + + if (mFrameTag != null) { + uses.put("tag", mFrameTag); + } + + meta.put("usesFramework", uses); + } + + private void putSdkInfo(Map meta) + throws AndrolibException { + Map info = getResTable().getSdkInfo(); + if (info.size() > 0) { + meta.put("sdkInfo", info); + } + + } + + private final Androlib mAndrolib; + + private ExtFile mApkFile; + private File mOutDir; + private ResTable mResTable; + private short mDecodeSources = DECODE_SOURCES_SMALI; + private short mDecodeResources = DECODE_RESOURCES_FULL; + private boolean mDebug = false; + private boolean mForceDelete = false; + private String mFrameTag; + private boolean mKeepBrokenResources = false; + private boolean mBakDeb = true; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApktoolProperties.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApktoolProperties.java new file mode 100644 index 0000000..f054579 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApktoolProperties.java @@ -0,0 +1,76 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.logging.Logger; +import org.jf.baksmali.baksmali; +import org.jf.smali.main; + +/** + * @author Ryszard Wiśniewski + */ +public class ApktoolProperties { + public static String get(String key) { + return get().getProperty(key); + } + + public static Properties get() { + if (sProps == null) { + loadProps(); + } + return sProps; + } + + private static void loadProps() { + InputStream in = ApktoolProperties.class + .getResourceAsStream("apktool.properties"); + sProps = new Properties(); + try { + sProps.load(in); + in.close(); + } catch (IOException ex) { + LOGGER.warning("Can't load properties."); + } + + InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("properties/baksmali.properties"); + Properties properties = new Properties(); + String version = "(unknown)"; + try { + properties.load(templateStream); + version = properties.getProperty("application.version"); + } catch (IOException ex) { + } + sProps.put("baksmaliVersion", version); + templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties"); + properties = new Properties(); + version = "(unknown)"; + try { + properties.load(templateStream); + version = properties.getProperty("application.version"); + } catch (IOException ex) { + } + sProps.put("smaliVersion", version); + } + + private static Properties sProps; + + private static final Logger LOGGER = + Logger.getLogger(ApktoolProperties.class.getName()); +} \ No newline at end of file diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/CantFind9PatchChunk.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/CantFind9PatchChunk.java new file mode 100644 index 0000000..0e80bc6 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/CantFind9PatchChunk.java @@ -0,0 +1,40 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.err; + +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class CantFind9PatchChunk extends AndrolibException { + + public CantFind9PatchChunk(Throwable cause) { + super(cause); + } + + public CantFind9PatchChunk(String message, Throwable cause) { + super(message, cause); + } + + public CantFind9PatchChunk(String message) { + super(message); + } + + public CantFind9PatchChunk() { + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/CantFindFrameworkResException.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/CantFindFrameworkResException.java new file mode 100644 index 0000000..0c0c10e --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/CantFindFrameworkResException.java @@ -0,0 +1,40 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.err; + +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class CantFindFrameworkResException extends AndrolibException { + + public CantFindFrameworkResException(Throwable cause, int id) { + super(cause); + mPkgId = id; + } + + public CantFindFrameworkResException(int id) { + mPkgId = id; + } + + public int getPkgId() { + return mPkgId; + } + + private final int mPkgId; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/InFileNotFoundException.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/InFileNotFoundException.java new file mode 100644 index 0000000..af8de52 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/InFileNotFoundException.java @@ -0,0 +1,40 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.err; + +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class InFileNotFoundException extends AndrolibException { + + public InFileNotFoundException(Throwable cause) { + super(cause); + } + + public InFileNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public InFileNotFoundException(String message) { + super(message); + } + + public InFileNotFoundException() { + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/OutDirExistsException.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/OutDirExistsException.java new file mode 100644 index 0000000..59b26ac --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/OutDirExistsException.java @@ -0,0 +1,40 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.err; + +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class OutDirExistsException extends AndrolibException { + + public OutDirExistsException(Throwable cause) { + super(cause); + } + + public OutDirExistsException(String message, Throwable cause) { + super(message, cause); + } + + public OutDirExistsException(String message) { + super(message); + } + + public OutDirExistsException() { + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/UndefinedResObject.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/UndefinedResObject.java new file mode 100644 index 0000000..3bcfa35 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/err/UndefinedResObject.java @@ -0,0 +1,39 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.err; + +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class UndefinedResObject extends AndrolibException { + public UndefinedResObject(Throwable cause) { + super(cause); + } + + public UndefinedResObject(String message, Throwable cause) { + super(message, cause); + } + + public UndefinedResObject(String message) { + super(message); + } + + public UndefinedResObject() { + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/java/AndrolibJava.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/java/AndrolibJava.java new file mode 100644 index 0000000..f3be8f7 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/java/AndrolibJava.java @@ -0,0 +1,34 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.java; + +import brut.androlib.res.util.ExtFile; +import java.io.File; + +/** + * @author Ryszard Wiśniewski + */ +public class AndrolibJava { + + public void decode(ExtFile apkFile, File outDir) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + public void build(File javaDir, File dex) { + throw new UnsupportedOperationException("Not yet implemented"); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/mod/IndentingWriter.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/mod/IndentingWriter.java new file mode 100644 index 0000000..99b1ce2 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/mod/IndentingWriter.java @@ -0,0 +1,29 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.mod; + +import java.io.Writer; + +/** + * @author Ryszard Wiśniewski + */ +public class IndentingWriter extends org.jf.util.IndentingWriter { + + public IndentingWriter(Writer writer) { + super(writer); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/mod/SmaliMod.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/mod/SmaliMod.java new file mode 100644 index 0000000..8927026 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/mod/SmaliMod.java @@ -0,0 +1,93 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.mod; + +import java.io.*; +import org.antlr.runtime.*; +import org.antlr.runtime.tree.CommonTree; +import org.antlr.runtime.tree.CommonTreeNodeStream; +import org.jf.dexlib.DexFile; +import org.jf.smali.*; + +/** + * @author Ryszard Wiśniewski + */ +public class SmaliMod { + + public static boolean assembleSmaliFile(InputStream smaliStream, + String name, DexFile dexFile, boolean verboseErrors, + boolean oldLexer, boolean printTokens) + throws IOException, RecognitionException { + CommonTokenStream tokens; + + + boolean lexerErrors = false; + LexerErrorInterface lexer; + + if (oldLexer) { + ANTLRInputStream input = new ANTLRInputStream(smaliStream, "UTF-8"); + input.name = name; + + lexer = new smaliLexer(input); + tokens = new CommonTokenStream((TokenSource)lexer); + } else { + InputStreamReader reader = + new InputStreamReader(smaliStream, "UTF-8"); + + lexer = new smaliFlexLexer(reader); + tokens = new CommonTokenStream((TokenSource)lexer); + } + + if (printTokens) { + tokens.getTokens(); + + for (int i=0; i 0 || lexer.getNumberOfSyntaxErrors() > 0) { + return false; + } + + CommonTree t = (CommonTree) result.getTree(); + + CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); + treeStream.setTokenStream(tokens); + + smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); + + dexGen.dexFile = dexFile; + dexGen.smali_file(); + + if (dexGen.getNumberOfSyntaxErrors() > 0) { + return false; + } + + return true; + } +} 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 new file mode 100644 index 0000000..06219ef --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java @@ -0,0 +1,583 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.AndrolibException; +import brut.androlib.err.CantFindFrameworkResException; +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.*; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import brut.common.BrutException; +import brut.directory.*; +import brut.util.*; +import java.io.*; +import java.util.*; +import java.util.logging.Logger; +import java.util.zip.*; +import org.apache.commons.io.IOUtils; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +final public class AndrolibResources { + 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); + if (loadMainPkg) { + loadMainPkg(resTable, apkFile); + } + return resTable; + } + + public ResPackage loadMainPkg(ResTable resTable, ExtFile apkFile) + throws AndrolibException { + LOGGER.info("Loading resource table..."); + ResPackage[] pkgs = getResPackagesFromApk( + apkFile, resTable, sKeepBroken); + ResPackage pkg = null; + + switch (pkgs.length) { + case 1: + pkg = pkgs[0]; + break; + case 2: + if (pkgs[0].getName().equals("android")) { + LOGGER.warning("Skipping \"android\" package group"); + pkg = pkgs[1]; + } else if (pkgs[0].getName().equals("com.htc")) { + LOGGER.warning("Skipping \"htc\" stupid package group"); + pkg = pkgs[1]; + } + break; + } + + if (pkg == null) { + throw new AndrolibException( + "Arsc files with zero or multiple packages"); + } + + resTable.addPackage(pkg, true); + LOGGER.info("Loaded."); + return pkg; + } + + public ResPackage loadFrameworkPkg(ResTable resTable, int id, + String frameTag) throws AndrolibException { + File apk = getFrameworkApk(id, frameTag); + + LOGGER.info("Loading resource table from file: " + apk); + ResPackage[] pkgs = getResPackagesFromApk( + new ExtFile(apk), resTable, true); + + if (pkgs.length != 1) { + throw new AndrolibException( + "Arsc files with zero or multiple packages"); + } + + ResPackage pkg = pkgs[0]; + if (pkg.getId() != id) { + throw new AndrolibException("Expected pkg of id: " + + String.valueOf(id) + ", got: " + pkg.getId()); + } + + resTable.addPackage(pkg, false); + LOGGER.info("Loaded."); + return pkg; + } + + public void decodeManifest(ResTable resTable, ExtFile apkFile, File outDir) + throws AndrolibException { + + Duo duo = getManifestFileDecoder(); + ResFileDecoder fileDecoder = duo.m1; + + // Set ResAttrDecoder + duo.m2.setAttrDecoder(new ResAttrDecoder()); + ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); + + // Fake ResPackage + attrDecoder.setCurrentPackage(new ResPackage(resTable, 0, null)); + + Directory inApk, out; + try { + inApk = apkFile.getDirectory(); + out = new FileDirectory(outDir); + + LOGGER.info("Decoding AndroidManifest.xml with only framework resources..."); + fileDecoder.decodeManifest( + inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); + + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public void decode(ResTable resTable, ExtFile apkFile, File outDir) + throws AndrolibException { + Duo duo = getResFileDecoder(); + ResFileDecoder fileDecoder = duo.m1; + ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); + + attrDecoder.setCurrentPackage( + resTable.listMainPackages().iterator().next()); + + Directory inApk, in = null, out; + try { + inApk = apkFile.getDirectory(); + out = new FileDirectory(outDir); + + LOGGER.info("Decoding AndroidManifest.xml with resources..."); + + fileDecoder.decodeManifest( + inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); + + if (inApk.containsDir("res")) { + in = inApk.getDir("res"); + } + out = out.createDir("res"); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + + ExtMXSerializer xmlSerializer = getResXmlSerializer(); + for (ResPackage pkg : resTable.listMainPackages()) { + attrDecoder.setCurrentPackage(pkg); + + LOGGER.info("Decoding file-resources..."); + for (ResResource res : pkg.listFiles()) { + fileDecoder.decode(res, in, out); + } + + LOGGER.info("Decoding values */* XMLs..."); + for (ResValuesFile valuesFile : pkg.listValuesFiles()) { + generateValuesFile(valuesFile, out, xmlSerializer); + } + generatePublicXml(pkg, out, xmlSerializer); + LOGGER.info("Done."); + } + + AndrolibException decodeError = duo.m2.getFirstError(); + if (decodeError != null) { + throw decodeError; + } + } + + public void setSdkInfo(Map map) { + if(map != null) { + mMinSdkVersion = map.get("minSdkVersion"); + mTargetSdkVersion = map.get("targetSdkVersion"); + mMaxSdkVersion = map.get("maxSdkVersion"); + } + } + + public void aaptPackage(File apkFile, File manifest, File resDir, + File rawDir, File assetDir, File[] include, HashMap flags) + throws AndrolibException { + List cmd = new ArrayList(); + + cmd.add("aapt"); + cmd.add("p"); + + if (flags.get("verbose")) { + cmd.add("-v"); + } + + if (flags.get("update")) { + cmd.add("-u"); + } + if (mMinSdkVersion != null) { + cmd.add("--min-sdk-version"); + cmd.add(mMinSdkVersion); + } + if (mTargetSdkVersion != null) { + cmd.add("--target-sdk-version"); + cmd.add(mTargetSdkVersion); + } + if (mMaxSdkVersion != null) { + cmd.add("--max-sdk-version"); + cmd.add(mMaxSdkVersion); + } + cmd.add("-F"); + cmd.add(apkFile.getAbsolutePath()); + + if (flags.get("framework")) { + cmd.add("-x"); +// cmd.add("-0"); +// cmd.add("arsc"); + } + + if (include != null) { + for (File file : include) { + cmd.add("-I"); + cmd.add(file.getPath()); + } + } + if (resDir != null) { + cmd.add("-S"); + cmd.add(resDir.getAbsolutePath()); + } + if (manifest != null) { + cmd.add("-M"); + cmd.add(manifest.getAbsolutePath()); + } + if (assetDir != null) { + cmd.add("-A"); + cmd.add(assetDir.getAbsolutePath()); + } + if (rawDir != null) { + cmd.add(rawDir.getAbsolutePath()); + } + + try { + OS.exec(cmd.toArray(new String[0])); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + + public boolean detectWhetherAppIsFramework(File appDir) + throws AndrolibException { + File publicXml = new File(appDir, "res/values/public.xml"); + if (! publicXml.exists()) { + return false; + } + + Iterator it; + try { + it = IOUtils.lineIterator( + new FileReader(new File(appDir, "res/values/public.xml"))); + } catch (FileNotFoundException ex) { + throw new AndrolibException( + "Could not detect whether app is framework one", ex); + } + it.next(); + it.next(); + return it.next().contains("0x01"); + } + + public void tagSmaliResIDs(ResTable resTable, File smaliDir) + throws AndrolibException { + new ResSmaliUpdater().tagResIDs(resTable, smaliDir); + } + + public void updateSmaliResIDs(ResTable resTable, File smaliDir) throws AndrolibException { + new ResSmaliUpdater().updateResIDs(resTable, smaliDir); + } + + public Duo getResFileDecoder() { + ResStreamDecoderContainer decoders = + new ResStreamDecoderContainer(); + decoders.setDecoder("raw", new ResRawStreamDecoder()); + decoders.setDecoder("9patch", new Res9patchStreamDecoder()); + + AXmlResourceParser axmlParser = new AXmlResourceParser(); + axmlParser.setAttrDecoder(new ResAttrDecoder()); + decoders.setDecoder("xml", + new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); + + return new Duo( + new ResFileDecoder(decoders), axmlParser); + } + + public Duo getManifestFileDecoder() { + ResStreamDecoderContainer decoders = + new ResStreamDecoderContainer(); + + AXmlResourceParser axmlParser = new AXmlResourceParser(); + + decoders.setDecoder("xml", + new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); + + return new Duo( + new ResFileDecoder(decoders), axmlParser); + } + + public ExtMXSerializer getResXmlSerializer() { + ExtMXSerializer serial = new ExtMXSerializer(); + serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION + , " "); + serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR, + System.getProperty("line.separator")); + serial.setProperty(ExtMXSerializer.PROPERTY_DEFAULT_ENCODING, "utf-8"); + serial.setDisabledAttrEscape(true); + return serial; + } + + private void generateValuesFile(ResValuesFile valuesFile, Directory out, + ExtXmlSerializer serial) throws AndrolibException { + try { + OutputStream outStream = out.getFileOutput(valuesFile.getPath()); + serial.setOutput((outStream), null); + serial.startDocument(null, null); + serial.startTag(null, "resources"); + + for (ResResource res : valuesFile.listResources()) { + if (valuesFile.isSynthesized(res)) { + continue; + } + ((ResValuesXmlSerializable) res.getValue()) + .serializeToResValuesXml(serial, res); + } + + serial.endTag(null, "resources"); + serial.newLine(); + serial.endDocument(); + serial.flush(); + outStream.close(); + } catch (IOException ex) { + throw new AndrolibException( + "Could not generate: " + valuesFile.getPath(), ex); + } catch (DirectoryException ex) { + throw new AndrolibException( + "Could not generate: " + valuesFile.getPath(), ex); + } + } + + private void generatePublicXml(ResPackage pkg, Directory out, + XmlSerializer serial) throws AndrolibException { +// try { +// OutputStream outStream = out.getFileOutput("values/public.xml"); +// serial.setOutput(outStream, null); +// serial.startDocument(null, null); +// serial.startTag(null, "resources"); +// +// for (ResResSpec spec : pkg.listResSpecs()) { +// serial.startTag(null, "public"); +// serial.attribute(null, "type", spec.getType().getName()); +// serial.attribute(null, "name", spec.getName()); +// serial.attribute(null, "id", String.format( +// "0x%08x", spec.getId().id)); +// serial.endTag(null, "public"); +// } +// +// serial.endTag(null, "resources"); +// serial.endDocument(); +// serial.flush(); +// outStream.close(); +// } catch (IOException ex) { +// throw new AndrolibException( +// "Could not generate public.xml file", ex); +// } catch (DirectoryException ex) { +// throw new AndrolibException( +// "Could not generate public.xml file", ex); +// } + } + + private ResPackage[] getResPackagesFromApk(ExtFile apkFile, + ResTable resTable, boolean keepBroken) throws AndrolibException { + try { + return ARSCDecoder.decode( + apkFile.getDirectory().getFileInput("resources.arsc"), false, + keepBroken, resTable).getPackages(); + } catch (DirectoryException ex) { + throw new AndrolibException( + "Could not load resources.arsc from file: " + apkFile, ex); + } + } + + public File getFrameworkApk(int id, String frameTag) + throws AndrolibException { + File dir = getFrameworkDir(); + File apk; + + if (frameTag != null) { + apk = new File(dir, String.valueOf(id) + '-' + frameTag + ".apk"); + if (apk.exists()) { + return apk; + } + } + + apk = new File(dir, String.valueOf(id) + ".apk"); + if (apk.exists()) { + return apk; + } + + if (id == 1) { + InputStream in = null; + OutputStream out = null; + try { + in = AndrolibResources.class.getResourceAsStream( + "/brut/androlib/android-framework.jar"); + out = new FileOutputStream(apk); + IOUtils.copy(in, out); + return apk; + } catch (IOException ex) { + throw new AndrolibException(ex); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ex) {} + } + if (out != null) { + try { + out.close(); + } catch (IOException ex) {} + } + } + } + + throw new CantFindFrameworkResException(id); + } + + public void installFramework(File frameFile, String tag) + throws AndrolibException { + InputStream in = null; + ZipOutputStream out = null; + try { + ZipFile zip = new ZipFile(frameFile); + ZipEntry entry = zip.getEntry("resources.arsc"); + + if (entry == null) { + throw new AndrolibException("Can't find resources.arsc file"); + } + + in = zip.getInputStream(entry); + byte[] data = IOUtils.toByteArray(in); + + ARSCData arsc = ARSCDecoder.decode( + new ByteArrayInputStream(data), true, true); + publicizeResources(data, arsc.getFlagsOffsets()); + + File outFile = new File(getFrameworkDir(), + String.valueOf(arsc.getOnePackage().getId()) + + (tag == null ? "" : '-' + tag) + ".apk"); + + out = new ZipOutputStream(new FileOutputStream(outFile)); + out.setMethod(ZipOutputStream.STORED); + CRC32 crc = new CRC32(); + crc.update(data); + entry = new ZipEntry("resources.arsc"); + entry.setSize(data.length); + entry.setCrc(crc.getValue()); + out.putNextEntry(entry); + out.write(data); + + LOGGER.info("Framework installed to: " + outFile); + } catch (ZipException ex) { + throw new AndrolibException(ex); + } catch (IOException ex) { + throw new AndrolibException(ex); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ex) {} + } + if (out != null) { + try { + out.close(); + } catch (IOException ex) {} + } + } + } + + public void publicizeResources(File arscFile) throws AndrolibException { + byte[] data = new byte[(int) arscFile.length()]; + + InputStream in = null; + OutputStream out = null; + try { + in = new FileInputStream(arscFile); + in.read(data); + + publicizeResources(data); + + out = new FileOutputStream(arscFile); + out.write(data); + } catch (IOException ex) { + throw new AndrolibException(ex); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ex) {} + } + if (out != null) { + try { + out.close(); + } catch (IOException ex) {} + } + } + } + + public void publicizeResources(byte[] arsc) throws AndrolibException { + publicizeResources(arsc, + ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true) + .getFlagsOffsets()); + } + + public void publicizeResources(byte[] arsc, FlagsOffset[] flagsOffsets) + throws AndrolibException { + for (FlagsOffset flags : flagsOffsets) { + int offset = flags.offset + 3; + int end = offset + 4 * flags.count; + while(offset < end) { + arsc[offset] |= (byte) 0x40; + offset += 4; + } + } + } + + private File getFrameworkDir() throws AndrolibException { + String path; + + /* store in user-home, for Mac OS X */ + if (System.getProperty("os.name").equals("Mac OS X")) { + path = System.getProperty("user.home") + File.separatorChar + + "Library/Application Support/apktool/framework"; } + else { + path = System.getProperty("user.home") + File.separatorChar + + "apktool" + File.separatorChar + "framework"; + } + File dir = new File(path); + if (! dir.exists()) { + if (! dir.mkdirs()) { + throw new AndrolibException("Can't create directory: " + dir); + } + } + return dir; + } + + public File getAndroidResourcesFile() throws AndrolibException { + try { + return Jar.getResourceAsFile("/brut/androlib/android-framework.jar"); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + + + // TODO: dirty static hack. I have to refactor decoding mechanisms. + public static boolean sKeepBroken = false; + + + private final static Logger LOGGER = + Logger.getLogger(AndrolibResources.class.getName()); + + private String mMinSdkVersion = null; + private String mMaxSdkVersion = null; + private String mTargetSdkVersion = null; + +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResSmaliUpdater.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResSmaliUpdater.java new file mode 100644 index 0000000..d2cd8eb --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResSmaliUpdater.java @@ -0,0 +1,167 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.AndrolibException; +import brut.androlib.err.UndefinedResObject; +import brut.androlib.res.data.ResResSpec; +import brut.androlib.res.data.ResTable; +import brut.directory.Directory; +import brut.directory.DirectoryException; +import brut.directory.FileDirectory; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.io.IOUtils; + +/** + * @author Ryszard Wiśniewski + */ +public class ResSmaliUpdater { + public void tagResIDs(ResTable resTable, File smaliDir) + throws AndrolibException { + Directory dir = null; + try { + dir = new FileDirectory(smaliDir); + } catch (DirectoryException ex) { + throw new AndrolibException( + "Could not tag res IDs", ex); + } + for (String fileName : dir.getFiles(true)) { + try { + tagResIdsForFile(resTable, dir, fileName); + } catch (IOException ex) { + throw new AndrolibException( + "Could not tag resIDs for file: " + fileName, ex); + } catch (DirectoryException ex) { + throw new AndrolibException( + "Could not tag resIDs for file: " + fileName, ex); + } catch (AndrolibException ex) { + throw new AndrolibException( + "Could not tag resIDs for file: " + fileName, ex); + } + } + } + + public void updateResIDs(ResTable resTable, File smaliDir) + throws AndrolibException { + try { + Directory dir = new FileDirectory(smaliDir); + for (String fileName : dir.getFiles(true)) { + Iterator it = + IOUtils.readLines(dir.getFileInput(fileName)).iterator(); + PrintWriter out = new PrintWriter(dir.getFileOutput(fileName)); + while (it.hasNext()) { + String line = it.next(); + out.println(line); + Matcher m1 = RES_NAME_PATTERN.matcher(line); + if (! m1.matches()) { + continue; + } + Matcher m2 = RES_ID_PATTERN.matcher(it.next()); + if (! m2.matches()) { + throw new AndrolibException(); + } + int resID = resTable.getPackage(m1.group(1)) + .getType(m1.group(2)).getResSpec(m1.group(3)) + .getId().id; + if (m2.group(1) != null) { + out.println(String.format( + RES_ID_FORMAT_FIELD, m2.group(1), resID)); + } else { + out.println(String.format( + RES_ID_FORMAT_CONST, m2.group(2), resID)); + } + } + out.close(); + } + } catch (IOException ex) { + throw new AndrolibException( + "Could not tag res IDs for: " + smaliDir.getAbsolutePath(), ex); + } catch (DirectoryException ex) { + throw new AndrolibException( + "Could not tag res IDs for: " + smaliDir.getAbsolutePath(), ex); + } + } + + private void tagResIdsForFile(ResTable resTable, Directory dir, + String fileName) throws IOException, DirectoryException, + AndrolibException { + Iterator it = + IOUtils.readLines(dir.getFileInput(fileName)).iterator(); + PrintWriter out = new PrintWriter(dir.getFileOutput(fileName)); + while (it.hasNext()) { + String line = it.next(); + if (RES_NAME_PATTERN.matcher(line).matches()) { + out.println(line); + out.println(it.next()); + continue; + } + Matcher m = RES_ID_PATTERN.matcher(line); + if (m.matches()) { + int resID = parseResID(m.group(3)); + if (resID != -1) { + try { + ResResSpec spec = resTable.getResSpec(resID); + out.println(String.format( + RES_NAME_FORMAT, spec.getFullName())); + } catch (UndefinedResObject ex) { + if (! R_FILE_PATTERN.matcher(fileName).matches()) { + LOGGER.warning(String.format( + "Undefined resource spec in %s: 0x%08x" + , fileName, resID)); + } + } + } + } + out.println(line); + } + out.close(); + } + + private int parseResID(String resIDHex) { + if (resIDHex.endsWith("ff")) { + return -1; + } + int resID = Integer.valueOf(resIDHex, 16); + if (resIDHex.length() == 4) { + resID = resID << 16; + } + return resID; + } + + private final static String RES_ID_FORMAT_FIELD = + ".field %s:I = 0x%08x"; + private final static String RES_ID_FORMAT_CONST = + " const %s, 0x%08x"; + private final static Pattern RES_ID_PATTERN = Pattern.compile( + "^(?:\\.field (.+?):I =| const(?:|/(?:|high)16) ([pv]\\d+?),) 0x(7[a-f]0[1-9a-f](?:|[0-9a-f]{4}))$"); + private final static String RES_NAME_FORMAT = + "# APKTOOL/RES_NAME: %s"; + private final static Pattern RES_NAME_PATTERN = Pattern.compile( + "^# APKTOOL/RES_NAME: ([a-zA-Z0-9.]+):([a-z]+)/([a-zA-Z0-9._]+)$"); + + private final static Pattern R_FILE_PATTERN = Pattern.compile( + ".*R\\$[a-z]+\\.smali$"); + + private final static Logger LOGGER = + Logger.getLogger(ResSmaliUpdater.class.getName()); +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfig.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfig.java new file mode 100644 index 0000000..917ec50 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfig.java @@ -0,0 +1,74 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data; + +import brut.androlib.AndrolibException; +import brut.androlib.err.UndefinedResObject; +import java.util.*; + +/** + * @author Ryszard Wiśniewski + */ +public class ResConfig { + private final ResConfigFlags mFlags; + private final Map mResources = + new LinkedHashMap(); + + public ResConfig(ResConfigFlags flags) { + this.mFlags = flags; + } + + public Set listResources() { + return new LinkedHashSet(mResources.values()); + } + + public ResResource getResource(ResResSpec spec) throws AndrolibException { + ResResource res = mResources.get(spec); + if (res == null) { + throw new UndefinedResObject(String.format( + "resource: spec=%s, config=%s", spec, this)); + } + return res; + } + + public Set listResSpecs() { + return mResources.keySet(); + } + + public ResConfigFlags getFlags() { + return mFlags; + } + + public void addResource(ResResource res) + throws AndrolibException { + addResource(res, false); + } + + public void addResource(ResResource res, boolean overwrite) + throws AndrolibException { + ResResSpec spec = res.getResSpec(); + if (mResources.put(spec, res) != null && ! overwrite) { + throw new AndrolibException(String.format( + "Multiple resources: spec=%s, config=%s", spec, this)); + } + } + + @Override + public String toString() { + return mFlags.toString(); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java new file mode 100644 index 0000000..751564f --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java @@ -0,0 +1,443 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data; + +import java.util.logging.Logger; + +/** + * @author Ryszard Wiśniewski + */ +public class ResConfigFlags { + public final short mcc; + public final short mnc; + + public final char[] language; + public final char[] country; + + public final byte orientation; + public final byte touchscreen; + public final short density; + + public final byte keyboard; + public final byte navigation; + public final byte inputFlags; + + public final short screenWidth; + public final short screenHeight; + + public final short sdkVersion; + + public final byte screenLayout; + public final byte uiMode; + public final short smallestScreenWidthDp; + + public final short screenWidthDp; + public final short screenHeightDp; + + public final boolean isInvalid; + + private final String mQualifiers; + + public ResConfigFlags() { + mcc = 0; + mnc = 0; + language = new char[]{'\00', '\00'}; + country = new char[]{'\00', '\00'}; + orientation = ORIENTATION_ANY; + touchscreen = TOUCHSCREEN_ANY; + density = DENSITY_DEFAULT; + keyboard = KEYBOARD_ANY; + navigation = NAVIGATION_ANY; + inputFlags = KEYSHIDDEN_ANY | NAVHIDDEN_ANY; + screenWidth = 0; + screenHeight = 0; + sdkVersion = 0; + screenLayout = SCREENLONG_ANY | SCREENSIZE_ANY; + uiMode = UI_MODE_TYPE_ANY | UI_MODE_NIGHT_ANY; + smallestScreenWidthDp = 0; + screenWidthDp = 0; + screenHeightDp = 0; + isInvalid = false; + mQualifiers = ""; + } + + public ResConfigFlags(short mcc, short mnc, char[] language, char[] country, + byte orientation, byte touchscreen, short density, byte keyboard, + byte navigation, byte inputFlags, short screenWidth, + short screenHeight, short sdkVersion, byte screenLayout, + byte uiMode, short smallestScreenWidthDp, short screenWidthDp, + short screenHeightDp, boolean isInvalid) { + if (orientation < 0 || orientation > 3) { + LOGGER.warning("Invalid orientation value: " + orientation); + orientation = 0; + isInvalid = true; + } + if (touchscreen < 0 || touchscreen > 3) { + LOGGER.warning("Invalid touchscreen value: " + touchscreen); + touchscreen = 0; + isInvalid = true; + } + if (density < -1) { + LOGGER.warning("Invalid density value: " + density); + density = 0; + isInvalid = true; + } + if (keyboard < 0 || keyboard > 3) { + LOGGER.warning("Invalid keyboard value: " + keyboard); + keyboard = 0; + isInvalid = true; + } + if (navigation < 0 || navigation > 4) { + LOGGER.warning("Invalid navigation value: " + navigation); + navigation = 0; + isInvalid = true; + } + + this.mcc = mcc; + this.mnc = mnc; + this.language = language; + this.country = country; + this.orientation = orientation; + this.touchscreen = touchscreen; + this.density = density; + this.keyboard = keyboard; + this.navigation = navigation; + this.inputFlags = inputFlags; + this.screenWidth = screenWidth; + this.screenHeight = screenHeight; + this.sdkVersion = sdkVersion; + this.screenLayout = screenLayout; + this.uiMode = uiMode; + this.smallestScreenWidthDp = smallestScreenWidthDp; + this.screenWidthDp = screenWidthDp; + this.screenHeightDp = screenHeightDp; + this.isInvalid = isInvalid; + mQualifiers = generateQualifiers(); + } + + public String getQualifiers() { + return mQualifiers; + } + + private String generateQualifiers() { + StringBuilder ret = new StringBuilder(); + if (mcc != 0) { + ret.append("-mcc").append(String.format("%03d", mcc)); + if (mnc != 0) { + ret.append("-mnc").append(mnc); + } + } + if (language[0] != '\00') { + ret.append('-').append(language); + if (country[0] != '\00') { + ret.append("-r").append(country); + } + } + if (smallestScreenWidthDp != 0) { + ret.append("-sw").append(smallestScreenWidthDp).append("dp"); + } + if (screenWidthDp != 0) { + ret.append("-w").append(screenWidthDp).append("dp"); + } + if (screenHeightDp != 0) { + ret.append("-h").append(screenHeightDp).append("dp"); + } + switch (screenLayout & MASK_SCREENSIZE) { + case SCREENSIZE_SMALL: + ret.append("-small"); + break; + case SCREENSIZE_NORMAL: + ret.append("-normal"); + break; + case SCREENSIZE_LARGE: + ret.append("-large"); + break; + case SCREENSIZE_XLARGE: + ret.append("-xlarge"); + break; + } + switch (screenLayout & MASK_SCREENLONG) { + case SCREENLONG_YES: + ret.append("-long"); + break; + case SCREENLONG_NO: + ret.append("-notlong"); + break; + } + switch (orientation) { + case ORIENTATION_PORT: + ret.append("-port"); + break; + case ORIENTATION_LAND: + ret.append("-land"); + break; + case ORIENTATION_SQUARE: + ret.append("-square"); + break; + } + switch (uiMode & MASK_UI_MODE_TYPE) { + case UI_MODE_TYPE_CAR: + ret.append("-car"); + break; + case UI_MODE_TYPE_DESK: + ret.append("-desk"); + break; + case UI_MODE_TYPE_TELEVISION: + ret.append("-television"); + break; + case UI_MODE_TYPE_SMALLUI: + ret.append("-smallui"); + break; + case UI_MODE_TYPE_MEDIUMUI: + ret.append("-mediumui"); + break; + case UI_MODE_TYPE_LARGEUI: + ret.append("-largeui"); + break; + case UI_MODE_TYPE_HUGEUI: + ret.append("-hugeui"); + break; + case UI_MODE_TYPE_APPLIANCE: + ret.append("-appliance"); + break; + } + switch (uiMode & MASK_UI_MODE_NIGHT) { + case UI_MODE_NIGHT_YES: + ret.append("-night"); + break; + case UI_MODE_NIGHT_NO: + ret.append("-notnight"); + break; + } + switch (density) { + case DENSITY_DEFAULT: + break; + case DENSITY_LOW: + ret.append("-ldpi"); + break; + case DENSITY_MEDIUM: + ret.append("-mdpi"); + break; + case DENSITY_HIGH: + ret.append("-hdpi"); + break; + case DENSITY_TV: + ret.append("-tvdpi"); + break; + case DENSITY_XHIGH: + ret.append("-xhdpi"); + break; + case DENSITY_XXHIGH: + ret.append("-xxhdpi"); + break; + case DENSITY_NONE: + ret.append("-nodpi"); + break; + default: + ret.append('-').append(density).append("dpi"); + } + switch (touchscreen) { + case TOUCHSCREEN_NOTOUCH: + ret.append("-notouch"); + break; + case TOUCHSCREEN_STYLUS: + ret.append("-stylus"); + break; + case TOUCHSCREEN_FINGER: + ret.append("-finger"); + break; + } + switch (inputFlags & MASK_KEYSHIDDEN) { + case KEYSHIDDEN_NO: + ret.append("-keysexposed"); + break; + case KEYSHIDDEN_YES: + ret.append("-keyshidden"); + break; + case KEYSHIDDEN_SOFT: + ret.append("-keyssoft"); + break; + } + switch (keyboard) { + case KEYBOARD_NOKEYS: + ret.append("-nokeys"); + break; + case KEYBOARD_QWERTY: + ret.append("-qwerty"); + break; + case KEYBOARD_12KEY: + ret.append("-12key"); + break; + } + switch (inputFlags & MASK_NAVHIDDEN) { + case NAVHIDDEN_NO: + ret.append("-navexposed"); + break; + case NAVHIDDEN_YES: + ret.append("-navhidden"); + break; + } + switch (navigation) { + case NAVIGATION_NONAV: + ret.append("-nonav"); + break; + case NAVIGATION_DPAD: + ret.append("-dpad"); + break; + case NAVIGATION_TRACKBALL: + ret.append("-trackball"); + break; + case NAVIGATION_WHEEL: + ret.append("-wheel"); + break; + } + if (screenWidth != 0 && screenHeight != 0) { + if (screenWidth > screenHeight) { + ret.append(String.format("-%dx%d", screenWidth, screenHeight)); + } else { + ret.append(String.format("-%dx%d", screenHeight, screenWidth)); + } + } + if (sdkVersion > getNaturalSdkVersionRequirement()) { + ret.append("-v").append(sdkVersion); + } + if (isInvalid) { + ret.append("-ERR" + sErrCounter++); + } + + return ret.toString(); + } + + private short getNaturalSdkVersionRequirement() { + if (smallestScreenWidthDp != 0 || screenWidthDp != 0 + || screenHeightDp != 0) { + return 13; + } + if ((uiMode & (MASK_UI_MODE_TYPE | MASK_UI_MODE_NIGHT)) != 0) { + return 8; + } + if ((screenLayout & (MASK_SCREENSIZE | MASK_SCREENLONG)) != 0 + || density != DENSITY_DEFAULT) { + return 4; + } + return 0; + } + + @Override + public String toString() { + return ! getQualifiers().equals("") ? getQualifiers() : "[DEFAULT]"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ResConfigFlags other = (ResConfigFlags) obj; + return this.mQualifiers.equals(other.mQualifiers); + } + + @Override + public int hashCode() { + int hash = 17; + hash = 31 * hash + this.mQualifiers.hashCode(); + return hash; + } + + + // TODO: Dirty static hack. This counter should be a part of ResPackage, + // but it would be hard right now and this feature is very rarely used. + private static int sErrCounter = 0; + + + public final static byte ORIENTATION_ANY = 0; + public final static byte ORIENTATION_PORT = 1; + public final static byte ORIENTATION_LAND = 2; + public final static byte ORIENTATION_SQUARE = 3; + + public final static byte TOUCHSCREEN_ANY = 0; + public final static byte TOUCHSCREEN_NOTOUCH = 1; + public final static byte TOUCHSCREEN_STYLUS = 2; + public final static byte TOUCHSCREEN_FINGER = 3; + + public final static short DENSITY_DEFAULT = 0; + public final static short DENSITY_LOW = 120; + public final static short DENSITY_MEDIUM = 160; + public final static short DENSITY_TV = 213; + public final static short DENSITY_HIGH = 240; + public final static short DENSITY_XHIGH = 320; + public final static short DENSITY_XXHIGH = 480; + public final static short DENSITY_NONE = -1; + + public final static byte KEYBOARD_ANY = 0; + public final static byte KEYBOARD_NOKEYS = 1; + public final static byte KEYBOARD_QWERTY = 2; + public final static byte KEYBOARD_12KEY = 3; + + public final static byte NAVIGATION_ANY = 0; + public final static byte NAVIGATION_NONAV = 1; + public final static byte NAVIGATION_DPAD = 2; + public final static byte NAVIGATION_TRACKBALL = 3; + public final static byte NAVIGATION_WHEEL = 4; + + public final static byte MASK_KEYSHIDDEN = 0x3; + public final static byte KEYSHIDDEN_ANY = 0x0; + public final static byte KEYSHIDDEN_NO = 0x1; + public final static byte KEYSHIDDEN_YES = 0x2; + public final static byte KEYSHIDDEN_SOFT = 0x3; + + public final static byte MASK_NAVHIDDEN = 0xc; + public final static byte NAVHIDDEN_ANY = 0x0; + public final static byte NAVHIDDEN_NO = 0x4; + public final static byte NAVHIDDEN_YES = 0x8; + + public final static byte MASK_SCREENSIZE = 0x0f; + public final static byte SCREENSIZE_ANY = 0x00; + public final static byte SCREENSIZE_SMALL = 0x01; + public final static byte SCREENSIZE_NORMAL = 0x02; + public final static byte SCREENSIZE_LARGE = 0x03; + public final static byte SCREENSIZE_XLARGE = 0x04; + + public final static byte MASK_SCREENLONG = 0x30; + public final static byte SCREENLONG_ANY = 0x00; + public final static byte SCREENLONG_NO = 0x10; + public final static byte SCREENLONG_YES = 0x20; + + public final static byte MASK_UI_MODE_TYPE = 0x0f; + public final static byte UI_MODE_TYPE_ANY = 0x00; + public final static byte UI_MODE_TYPE_NORMAL = 0x01; + public final static byte UI_MODE_TYPE_DESK = 0x02; + public final static byte UI_MODE_TYPE_CAR = 0x03; + public final static byte UI_MODE_TYPE_TELEVISION = 0x04; + public final static byte UI_MODE_TYPE_APPLIANCE = 0x05; + public final static byte UI_MODE_TYPE_SMALLUI = 0x0c; + public final static byte UI_MODE_TYPE_MEDIUMUI = 0x0d; + public final static byte UI_MODE_TYPE_LARGEUI = 0x0e; + public final static byte UI_MODE_TYPE_HUGEUI = 0x0f; + + public final static byte MASK_UI_MODE_NIGHT = 0x30; + public final static byte UI_MODE_NIGHT_ANY = 0x00; + public final static byte UI_MODE_NIGHT_NO = 0x10; + public final static byte UI_MODE_NIGHT_YES = 0x20; + + + private static final Logger LOGGER = + Logger.getLogger(ResConfigFlags.class.getName()); +} \ No newline at end of file diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResID.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResID.java new file mode 100644 index 0000000..2415803 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResID.java @@ -0,0 +1,70 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data; + +/** + * @author Ryszard Wiśniewski + */ +public class ResID { + public final int package_; + public final int type; + public final int entry; + + public final int id; + + public ResID(int package_, int type, int entry) { + this(package_, type, entry, (package_ << 24) + (type << 16) + entry); + } + + public ResID(int id) { + this(id >> 24, (id >> 16) & 0x000000ff, id & 0x0000ffff, id); + } + + public ResID(int package_, int type, int entry, int id) { + this.package_ = package_; + this.type = type; + this.entry = entry; + this.id = id; + } + + @Override + public String toString() { + return String.format("0x%08x", id); + } + + @Override + public int hashCode() { + int hash = 17; + hash = 31 * hash + this.id; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ResID other = (ResID) obj; + if (this.id != other.id) { + return false; + } + return true; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResPackage.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResPackage.java new file mode 100644 index 0000000..2fee2bc --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResPackage.java @@ -0,0 +1,220 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data; + +import brut.androlib.AndrolibException; +import brut.androlib.err.UndefinedResObject; +import brut.androlib.res.data.value.ResFileValue; +import brut.androlib.res.data.value.ResValueFactory; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import brut.util.Duo; +import java.util.*; + +/** + * @author Ryszard Wiśniewski + */ +public class ResPackage { + private final ResTable mResTable; + private final int mId; + private final String mName; + private final Map mResSpecs = + new LinkedHashMap(); + private final Map mConfigs = + new LinkedHashMap(); + private final Map mTypes = + new LinkedHashMap(); + private final Set mSynthesizedRes = new HashSet(); + + private ResValueFactory mValueFactory; + + public ResPackage(ResTable resTable, int id, String name) { + this.mResTable = resTable; + this.mId = id; + this.mName = name; + } + + public List listResSpecs() { + return new ArrayList(mResSpecs.values()); + } + + public boolean hasResSpec(ResID resID) { + return mResSpecs.containsKey(resID); + } + + public ResResSpec getResSpec(ResID resID) throws UndefinedResObject { + ResResSpec spec = mResSpecs.get(resID); + if (spec == null) { + throw new UndefinedResObject("resource spec: " + resID.toString()); + } + return spec; + } + + public List getConfigs() { + return new ArrayList(mConfigs.values()); + } + + public boolean hasConfig(ResConfigFlags flags) { + return mConfigs.containsKey(flags); + } + + public ResConfig getConfig(ResConfigFlags flags) throws AndrolibException { + ResConfig config = mConfigs.get(flags); + if (config == null) { + throw new UndefinedResObject("config: " + flags); + } + return config; + } + + public ResConfig getOrCreateConfig(ResConfigFlags flags) + throws AndrolibException { + ResConfig config = mConfigs.get(flags); + if (config == null) { + config = new ResConfig(flags); + mConfigs.put(flags, config); + } + return config; + } + + public List listTypes() { + return new ArrayList(mTypes.values()); + } + + public boolean hasType(String typeName) { + return mTypes.containsKey(typeName); + } + + public ResType getType(String typeName) throws AndrolibException { + ResType type = mTypes.get(typeName); + if (type == null) { + throw new UndefinedResObject("type: " + typeName); + } + return type; + } + + public Set listFiles() { + Set ret = new HashSet(); + for (ResResSpec spec : mResSpecs.values()) { + for (ResResource res : spec.listResources()) { + if (res.getValue() instanceof ResFileValue) { + ret.add(res); + } + } + } + return ret; + } + + public Collection listValuesFiles() { + Map, ResValuesFile> ret = + new HashMap, ResValuesFile>(); + for (ResResSpec spec : mResSpecs.values()) { + for (ResResource res : spec.listResources()) { + if (res.getValue() instanceof ResValuesXmlSerializable) { + ResType type = res.getResSpec().getType(); + ResConfig config = res.getConfig(); + Duo key = + new Duo(type, config); + ResValuesFile values = ret.get(key); + if (values == null) { + values = new ResValuesFile(this, type, config); + ret.put(key, values); + } + values.addResource(res); + } + } + } + return ret.values(); + } + + public ResTable getResTable() { + return mResTable; + } + + public int getId() { + return mId; + } + + public String getName() { + return mName; + } + + boolean isSynthesized(ResID resId) { + return mSynthesizedRes.contains(resId); + } + + public void addResSpec(ResResSpec spec) throws AndrolibException { + if (mResSpecs.put(spec.getId(), spec) != null) { + throw new AndrolibException("Multiple resource specs: " + spec); + } + } + + public void addConfig(ResConfig config) throws AndrolibException { + if (mConfigs.put(config.getFlags(), config) != null) { + throw new AndrolibException("Multiple configs: " + config); + } + } + + public void addType(ResType type) throws AndrolibException { + if (mTypes.put(type.getName(), type) != null) { + throw new AndrolibException("Multiple types: " + type); + } + } + + public void addResource(ResResource res) { + } + + public void addSynthesizedRes(int resId) { + mSynthesizedRes.add(new ResID(resId)); + } + + @Override + public String toString() { + return mName; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ResPackage other = (ResPackage) obj; + if (this.mResTable != other.mResTable && (this.mResTable == null || !this.mResTable.equals(other.mResTable))) { + return false; + } + if (this.mId != other.mId) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 17; + hash = 31 * hash + (this.mResTable != null ? this.mResTable.hashCode() : 0); + hash = 31 * hash + this.mId; + return hash; + } + + public ResValueFactory getValueFactory() { + if (mValueFactory == null) { + mValueFactory = new ResValueFactory(this); + } + return mValueFactory; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResSpec.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResSpec.java new file mode 100755 index 0000000..f39f561 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResSpec.java @@ -0,0 +1,125 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data; + +import brut.androlib.AndrolibException; +import brut.androlib.err.UndefinedResObject; +import java.util.*; +import org.apache.commons.lang3.StringUtils; + +/** + * @author Ryszard Wiśniewski + */ +public class ResResSpec { + private final ResID mId; + private final String mName; + private final ResPackage mPackage; + private final ResType mType; + private final Map mResources = + new LinkedHashMap(); + + public ResResSpec(ResID id, String name, ResPackage pkg, ResType type) { + this.mId = id; + this.mName = name; + this.mPackage = pkg; + this.mType = type; + } + + public Set listResources() { + return new LinkedHashSet(mResources.values()); + } + + public ResResource getResource(ResConfig config) throws AndrolibException { + return getResource(config.getFlags()); + } + + public ResResource getResource(ResConfigFlags config) + throws AndrolibException { + ResResource res = mResources.get(config); + if (res == null) { + throw new UndefinedResObject(String.format( + "resource: spec=%s, config=%s", this, config)); + } + return res; + } + + public boolean hasResource(ResConfig config) { + return hasResource(config.getFlags()); + } + + private boolean hasResource(ResConfigFlags flags) { + return mResources.containsKey(flags); + } + + public ResResource getDefaultResource() throws AndrolibException { + return getResource(new ResConfigFlags()); + } + + public boolean hasDefaultResource() { + return mResources.containsKey(new ResConfigFlags()); + } + + public String getFullName() { + return getFullName(false, false); + } + + public String getFullName(ResPackage relativeToPackage, + boolean excludeType) { + return getFullName( + getPackage().equals(relativeToPackage), excludeType); + } + + public String getFullName(boolean excludePackage, boolean excludeType) { + return + (excludePackage ? "" : getPackage().getName() + ":") + + (excludeType ? "" : getType().getName() + "/") + getName(); + } + + public ResID getId() { + return mId; + } + + public String getName() { + return StringUtils.replace(mName, "\"", "q"); + } + + public ResPackage getPackage() { + return mPackage; + } + + public ResType getType() { + return mType; + } + + public void addResource(ResResource res) + throws AndrolibException { + addResource(res, false); + } + + public void addResource(ResResource res, boolean overwrite) + throws AndrolibException { + ResConfigFlags flags = res.getConfig().getFlags(); + if (mResources.put(flags, res) != null && ! overwrite) { + throw new AndrolibException(String.format("Multiple resources: spec=%s, config=%s", this, flags)); + } + } + + @Override + public String toString() { + return mId.toString() + " " + mType.toString() + "/" + mName; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResource.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResource.java new file mode 100644 index 0000000..2351b3c --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResource.java @@ -0,0 +1,64 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.value.ResValue; + +/** + * @author Ryszard Wiśniewski + */ +public class ResResource { + private final ResConfig mConfig; + private final ResResSpec mResSpec; + private final ResValue mValue; + + public ResResource(ResConfig config, ResResSpec spec, + ResValue value) { + this.mConfig = config; + this.mResSpec = spec; + this.mValue = value; + } + + public String getFilePath() { + return mResSpec.getType().getName() + + mConfig.getFlags().getQualifiers() + "/" + mResSpec.getName(); + } + + public ResConfig getConfig() { + return mConfig; + } + + public ResResSpec getResSpec() { + return mResSpec; + } + + public ResValue getValue() { + return mValue; + } + + public void replace(ResValue value) throws AndrolibException { + ResResource res = new ResResource(mConfig, mResSpec, value); + mConfig.addResource(res, true); + mResSpec.addResource(res, true); + } + + @Override + public String toString() { + return getFilePath(); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java new file mode 100644 index 0000000..5894928 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java @@ -0,0 +1,137 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data; + +import brut.androlib.AndrolibException; +import brut.androlib.err.UndefinedResObject; +import brut.androlib.res.AndrolibResources; +import brut.androlib.res.data.value.ResValue; +import java.util.*; + +/** + * @author Ryszard Wiśniewski + */ +public class ResTable { + private final AndrolibResources mAndRes; + + private final Map mPackagesById = + new HashMap(); + private final Map mPackagesByName = + new HashMap(); + private final Set mMainPackages = + new LinkedHashSet(); + private final Set mFramePackages = + new LinkedHashSet(); + + private String mFrameTag; + + private Map mSdkInfo = new LinkedHashMap(); + + public ResTable() { + mAndRes = null; + } + + public ResTable(AndrolibResources andRes) { + mAndRes = andRes; + } + + public ResResSpec getResSpec(int resID) throws AndrolibException { + return getResSpec(new ResID(resID)); + } + + public ResResSpec getResSpec(ResID resID) throws AndrolibException { + return getPackage(resID.package_).getResSpec(resID); + } + + public Set listMainPackages() { + return mMainPackages; + } + + public Set listFramePackages() { + return mFramePackages; + } + + public ResPackage getPackage(int id) throws AndrolibException { + ResPackage pkg = mPackagesById.get(id); + if (pkg != null) { + return pkg; + } + if (mAndRes != null) { + return mAndRes.loadFrameworkPkg(this, id, mFrameTag); + } + throw new UndefinedResObject(String.format("package: id=%d", id)); + } + + public ResPackage getPackage(String name) throws AndrolibException { + ResPackage pkg = mPackagesByName.get(name); + if (pkg == null) { + throw new UndefinedResObject("package: name=" + name); + } + return pkg; + } + + public boolean hasPackage(int id) { + return mPackagesById.containsKey(id); + } + + public boolean hasPackage(String name) { + return mPackagesByName.containsKey(name); + } + + public ResValue getValue(String package_, String type, String name) + throws AndrolibException { + return getPackage(package_).getType(type).getResSpec(name) + .getDefaultResource().getValue(); + } + + public void addPackage(ResPackage pkg, boolean main) + throws AndrolibException { + Integer id = pkg.getId(); + if (mPackagesById.containsKey(id)) { + throw new AndrolibException( + "Multiple packages: id=" + id.toString()); + } + String name = pkg.getName(); + if (mPackagesByName.containsKey(name)) { + throw new AndrolibException("Multiple packages: name=" + name); + } + + mPackagesById.put(id, pkg); + mPackagesByName.put(name, pkg); + if (main) { + mMainPackages.add(pkg); + } else { + mFramePackages.add(pkg); + } + } + + public void setFrameTag(String tag) { + mFrameTag = tag; + } + + public Map getSdkInfo() { + return mSdkInfo; + } + + public void addSdkInfo(String key, String value) { + mSdkInfo.put(key, value); + } + + public void clearSdkInfo() { + mSdkInfo.clear(); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResType.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResType.java new file mode 100644 index 0000000..c73b7f8 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResType.java @@ -0,0 +1,70 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data; + +import brut.androlib.AndrolibException; +import brut.androlib.err.UndefinedResObject; +import java.util.*; + +/** + * @author Ryszard Wiśniewski + */ +public final class ResType { + private final String mName; + private final Map mResSpecs = + new LinkedHashMap(); + + private final ResTable mResTable; + private final ResPackage mPackage; + + public ResType(String name, ResTable resTable, + ResPackage package_) { + this.mName = name; + this.mResTable = resTable; + this.mPackage = package_; + } + + public String getName() { + return mName; + } + + public Set listResSpecs() { + return new LinkedHashSet(mResSpecs.values()); + } + + public ResResSpec getResSpec(String name) throws AndrolibException { + ResResSpec spec = mResSpecs.get(name); + if (spec == null) { + throw new UndefinedResObject(String.format( + "resource spec: %s/%s", getName(), name)); + } + return spec; + } + + public void addResSpec(ResResSpec spec) + throws AndrolibException { + if (mResSpecs.put(spec.getName(), spec) != null) { + throw new AndrolibException(String.format( + "Multiple res specs: %s/%s", getName(), spec.getName())); + } + } + + @Override + public String toString() { + return mName; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResValuesFile.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResValuesFile.java new file mode 100644 index 0000000..0b7946a --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResValuesFile.java @@ -0,0 +1,90 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @author Ryszard Wiśniewski + */ +public class ResValuesFile { + private final ResPackage mPackage; + private final ResType mType; + private final ResConfig mConfig; + private final Set mResources = + new LinkedHashSet(); + + public ResValuesFile(ResPackage pkg, ResType type, ResConfig config) { + this.mPackage = pkg; + this.mType = type; + this.mConfig = config; + } + + public String getPath() { + return "values" + mConfig.getFlags().getQualifiers() + + "/" + mType.getName() + + (mType.getName().endsWith("s") ? "" : "s") + + ".xml"; + } + + public Set listResources() { + return mResources; + } + + public ResType getType() { + return mType; + } + + public ResConfig getConfig() { + return mConfig; + } + + public boolean isSynthesized(ResResource res) { + return mPackage.isSynthesized(res.getResSpec().getId()); + } + + public void addResource(ResResource res) { + mResources.add(res); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ResValuesFile other = (ResValuesFile) obj; + if (this.mType != other.mType && (this.mType == null || !this.mType.equals(other.mType))) { + return false; + } + if (this.mConfig != other.mConfig && (this.mConfig == null || !this.mConfig.equals(other.mConfig))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 17; + hash = 31 * hash + (this.mType != null ? this.mType.hashCode() : 0); + hash = 31 * hash + (this.mConfig != null ? this.mConfig.hashCode() : 0); + return hash; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResArrayValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResArrayValue.java new file mode 100644 index 0000000..595e286 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResArrayValue.java @@ -0,0 +1,87 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import brut.util.Duo; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; +import org.apache.commons.lang3.StringUtils; + +/** + * @author Ryszard Wiśniewski + */ +public class ResArrayValue extends ResBagValue implements ResValuesXmlSerializable { + private String mRawItems; + ResArrayValue(ResReferenceValue parent, + Duo[] items) { + super(parent); + + mItems = new ResScalarValue[items.length]; + for (int i = 0; i < items.length; i++) { + mItems[i] = items[i].m2; + } + } + + public ResArrayValue(ResReferenceValue parent, ResScalarValue[] items) { + super(parent); + mItems = items; + } + + @Override + public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + String type = getType(); + type = (type == null ? "" : type + "-") + "array"; + + serializer.startTag(null, type); + serializer.attribute(null, "name", res.getResSpec().getName()); + for (int i = 0; i < mItems.length; i++) { + serializer.startTag(null, "item"); + serializer.text(mItems[i].encodeAsResXmlItemValue()); + serializer.endTag(null, "item"); + } + serializer.endTag(null, type); + } + + public String getType() throws AndrolibException { + if (mItems.length == 0) { + return null; + } + String type = mItems[0].getType(); + for (int i = 1; i < mItems.length; i++) { + + if (mItems[i].encodeAsResXmlItemValue().startsWith("@string")) { + return "string"; + } else if (mItems[i].encodeAsResXmlItemValue().startsWith("@drawable")) { + return null; + } else if (!"string".equals(type) && !"integer".equals(type)) { + return null; + } else if (!type.equals(mItems[i].getType())) { + return null; + } + } + return type; + } + + private final ResScalarValue[] mItems; + + + public static final int BAG_KEY_ARRAY_START = 0x02000000; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResAttr.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResAttr.java new file mode 100644 index 0000000..24282d3 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResAttr.java @@ -0,0 +1,175 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResPackage; +import brut.androlib.res.data.ResResource; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import brut.util.Duo; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResAttr extends ResBagValue implements ResValuesXmlSerializable { + ResAttr(ResReferenceValue parentVal, int type, Integer min, Integer max, + Boolean l10n) { + super(parentVal); + mType = type; + mMin = min; + mMax = max; + mL10n = l10n; + } + + public String convertToResXmlFormat(ResScalarValue value) + throws AndrolibException { + return null; + } + + @Override + public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + String type = getTypeAsString(); + + serializer.startTag(null, "attr"); + serializer.attribute(null, "name", res.getResSpec().getName()); + if (type != null) { + serializer.attribute(null, "format", type); + } + if (mMin != null) { + serializer.attribute(null, "min", mMin.toString()); + } + if (mMax != null) { + serializer.attribute(null, "max", mMax.toString()); + } + if (mL10n != null && mL10n) { + serializer.attribute(null, "localization", "suggested"); + } + serializeBody(serializer, res); + serializer.endTag(null, "attr"); + } + + + public static ResAttr factory(ResReferenceValue parent, + Duo[] items, ResValueFactory factory, + ResPackage pkg) throws AndrolibException { + + int type = ((ResIntValue) items[0].m2).getValue(); + int scalarType = type & 0xffff; + Integer min = null, max = null; + Boolean l10n = null; + int i; + for (i = 1; i < items.length; i++) { + switch (items[i].m1) { + case BAG_KEY_ATTR_MIN: + min = ((ResIntValue) items[i].m2).getValue(); + continue; + case BAG_KEY_ATTR_MAX: + max = ((ResIntValue) items[i].m2).getValue(); + continue; + case BAG_KEY_ATTR_L10N: + l10n = ((ResIntValue) items[i].m2).getValue() != 0; + continue; + } + break; + } + + if (i == items.length) { + return new ResAttr(parent, scalarType, min, max, l10n); + } + Duo[] attrItems = + new Duo[items.length - i]; + int j = 0; + for (; i < items.length; i++) { + int resId = items[i].m1; + pkg.addSynthesizedRes(resId); + attrItems[j++] = new Duo( + factory.newReference(resId, null), (ResIntValue) items[i].m2); + } + switch (type & 0xff0000) { + case TYPE_ENUM: + return new ResEnumAttr( + parent, scalarType, min, max, l10n, attrItems); + case TYPE_FLAGS: + return new ResFlagsAttr( + parent, scalarType, min, max, l10n, attrItems); + } + + throw new AndrolibException("Could not decode attr value"); + } + + protected void serializeBody(XmlSerializer serializer, ResResource res) + throws AndrolibException, IOException {} + + protected String getTypeAsString() { + String s = ""; + if ((mType & TYPE_REFERENCE) != 0) { + s += "|reference"; + } + if ((mType & TYPE_STRING) != 0) { + s += "|string"; + } + if ((mType & TYPE_INT) != 0) { + s += "|integer"; + } + if ((mType & TYPE_BOOL) != 0) { + s += "|boolean"; + } + if ((mType & TYPE_COLOR) != 0) { + s += "|color"; + } + if ((mType & TYPE_FLOAT) != 0) { + s += "|float"; + } + if ((mType & TYPE_DIMEN) != 0) { + s += "|dimension"; + } + if ((mType & TYPE_FRACTION) != 0) { + s += "|fraction"; + } + if (s.isEmpty()) { + return null; + } + return s.substring(1); + } + + private final int mType; + private final Integer mMin; + private final Integer mMax; + private final Boolean mL10n; + + + public static final int BAG_KEY_ATTR_TYPE = 0x01000000; + private static final int BAG_KEY_ATTR_MIN = 0x01000001; + private static final int BAG_KEY_ATTR_MAX = 0x01000002; + private static final int BAG_KEY_ATTR_L10N = 0x01000003; + + private final static int TYPE_REFERENCE = 0x01; + private final static int TYPE_STRING = 0x02; + private final static int TYPE_INT = 0x04; + private final static int TYPE_BOOL = 0x08; + private final static int TYPE_COLOR = 0x10; + private final static int TYPE_FLOAT = 0x20; + private final static int TYPE_DIMEN = 0x40; + private final static int TYPE_FRACTION = 0x80; + private final static int TYPE_ANY_STRING = 0xee; + + private static final int TYPE_ENUM = 0x00010000; + private static final int TYPE_FLAGS = 0x00020000; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResBagValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResBagValue.java new file mode 100644 index 0000000..f8bdeed --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResBagValue.java @@ -0,0 +1,64 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import brut.util.Duo; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResBagValue extends ResValue implements ResValuesXmlSerializable { + protected final ResReferenceValue mParent; + + public ResBagValue(ResReferenceValue parent) { + this.mParent = parent; + } + + public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + String type = res.getResSpec().getType().getName(); + if ("style".equals(type)) { + new ResStyleValue(mParent, new Duo[0], null) + .serializeToResValuesXml(serializer, res); + return; + } + if ("array".equals(type)) { + new ResArrayValue(mParent, new Duo[0]) + .serializeToResValuesXml(serializer, res); + return; + } + if ("plurals".equals(type)) { + new ResPluralsValue(mParent, new Duo[0]) + .serializeToResValuesXml(serializer, res); + return; + } + + serializer.startTag(null, "item"); + serializer.attribute(null, "type", type); + serializer.attribute(null, "name", res.getResSpec().getName()); + serializer.endTag(null, "item"); + } + + public ResReferenceValue getParent() { + return mParent; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResBoolValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResBoolValue.java new file mode 100644 index 0000000..807196f --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResBoolValue.java @@ -0,0 +1,37 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +/** + * @author Ryszard Wiśniewski + */ +public class ResBoolValue extends ResScalarValue { + private final boolean mValue; + + public ResBoolValue(boolean value, String rawValue) { + super("bool", rawValue); + this.mValue = value; + } + + public boolean getValue() { + return mValue; + } + + protected String encodeAsResXml() { + return mValue ? "true" : "false"; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResColorValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResColorValue.java new file mode 100644 index 0000000..224186f --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResColorValue.java @@ -0,0 +1,31 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +/** + * @author Ryszard Wiśniewski + */ +public class ResColorValue extends ResIntValue { + public ResColorValue(int value, String rawValue) { + super(value, rawValue, "color"); + } + + @Override + protected String encodeAsResXml() { + return String.format("#%08x", mValue); + } +} \ No newline at end of file diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResDimenValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResDimenValue.java new file mode 100644 index 0000000..d00a607 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResDimenValue.java @@ -0,0 +1,34 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import android.util.TypedValue; +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class ResDimenValue extends ResIntValue { + public ResDimenValue(int value, String rawValue) { + super(value, rawValue, "dimen"); + } + + @Override + protected String encodeAsResXml() throws AndrolibException { + return TypedValue.coerceToString(TypedValue.TYPE_DIMENSION, mValue); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResEnumAttr.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResEnumAttr.java new file mode 100644 index 0000000..bf550d3 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResEnumAttr.java @@ -0,0 +1,84 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import brut.util.Duo; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResEnumAttr extends ResAttr { + ResEnumAttr(ResReferenceValue parent, int type, Integer min, Integer max, + Boolean l10n, Duo[] items) { + super(parent, type, min, max, l10n); + mItems = items; + } + + @Override + public String convertToResXmlFormat(ResScalarValue value) + throws AndrolibException { + if (value instanceof ResIntValue) { + String ret = decodeValue(((ResIntValue) value).getValue()); + if (ret != null) { + return ret; + } + } + return super.convertToResXmlFormat(value); + } + + @Override + protected void serializeBody(XmlSerializer serializer, ResResource res) + throws AndrolibException, IOException { + for (Duo duo : mItems) { + int intVal = duo.m2.getValue(); + + serializer.startTag(null, "enum"); + serializer.attribute(null, "name", duo.m1.getReferent().getName()); + serializer.attribute(null, "value", String.valueOf(intVal)); + serializer.endTag(null, "enum"); + } + } + + private String decodeValue(int value) throws AndrolibException { + String value2 = mItemsCache.get(value); + if (value2 == null) { + ResReferenceValue ref = null; + for (Duo duo : mItems) { + if (duo.m2.getValue() == value) { + ref = duo.m1; + break; + } + } + if (ref != null) { + value2 = ref.getReferent().getName(); + mItemsCache.put(value, value2); + } + } + return value2; + } + + + private final Duo[] mItems; + private final Map mItemsCache = + new HashMap(); +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFileValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFileValue.java new file mode 100644 index 0000000..e6ddff6 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFileValue.java @@ -0,0 +1,42 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class ResFileValue extends ResValue { + private final String mPath; + + public ResFileValue(String path) { + this.mPath = path; + } + + public String getPath() { + return mPath; + } + + public String getStrippedPath() throws AndrolibException { + if (! mPath.startsWith("res/")) { + throw new AndrolibException( + "File path does not start with \"res/\": " + mPath); + } + return mPath.substring(4); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFlagsAttr.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFlagsAttr.java new file mode 100644 index 0000000..7761562 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFlagsAttr.java @@ -0,0 +1,160 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import brut.util.Duo; +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResFlagsAttr extends ResAttr { + ResFlagsAttr(ResReferenceValue parent, int type, Integer min, Integer max, Boolean l10n, Duo[] items) { + super(parent, type, min, max, l10n); + + mItems = new FlagItem[items.length]; + for (int i = 0; i < items.length; i++) { + mItems[i] = new FlagItem(items[i].m1, items[i].m2.getValue()); + } + } + + @Override + public String convertToResXmlFormat(ResScalarValue value) + throws AndrolibException { + if (! (value instanceof ResIntValue)) { + return super.convertToResXmlFormat(value); + } + loadFlags(); + int intVal = ((ResIntValue) value).getValue(); + + if (intVal == 0) { + return renderFlags(mZeroFlags); + } + + FlagItem[] flagItems = new FlagItem[mFlags.length]; + int[] flags = new int[mFlags.length]; + int flagsCount = 0; + for (int i = 0; i < mFlags.length; i++) { + FlagItem flagItem = mFlags[i]; + int flag = flagItem.flag; + + if ((intVal & flag) != flag) { + continue; + } + + if (! isSubpartOf(flag, flags)) { + flags[flagsCount] = flag; + flagItems[flagsCount++] = flagItem; + } + } + return renderFlags(Arrays.copyOf(flagItems, flagsCount)); + } + + @Override + protected void serializeBody(XmlSerializer serializer, ResResource res) + throws AndrolibException, IOException { + for (int i = 0; i < mItems.length; i++) { + FlagItem item = mItems[i]; + + serializer.startTag(null, "flag"); + serializer.attribute(null, "name", item.getValue()); + serializer.attribute(null, "value", + String.format("0x%08x", item.flag)); + serializer.endTag(null, "flag"); + } + } + + private boolean isSubpartOf(int flag, int[] flags) { + for (int i = 0; i < flags.length; i++) { + if ((flags[i] & flag) == flag) { + return true; + } + } + return false; + } + + private String renderFlags(FlagItem[] flags) throws AndrolibException { + String ret = ""; + for (int i = 0; i < flags.length; i++) { + ret += "|" + flags[i].getValue(); + } + if (ret.isEmpty()) { + return ret; + } + return ret.substring(1); + } + + private void loadFlags() { + if (mFlags != null) { + return; + } + + FlagItem[] zeroFlags = new FlagItem[mItems.length]; + int zeroFlagsCount = 0; + FlagItem[] flags = new FlagItem[mItems.length]; + int flagsCount = 0; + + for (int i = 0; i < mItems.length; i++) { + FlagItem item = mItems[i]; + if (item.flag == 0) { + zeroFlags[zeroFlagsCount++] = item; + } else { + flags[flagsCount++] = item; + } + } + + mZeroFlags = Arrays.copyOf(zeroFlags, zeroFlagsCount); + mFlags = Arrays.copyOf(flags, flagsCount); + + Arrays.sort(mFlags, new Comparator() { + public int compare(FlagItem o1, FlagItem o2) { + return Integer.valueOf(Integer.bitCount(o2.flag)) + .compareTo(Integer.bitCount(o1.flag)); + } + }); + } + + + private final FlagItem[] mItems; + + private FlagItem[] mZeroFlags; + private FlagItem[] mFlags; + + + private static class FlagItem { + public final ResReferenceValue ref; + public final int flag; + public String value; + + public FlagItem(ResReferenceValue ref, int flag) { + this.ref = ref; + this.flag = flag; + } + + public String getValue() throws AndrolibException { + if (value == null) { + value = ref.getReferent().getName(); + } + return value; + } + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFloatValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFloatValue.java new file mode 100644 index 0000000..bbd7721 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFloatValue.java @@ -0,0 +1,37 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +/** + * @author Ryszard Wiśniewski + */ +public class ResFloatValue extends ResScalarValue { + private final float mValue; + + public ResFloatValue(float value, String rawValue) { + super("float", rawValue); + this.mValue = value; + } + + public float getValue() { + return mValue; + } + + protected String encodeAsResXml() { + return String.valueOf(mValue); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFractionValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFractionValue.java new file mode 100644 index 0000000..c332583 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResFractionValue.java @@ -0,0 +1,34 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import android.util.TypedValue; +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class ResFractionValue extends ResIntValue { + public ResFractionValue(int value, String rawValue) { + super(value, rawValue, "fraction"); + } + + @Override + protected String encodeAsResXml() throws AndrolibException { + return TypedValue.coerceToString(TypedValue.TYPE_FRACTION, mValue); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResIdValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResIdValue.java new file mode 100644 index 0000000..cb08ae8 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResIdValue.java @@ -0,0 +1,35 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResIdValue extends ResValue implements ResValuesXmlSerializable { + public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) throws IOException, AndrolibException { + serializer.startTag(null, "item"); + serializer.attribute(null, "type", res.getResSpec().getType().getName()); + serializer.attribute(null, "name", res.getResSpec().getName()); + serializer.endTag(null, "item"); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResIntValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResIntValue.java new file mode 100644 index 0000000..d113999 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResIntValue.java @@ -0,0 +1,46 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import android.util.TypedValue; +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public class ResIntValue extends ResScalarValue { + protected final int mValue; + private int type; + + public ResIntValue(int value, String rawValue, int type) { + this(value, rawValue, "integer"); + this.type = type; + } + + public ResIntValue(int value, String rawValue, String type) { + super(type, rawValue); + this.mValue = value; + } + + public int getValue() { + return mValue; + } + + protected String encodeAsResXml() throws AndrolibException { + return TypedValue.coerceToString(type, mValue); + } +} \ No newline at end of file diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResPluralsValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResPluralsValue.java new file mode 100644 index 0000000..b590292 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResPluralsValue.java @@ -0,0 +1,81 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import brut.androlib.res.xml.ResXmlEncoders; +import brut.util.Duo; +import java.io.IOException; +import org.apache.commons.lang3.StringUtils; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResPluralsValue extends ResBagValue implements ResValuesXmlSerializable { + ResPluralsValue(ResReferenceValue parent, + Duo[] items) { + super(parent); + + mItems = new ResScalarValue[6]; + for (int i = 0; i < items.length; i++) { + mItems[items[i].m1 - BAG_KEY_PLURALS_START] = + (ResScalarValue) items[i].m2; + } + } + + @Override + public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + serializer.startTag(null, "plurals"); + serializer.attribute(null, "name", res.getResSpec().getName()); + for (int i = 0; i < mItems.length; i++) { + ResScalarValue item = mItems[i]; + if (item == null) { + continue; + } + + ResScalarValue rawValue = item; + + serializer.startTag(null, "item"); + serializer.attribute(null, "quantity", QUANTITY_MAP[i]); + if (ResXmlEncoders.hasMultipleNonPositionalSubstitutions(rawValue.encodeAsResXmlValue())) { + serializer.text(item.encodeAsResXmlValueExt()); + } else { + String recode = item.encodeAsResXmlValue(); + //Dirty, but working fix @miuirussia + for (int j = 0; j < 10; j++) { + recode = StringUtils.replace(recode, "%" + Integer.toString(j) + "$" + Integer.toString(j) + "$", "%" + Integer.toString(j) + "$"); + } + serializer.text(recode); + } + serializer.endTag(null, "item"); + } + serializer.endTag(null, "plurals"); + } + + + private final ResScalarValue[] mItems; + + + public static final int BAG_KEY_PLURALS_START = 0x01000004; + public static final int BAG_KEY_PLURALS_END = 0x01000009; + private static final String[] QUANTITY_MAP = + new String[] {"other", "zero", "one", "two", "few", "many"}; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResReferenceValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResReferenceValue.java new file mode 100644 index 0000000..d626e03 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResReferenceValue.java @@ -0,0 +1,68 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResPackage; +import brut.androlib.res.data.ResResSpec; + +/** + * @author Ryszard Wiśniewski + */ +public class ResReferenceValue extends ResIntValue { + private final ResPackage mPackage; + private final boolean mTheme; + + public ResReferenceValue(ResPackage package_, int value, String rawValue) { + this(package_, value, rawValue, false); + } + + public ResReferenceValue(ResPackage package_, int value, String rawValue, + boolean theme) { + super(value, rawValue, "reference"); + mPackage = package_; + mTheme = theme; + } + + protected String encodeAsResXml() throws AndrolibException { + if (isNull()) { + return "@null"; + } + + ResResSpec spec = getReferent(); + boolean newId = + spec.hasDefaultResource() && + spec.getDefaultResource().getValue() instanceof ResIdValue; + + /* generate the beginning to fix @android */ + String mStart = (mTheme ? '?' : '@') + (newId ? "+" : ""); + // mStart = mStart.replace("@android", "@*android"); + + /* now dump back */ + return mStart + + spec.getFullName(mPackage, + mTheme && spec.getType().getName().equals("attr")); + } + + public ResResSpec getReferent() throws AndrolibException { + return mPackage.getResTable().getResSpec(getValue()); + } + + public boolean isNull() { + return mValue == 0; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResScalarValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResScalarValue.java new file mode 100644 index 0000000..9569d88 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResScalarValue.java @@ -0,0 +1,132 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import brut.androlib.res.xml.ResXmlEncodable; +import brut.androlib.res.xml.ResXmlEncoders; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public abstract class ResScalarValue extends ResValue + implements ResXmlEncodable, ResValuesXmlSerializable { + protected final String mType; + protected final String mRawValue; + + protected ResScalarValue(String type, String rawValue) { + mType = type; + mRawValue = rawValue; + } + + public String encodeAsResXmlAttr() throws AndrolibException { + if (mRawValue != null) { + return mRawValue; + } + return encodeAsResXml().replace("@android:", "@*android:"); + } + + public String encodeAsResXmlItemValue() throws AndrolibException { + return encodeAsResXmlValue().replace("@android:", "@*android:"); + } + + public String encodeAsResXmlValue() throws AndrolibException { + if (mRawValue != null) { + return mRawValue; + } + return encodeAsResXmlValueExt().replace("@android:", "@*android:"); + } + + public String encodeAsResXmlValueExt() throws AndrolibException { + String rawValue = mRawValue; + if (rawValue != null) { + if (ResXmlEncoders.hasMultipleNonPositionalSubstitutions(rawValue)) { + int count = 1; + StringBuilder result = new StringBuilder(); + String tmp1[] = rawValue.split("%%", -1); + int tmp1_sz = tmp1.length; + for(int i=0;i + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import brut.androlib.res.xml.ResXmlEncoders; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResStringValue extends ResScalarValue { + + public ResStringValue(String value) { + this(value, "string"); + } + + public ResStringValue(String value, String type) { + super(type, value); + } + + @Override + public String encodeAsResXmlAttr() { + return ResXmlEncoders.encodeAsResXmlAttr(mRawValue); + } + + @Override + public String encodeAsResXmlItemValue() { + return ResXmlEncoders.enumerateNonPositionalSubstitutions( + ResXmlEncoders.encodeAsXmlValue(mRawValue)); + } + + @Override + public String encodeAsResXmlValue() { + return ResXmlEncoders.encodeAsXmlValue(mRawValue); + } + + @Override + protected String encodeAsResXml() throws AndrolibException { + throw new UnsupportedOperationException(); + } + + @Override + protected void serializeExtraXmlAttrs(XmlSerializer serializer, + ResResource res) throws IOException { + if (ResXmlEncoders.hasMultipleNonPositionalSubstitutions(mRawValue)) { + serializer.attribute(null, "formatted", "false"); + } + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResStyleValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResStyleValue.java new file mode 100644 index 0000000..99331f9 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResStyleValue.java @@ -0,0 +1,74 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResSpec; +import brut.androlib.res.data.ResResource; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import brut.util.Duo; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ResStyleValue extends ResBagValue implements ResValuesXmlSerializable { + ResStyleValue(ResReferenceValue parent, + Duo[] items, ResValueFactory factory) { + super(parent); + + mItems = new Duo[items.length]; + for (int i = 0; i < items.length; i++) { + mItems[i] = new Duo( + factory.newReference(items[i].m1, null), items[i].m2); + } + } + + @Override + public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException { + serializer.startTag(null, "style"); + serializer.attribute(null, "name", res.getResSpec().getName()); + if (! mParent.isNull()) { + serializer.attribute(null, "parent", mParent.encodeAsResXmlAttr()); + } + for (int i = 0; i < mItems.length; i++) { + ResResSpec spec = mItems[i].m1.getReferent(); + ResAttr attr = (ResAttr) spec.getDefaultResource().getValue(); + String value = attr.convertToResXmlFormat(mItems[i].m2); + + if (value == null) { + value = mItems[i].m2.encodeAsResXmlValue(); + } + + if (value == null) { + continue; + } + + serializer.startTag(null, "item"); + serializer.attribute(null, "name", + spec.getFullName(res.getResSpec().getPackage(), true)); + serializer.text(value); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "style"); + } + + + private final Duo[] mItems; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValue.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValue.java new file mode 100644 index 0000000..fd07436 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValue.java @@ -0,0 +1,24 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +/** + * @author Ryszard Wiśniewski + */ +public class ResValue { + +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java new file mode 100644 index 0000000..a123002 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java @@ -0,0 +1,101 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.data.value; + +import android.util.TypedValue; +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResPackage; +import brut.util.Duo; + +/** + * @author Ryszard Wiśniewski + */ +public class ResValueFactory { + private final ResPackage mPackage; + + public ResValueFactory(ResPackage pakage_) { + this.mPackage = pakage_; + } + + public ResScalarValue factory(int type, int value, String rawValue) + throws AndrolibException { + switch (type) { + case TypedValue.TYPE_REFERENCE: + return newReference(value, rawValue); + case TypedValue.TYPE_ATTRIBUTE: + return newReference(value, rawValue, true); + case TypedValue.TYPE_STRING: + return new ResStringValue(rawValue); + case TypedValue.TYPE_FLOAT: + return new ResFloatValue(Float.intBitsToFloat(value), rawValue); + case TypedValue.TYPE_DIMENSION: + return new ResDimenValue(value, rawValue); + case TypedValue.TYPE_FRACTION: + return new ResFractionValue(value, rawValue); + case TypedValue.TYPE_INT_BOOLEAN: + return new ResBoolValue(value != 0, rawValue); + } + + if (type >= TypedValue.TYPE_FIRST_COLOR_INT + && type <= TypedValue.TYPE_LAST_COLOR_INT) { + return new ResColorValue(value, rawValue); + } + if (type >= TypedValue.TYPE_FIRST_INT + && type <= TypedValue.TYPE_LAST_INT) { + return new ResIntValue(value, rawValue, type); + } + + throw new AndrolibException("Invalid value type: "+ type); + } + + public ResValue factory(String value) { + if (value.startsWith("res/")) { + return new ResFileValue(value); + } + return new ResStringValue(value); + } + + public ResBagValue bagFactory(int parent, + Duo[] items) throws AndrolibException { + ResReferenceValue parentVal = newReference(parent, null); + + if (items.length == 0) { + return new ResBagValue(parentVal); + } + int key = items[0].m1; + if (key == ResAttr.BAG_KEY_ATTR_TYPE) { + return ResAttr.factory(parentVal, items, this, mPackage); + } + if (key == ResArrayValue.BAG_KEY_ARRAY_START) { + return new ResArrayValue(parentVal, items); + } + if (key >= ResPluralsValue.BAG_KEY_PLURALS_START + && key <= ResPluralsValue.BAG_KEY_PLURALS_END) { + return new ResPluralsValue(parentVal, items); + } + return new ResStyleValue(parentVal, items, this); + } + + public ResReferenceValue newReference(int resID, String rawValue) { + return newReference(resID, rawValue, false); + } + + public ResReferenceValue newReference(int resID, String rawValue, + boolean theme) { + return new ResReferenceValue(mPackage, resID, rawValue, theme); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..cc5bf6d --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java @@ -0,0 +1,440 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import android.util.TypedValue; +import brut.androlib.AndrolibException; +import brut.androlib.res.data.*; +import brut.androlib.res.data.value.*; +import brut.util.Duo; +import brut.androlib.res.data.ResTable; +import brut.util.ExtDataInput; +import com.mindprod.ledatastream.LEDataInputStream; +import java.io.*; +import java.math.BigInteger; +import java.util.*; +import java.util.logging.Logger; +import org.apache.commons.io.input.CountingInputStream; + +/** + * @author Ryszard Wiśniewski + */ +public class ARSCDecoder { + public static ARSCData decode(InputStream arscStream, + boolean findFlagsOffsets, boolean keepBroken) + throws AndrolibException { + return decode(arscStream, findFlagsOffsets, keepBroken, new ResTable()); + } + + public static ARSCData decode(InputStream arscStream, + boolean findFlagsOffsets, boolean keepBroken, ResTable resTable) + throws AndrolibException { + try { + ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, + findFlagsOffsets, keepBroken); + ResPackage[] pkgs = decoder.readTable(); + return new ARSCData( + pkgs, + decoder.mFlagsOffsets == null ? null : + decoder.mFlagsOffsets.toArray(new FlagsOffset[0]), + resTable); + } catch (IOException ex) { + throw new AndrolibException("Could not decode arsc file", ex); + } + } + + private ARSCDecoder(InputStream arscStream, ResTable resTable, + boolean storeFlagsOffsets, boolean keepBroken) { + if (storeFlagsOffsets) { + arscStream = mCountIn = new CountingInputStream(arscStream); + mFlagsOffsets = new ArrayList(); + } else { + mCountIn = null; + mFlagsOffsets = null; + } + mIn = new ExtDataInput(new LEDataInputStream(arscStream)); + mResTable = resTable; + mKeepBroken = keepBroken; + } + + private ResPackage[] readTable() throws IOException, AndrolibException { + nextChunkCheckType(Header.TYPE_TABLE); + int packageCount = mIn.readInt(); + + mTableStrings = StringBlock.read(mIn); + ResPackage[] packages = new ResPackage[packageCount]; + + nextChunk(); + for (int i = 0; i < packageCount; i++) { + packages[i] = readPackage(); + } + + return packages; + } + + private ResPackage readPackage() throws IOException, AndrolibException { + checkChunkType(Header.TYPE_PACKAGE); + int id = (byte) mIn.readInt(); + String name = mIn.readNulEndedString(128, true); + /*typeNameStrings*/ mIn.skipInt(); + /*typeNameCount*/ mIn.skipInt(); + /*specNameStrings*/ mIn.skipInt(); + /*specNameCount*/ mIn.skipInt(); + + mTypeNames = StringBlock.read(mIn); + mSpecNames = StringBlock.read(mIn); + + mResId = id << 24; + mPkg = new ResPackage(mResTable, id, name); + + nextChunk(); + while (mHeader.type == Header.TYPE_TYPE) { + readType(); + } + + return mPkg; + } + + private ResType readType() throws AndrolibException, IOException { + checkChunkType(Header.TYPE_TYPE); + byte id = mIn.readByte(); + mIn.skipBytes(3); + int entryCount = mIn.readInt(); + + mMissingResSpecs = new boolean[entryCount]; + Arrays.fill(mMissingResSpecs, true); + + if (mFlagsOffsets != null) { + mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount)); + } + /*flags*/ mIn.skipBytes(entryCount * 4); + + mResId = (0xff000000 & mResId) | id << 16; + mType = new ResType(mTypeNames.getString(id - 1), mResTable, mPkg); + mPkg.addType(mType); + + while (nextChunk().type == Header.TYPE_CONFIG) { + readConfig(); + } + + addMissingResSpecs(); + + return mType; + } + + private ResConfig readConfig() throws IOException, AndrolibException { + checkChunkType(Header.TYPE_CONFIG); + /*typeId*/ mIn.skipInt(); + int entryCount = mIn.readInt(); + /*entriesStart*/ mIn.skipInt(); + + ResConfigFlags flags = readConfigFlags(); + int[] entryOffsets = mIn.readIntArray(entryCount); + + if (flags.isInvalid) { + String resName = mType.getName() + flags.getQualifiers(); + if (mKeepBroken) { + LOGGER.warning( + "Invalid config flags detected: " + resName); + } else { + LOGGER.warning( + "Invalid config flags detected. Dropping resources: " + resName); + } + } + + mConfig = flags.isInvalid && ! mKeepBroken ? + null : mPkg.getOrCreateConfig(flags); + + for (int i = 0; i < entryOffsets.length; i++) { + if (entryOffsets[i] != -1) { + mMissingResSpecs[i] = false; + mResId = (mResId & 0xffff0000) | i; + readEntry(); + } + } + + return mConfig; + } + + private void readEntry() throws IOException, AndrolibException { + /*size*/ mIn.skipBytes(2); + short flags = mIn.readShort(); + int specNamesId = mIn.readInt(); + + ResValue value = (flags & ENTRY_FLAG_COMPLEX) == 0 ? + readValue() : readComplexEntry(); + + if (mConfig == null) { + return; + } + + ResID resId = new ResID(mResId); + ResResSpec spec; + if (mPkg.hasResSpec(resId)) { + spec = mPkg.getResSpec(resId); + } else { + spec = new ResResSpec( + resId, mSpecNames.getString(specNamesId), mPkg, mType); + mPkg.addResSpec(spec); + mType.addResSpec(spec); + } + ResResource res = new ResResource(mConfig, spec, value); + + mConfig.addResource(res); + spec.addResource(res); + mPkg.addResource(res); + } + + private ResBagValue readComplexEntry() throws IOException, + AndrolibException { + int parent = mIn.readInt(); + int count = mIn.readInt(); + + ResValueFactory factory = mPkg.getValueFactory(); + Duo[] items = new Duo[count]; + for (int i = 0; i < count; i++) { + items[i] = new Duo( + mIn.readInt(), (ResScalarValue) readValue()); + } + + return factory.bagFactory(parent, items); + } + + private ResValue readValue() throws IOException, AndrolibException { + /*size*/ mIn.skipCheckShort((short) 8); + /*zero*/ mIn.skipCheckByte((byte) 0); + byte type = mIn.readByte(); + int data = mIn.readInt(); + + return type == TypedValue.TYPE_STRING ? + mPkg.getValueFactory().factory(mTableStrings.getHTML(data)) : + mPkg.getValueFactory().factory(type, data, null); + } + + private ResConfigFlags readConfigFlags() throws IOException, AndrolibException { + int size = mIn.readInt(); + if (size < 28) { + throw new AndrolibException("Config size < 28"); + } + + boolean isInvalid = false; + + short mcc = mIn.readShort(); + short mnc = mIn.readShort(); + + char[] language = new char[]{ + (char) mIn.readByte(), (char) mIn.readByte()}; + char[] country = new char[]{ + (char) mIn.readByte(), (char) mIn.readByte()}; + + byte orientation = mIn.readByte(); + byte touchscreen = mIn.readByte(); + short density = mIn.readShort(); + + byte keyboard = mIn.readByte(); + byte navigation = mIn.readByte(); + byte inputFlags = mIn.readByte(); + /*inputPad0*/ mIn.skipBytes(1); + + short screenWidth = mIn.readShort(); + short screenHeight = mIn.readShort(); + + short sdkVersion = mIn.readShort(); + /*minorVersion, now must always be 0*/ mIn.skipBytes(2); + + byte screenLayout = 0; + byte uiMode = 0; + short smallestScreenWidthDp = 0; + if (size >= 32) { + screenLayout = mIn.readByte(); + uiMode = mIn.readByte(); + smallestScreenWidthDp = mIn.readShort(); + } + + short screenWidthDp = 0; + short screenHeightDp = 0; + + if (size >= 36) { + screenWidthDp = mIn.readShort(); + screenHeightDp = mIn.readShort(); + } + + if (size >= 40) { + // mIn.skipBytes(2); + } + + int exceedingSize = size - KNOWN_CONFIG_BYTES; + if (exceedingSize > 0) { + byte[] buf = new byte[exceedingSize]; + mIn.readFully(buf); + BigInteger exceedingBI = new BigInteger(1, buf); + + if (exceedingBI.equals(BigInteger.ZERO)) { + LOGGER.fine(String.format( + "Config flags size > %d, but exceeding bytes are all zero, so it should be ok.", + KNOWN_CONFIG_BYTES)); + } else { + LOGGER.warning(String.format( + "Config flags size > %d. Exceeding bytes: 0x%X.", + KNOWN_CONFIG_BYTES, exceedingBI)); + isInvalid = true; + } + } + + return new ResConfigFlags(mcc, mnc, language, country, orientation, + touchscreen, density, keyboard, navigation, inputFlags, + screenWidth, screenHeight, sdkVersion, screenLayout, uiMode, + smallestScreenWidthDp, screenWidthDp, screenHeightDp, isInvalid); + } + + private void addMissingResSpecs() throws AndrolibException { + int resId = mResId & 0xffff0000; + + for (int i = 0; i < mMissingResSpecs.length; i++) { + if (! mMissingResSpecs[i]) { + continue; + } + + ResResSpec spec = new ResResSpec(new ResID(resId | i), + String.format("APKTOOL_DUMMY_%04x", i), mPkg, mType); + mPkg.addResSpec(spec); + mType.addResSpec(spec); + + ResValue value = new ResBoolValue(false, null); + ResResource res = new ResResource( + mPkg.getOrCreateConfig(new ResConfigFlags()), spec, value); + mPkg.addResource(res); + mConfig.addResource(res); + spec.addResource(res); + } + } + + private Header nextChunk() throws IOException { + return mHeader = Header.read(mIn); + } + + private void checkChunkType(int expectedType) throws AndrolibException { + if (mHeader.type != expectedType) { + throw new AndrolibException(String.format( + "Invalid chunk type: expected=0x%08x, got=0x%08x", + expectedType, mHeader.type)); + } + } + + private void nextChunkCheckType(int expectedType) + throws IOException, AndrolibException { + nextChunk(); + checkChunkType(expectedType); + } + + private final ExtDataInput mIn; + private final ResTable mResTable; + private final CountingInputStream mCountIn; + private final List mFlagsOffsets; + private final boolean mKeepBroken; + + private Header mHeader; + private StringBlock mTableStrings; + private StringBlock mTypeNames; + private StringBlock mSpecNames; + private ResPackage mPkg; + private ResType mType; + private ResConfig mConfig; + private int mResId; + private boolean[] mMissingResSpecs; + + + private final static short ENTRY_FLAG_COMPLEX = 0x0001; + + + public static class Header { + public final short type; + public final int chunkSize; + + public Header(short type, int size) { + this.type = type; + this.chunkSize = size; + } + + public static Header read(ExtDataInput in) throws IOException { + short type; + try { + type = in.readShort(); + } catch (EOFException ex) { + return new Header(TYPE_NONE, 0); + } + in.skipBytes(2); + return new Header(type, in.readInt()); + } + + public final static short + TYPE_NONE = -1, + TYPE_TABLE = 0x0002, + TYPE_PACKAGE = 0x0200, + TYPE_TYPE = 0x0202, + TYPE_CONFIG = 0x0201; + } + + 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 static final Logger LOGGER = + Logger.getLogger(ARSCDecoder.class.getName()); + private static final int KNOWN_CONFIG_BYTES = 36; + + + 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 != 1) { + throw new AndrolibException( + "Arsc file contains zero or multiple packages"); + } + return mPackages[0]; + } + + public ResTable getResTable() { + return mResTable; + } + + private final ResPackage[] mPackages; + private final FlagsOffset[] mFlagsOffsets; + private final ResTable mResTable; + } +} 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 new file mode 100644 index 0000000..91197bc --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java @@ -0,0 +1,1013 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import android.content.res.XmlResourceParser; +import android.util.TypedValue; +import brut.androlib.AndrolibException; +import brut.androlib.res.xml.ResXmlEncoders; +import brut.util.ExtDataInput; +import com.mindprod.ledatastream.LEDataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.xmlpull.v1.XmlPullParserException; + +/** + * @author Ryszard Wiśniewski + * @author Dmitry Skiba + * + * Binary xml files parser. + * + * Parser has only two states: (1) Operational state, which parser obtains after + * first successful call to next() and retains until 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 { + + public AXmlResourceParser() { + resetEventInfo(); + } + + public AXmlResourceParser(InputStream stream) { + this(); + open(stream); + } + + public AndrolibException getFirstError() { + return mFirstError; + } + + public ResAttrDecoder getAttrDecoder() { + return mAttrDecoder; + } + + public void setAttrDecoder(ResAttrDecoder attrDecoder) { + mAttrDecoder = attrDecoder; + } + + public void open(InputStream stream) { + close(); + if (stream != null) { + m_reader = new ExtDataInput( + new LEDataInputStream(stream)); + } + } + + public void close() { + if (!m_operational) { + return; + } + m_operational = false; +// m_reader.close(); + m_reader = null; + m_strings = null; + m_resourceIDs = null; + m_namespaces.reset(); + resetEventInfo(); + } + + /////////////////////////////////// iteration + public int next() throws XmlPullParserException, IOException { + if (m_reader == null) { + throw new XmlPullParserException("Parser is not opened.", this, null); + } + try { + doNext(); + return m_event; + } catch (IOException e) { + close(); + throw e; + } + } + + public int nextToken() throws XmlPullParserException, IOException { + return next(); + } + + public int nextTag() throws XmlPullParserException, IOException { + int eventType = next(); + if (eventType == TEXT && isWhitespace()) { + eventType = next(); + } + if (eventType != START_TAG && eventType != END_TAG) { + throw new XmlPullParserException("Expected start or end tag.", this, null); + } + return eventType; + } + + public String nextText() throws XmlPullParserException, IOException { + if (getEventType() != START_TAG) { + throw new XmlPullParserException("Parser must be on START_TAG to read next text.", this, null); + } + int eventType = next(); + if (eventType == TEXT) { + String result = getText(); + eventType = next(); + if (eventType != END_TAG) { + throw new XmlPullParserException("Event TEXT must be immediately followed by END_TAG.", this, null); + } + return result; + } else if (eventType == END_TAG) { + return ""; + } else { + throw new XmlPullParserException("Parser must be on START_TAG or TEXT to read text.", this, null); + } + } + + public void require(int type, String namespace, String name) throws XmlPullParserException, IOException { + if (type != getEventType() + || (namespace != null && !namespace.equals(getNamespace())) + || (name != null && !name.equals(getName()))) { + throw new XmlPullParserException(TYPES[type] + " is expected.", this, null); + } + } + + public int getDepth() { + return m_namespaces.getDepth() - 1; + } + + public int getEventType() throws XmlPullParserException { + return m_event; + } + + public int getLineNumber() { + return m_lineNumber; + } + + public String getName() { + if (m_name == -1 || (m_event != START_TAG && m_event != END_TAG)) { + return null; + } + return m_strings.getString(m_name); + } + + public String getText() { + if (m_name == -1 || m_event != TEXT) { + return null; + } + return m_strings.getString(m_name); + } + + public char[] getTextCharacters(int[] holderForStartAndLength) { + String text = getText(); + if (text == null) { + return null; + } + holderForStartAndLength[0] = 0; + holderForStartAndLength[1] = text.length(); + char[] chars = new char[text.length()]; + text.getChars(0, text.length(), chars, 0); + return chars; + } + + public String getNamespace() { + return m_strings.getString(m_namespaceUri); + } + + public String getPrefix() { + int prefix = m_namespaces.findPrefix(m_namespaceUri); + return m_strings.getString(prefix); + } + + public String getPositionDescription() { + return "XML line #" + getLineNumber(); + } + + public int getNamespaceCount(int depth) throws XmlPullParserException { + return m_namespaces.getAccumulatedCount(depth); + } + + public String getNamespacePrefix(int pos) throws XmlPullParserException { + int prefix = m_namespaces.getPrefix(pos); + return m_strings.getString(prefix); + } + + public String getNamespaceUri(int pos) throws XmlPullParserException { + int uri = m_namespaces.getUri(pos); + return m_strings.getString(uri); + } + + /////////////////////////////////// attributes + public String getClassAttribute() { + if (m_classAttribute == -1) { + return null; + } + int offset = getAttributeOffset(m_classAttribute); + int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING]; + return m_strings.getString(value); + } + + public String getIdAttribute() { + if (m_idAttribute == -1) { + return null; + } + int offset = getAttributeOffset(m_idAttribute); + int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING]; + return m_strings.getString(value); + } + + public int getIdAttributeResourceValue(int defaultValue) { + if (m_idAttribute == -1) { + return defaultValue; + } + int offset = getAttributeOffset(m_idAttribute); + int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType != TypedValue.TYPE_REFERENCE) { + return defaultValue; + } + return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA]; + } + + public int getStyleAttribute() { + if (m_styleAttribute == -1) { + return 0; + } + int offset = getAttributeOffset(m_styleAttribute); + return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA]; + } + + public int getAttributeCount() { + if (m_event != START_TAG) { + return -1; + } + return m_attributes.length / ATTRIBUTE_LENGHT; + } + + public String getAttributeNamespace(int index) { + int offset = getAttributeOffset(index); + int namespace = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI]; + if (namespace == -1) { + return ""; + } + return m_strings.getString(namespace); + } + + public String getAttributePrefix(int index) { + int offset = getAttributeOffset(index); + int uri = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI]; + int prefix = m_namespaces.findPrefix(uri); + if (prefix == -1) { + return ""; + } + return m_strings.getString(prefix); + } + + public String getAttributeName(int index) { + int offset = getAttributeOffset(index); + int name = m_attributes[offset + ATTRIBUTE_IX_NAME]; + if (name == -1) { + return ""; + } + return m_strings.getString(name); + } + + public int getAttributeNameResource(int index) { + int offset = getAttributeOffset(index); + int name = m_attributes[offset + ATTRIBUTE_IX_NAME]; + if (m_resourceIDs == null + || name < 0 || name >= m_resourceIDs.length) { + return 0; + } + return m_resourceIDs[name]; + } + + public int getAttributeValueType(int index) { + int offset = getAttributeOffset(index); + return m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE]; + } + + public int getAttributeValueData(int index) { + int offset = getAttributeOffset(index); + return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA]; + } + + public String getAttributeValue(int index) { + int offset = getAttributeOffset(index); + int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE]; + int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA]; + int valueRaw = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING]; + + if (mAttrDecoder != null) { + try { + return mAttrDecoder.decode(valueType, valueData, + valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars( + m_strings.getString(valueRaw)), + getAttributeNameResource(index)); + } catch (AndrolibException ex) { + setFirstError(ex); + LOGGER.log(Level.WARNING, String.format( + "Could not decode attr value, using undecoded value " + + "instead: ns=%s, name=%s, value=0x%08x", + getAttributePrefix(index), getAttributeName(index), + valueData), ex); + } + } else { + if (valueType == TypedValue.TYPE_STRING) { + return ResXmlEncoders.escapeXmlChars(m_strings.getString(valueRaw)); + } + } + + return TypedValue.coerceToString(valueType, valueData); + } + + public boolean getAttributeBooleanValue(int index, boolean defaultValue) { + return getAttributeIntValue(index, defaultValue ? 1 : 0) != 0; + } + + public float getAttributeFloatValue(int index, float defaultValue) { + int offset = getAttributeOffset(index); + int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType == TypedValue.TYPE_FLOAT) { + int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA]; + return Float.intBitsToFloat(valueData); + } + return defaultValue; + } + + public int getAttributeIntValue(int index, int defaultValue) { + int offset = getAttributeOffset(index); + int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType >= TypedValue.TYPE_FIRST_INT + && valueType <= TypedValue.TYPE_LAST_INT) { + return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA]; + } + return defaultValue; + } + + public int getAttributeUnsignedIntValue(int index, int defaultValue) { + return getAttributeIntValue(index, defaultValue); + } + + public int getAttributeResourceValue(int index, int defaultValue) { + int offset = getAttributeOffset(index); + int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType == TypedValue.TYPE_REFERENCE) { + return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA]; + } + return defaultValue; + } + + public String getAttributeValue(String namespace, String attribute) { + int index = findAttribute(namespace, attribute); + if (index == -1) { + return ""; + } + return getAttributeValue(index); + } + + public boolean getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue) { + int index = findAttribute(namespace, attribute); + if (index == -1) { + return defaultValue; + } + return getAttributeBooleanValue(index, defaultValue); + } + + public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) { + int index = findAttribute(namespace, attribute); + if (index == -1) { + return defaultValue; + } + return getAttributeFloatValue(index, defaultValue); + } + + public int getAttributeIntValue(String namespace, String attribute, int defaultValue) { + int index = findAttribute(namespace, attribute); + if (index == -1) { + return defaultValue; + } + return getAttributeIntValue(index, defaultValue); + } + + public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) { + int index = findAttribute(namespace, attribute); + if (index == -1) { + return defaultValue; + } + return getAttributeUnsignedIntValue(index, defaultValue); + } + + public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) { + int index = findAttribute(namespace, attribute); + if (index == -1) { + return defaultValue; + } + return getAttributeResourceValue(index, defaultValue); + } + + public int getAttributeListValue(int index, String[] options, int defaultValue) { + // TODO implement + return 0; + } + + public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) { + // TODO implement + return 0; + } + + public String getAttributeType(int index) { + return "CDATA"; + } + + public boolean isAttributeDefault(int index) { + return false; + } + + /////////////////////////////////// dummies + public void setInput(InputStream stream, String inputEncoding) throws XmlPullParserException { + open(stream); + } + + public void setInput(Reader reader) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + public String getInputEncoding() { + return null; + } + + public int getColumnNumber() { + return -1; + } + + public boolean isEmptyElementTag() throws XmlPullParserException { + return false; + } + + public boolean isWhitespace() throws XmlPullParserException { + return false; + } + + public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + public String getNamespace(String prefix) { + throw new RuntimeException(E_NOT_SUPPORTED); + } + + public Object getProperty(String name) { + return null; + } + + public void setProperty(String name, Object value) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + public boolean getFeature(String feature) { + return false; + } + + public void setFeature(String name, boolean value) throws XmlPullParserException { + 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 final void reset() { + m_dataLength = 0; + m_count = 0; + m_depth = 0; + } + + public final int getTotalCount() { + return m_count; + } + + public final int getCurrentCount() { + if (m_dataLength == 0) { + return 0; + } + int offset = m_dataLength - 1; + return m_data[offset]; + } + + public final 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 final 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; + m_count += 1; + } + + public final boolean pop(int prefix, int uri) { + if (m_dataLength == 0) { + return false; + } + int offset = m_dataLength - 1; + int count = m_data[offset]; + for (int i = 0, o = offset - 2; i != count; ++i, o -= 2) { + if (m_data[o] != prefix || m_data[o + 1] != uri) { + continue; + } + count -= 1; + if (i == 0) { + m_data[o] = count; + o -= (1 + count * 2); + m_data[o] = count; + } else { + m_data[offset] = count; + offset -= (1 + 2 + count * 2); + m_data[offset] = count; + System.arraycopy( + m_data, o + 2, + m_data, o, + m_dataLength - o); + } + m_dataLength -= 2; + m_count -= 1; + return true; + } + return false; + } + + public final 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; + m_count -= 1; + return true; + } + + public final int getPrefix(int index) { + return get(index, true); + } + + public final int getUri(int index) { + return get(index, false); + } + + public final int findPrefix(int uri) { + return find(uri, false); + } + + public final int findUri(int prefix) { + return find(prefix, true); + } + + public final int getDepth() { + return m_depth; + } + + public final void increaseDepth() { + ensureDataCapacity(2); + int offset = m_dataLength; + m_data[offset] = 0; + m_data[offset + 1] = 0; + m_dataLength += 2; + m_depth += 1; + } + + public final 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_count -= count; + 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 final 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 final 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_count; + private int m_depth; + } + + /////////////////////////////////// package-visible +// final void fetchAttributes(int[] styleableIDs,TypedArray result) { +// result.resetIndices(); +// if (m_attributes==null || m_resourceIDs==null) { +// return; +// } +// boolean needStrings=false; +// for (int i=0,e=styleableIDs.length;i!=e;++i) { +// int id=styleableIDs[i]; +// for (int o=0;o!=m_attributes.length;o+=ATTRIBUTE_LENGHT) { +// int name=m_attributes[o+ATTRIBUTE_IX_NAME]; +// if (name>=m_resourceIDs.length || +// m_resourceIDs[name]!=id) +// { +// continue; +// } +// int valueType=m_attributes[o+ATTRIBUTE_IX_VALUE_TYPE]; +// int valueData; +// int assetCookie; +// if (valueType==TypedValue.TYPE_STRING) { +// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_STRING]; +// assetCookie=-1; +// needStrings=true; +// } else { +// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_DATA]; +// assetCookie=0; +// } +// result.addValue(i,valueType,valueData,assetCookie,id,0); +// } +// } +// if (needStrings) { +// result.setStrings(m_strings); +// } +// } + final StringBlock getStrings() { + return m_strings; + } + + /////////////////////////////////// + private final int getAttributeOffset(int index) { + if (m_event != START_TAG) { + throw new IndexOutOfBoundsException("Current event is not START_TAG."); + } + int offset = index * ATTRIBUTE_LENGHT; + if (offset >= m_attributes.length) { + throw new IndexOutOfBoundsException("Invalid attribute index (" + index + ")."); + } + return offset; + } + + private final int findAttribute(String namespace, String attribute) { + if (m_strings == null || attribute == null) { + return -1; + } + int name = m_strings.find(attribute); + if (name == -1) { + return -1; + } + int uri = (namespace != null) + ? m_strings.find(namespace) + : -1; + for (int o = 0; o != m_attributes.length; ++o) { + if (name == m_attributes[o + ATTRIBUTE_IX_NAME] + && (uri == -1 || uri == m_attributes[o + ATTRIBUTE_IX_NAMESPACE_URI])) { + return o / ATTRIBUTE_LENGHT; + } + } + return -1; + } + + private final void resetEventInfo() { + m_event = -1; + m_lineNumber = -1; + m_name = -1; + m_namespaceUri = -1; + m_attributes = null; + m_idAttribute = -1; + m_classAttribute = -1; + m_styleAttribute = -1; + } + + private final void doNext() throws IOException { + // Delayed initialization. + if (m_strings == null) { + m_reader.skipCheckInt(CHUNK_AXML_FILE); + /* + * chunkSize + */ m_reader.skipInt(); + m_strings = StringBlock.read(m_reader); + m_namespaces.increaseDepth(); + m_operational = true; + } + + if (m_event == END_DOCUMENT) { + return; + } + + int event = m_event; + resetEventInfo(); + + while (true) { + if (m_decreaseDepth) { + m_decreaseDepth = false; + m_namespaces.decreaseDepth(); + } + + // Fake END_DOCUMENT event. + if (event == END_TAG + && m_namespaces.getDepth() == 1 + && m_namespaces.getCurrentCount() == 0) { + m_event = END_DOCUMENT; + break; + } + + int chunkType; + if (event == START_DOCUMENT) { + // Fake event, see CHUNK_XML_START_TAG handler. + chunkType = CHUNK_XML_START_TAG; + } else { + chunkType = m_reader.readInt(); + } + + if (chunkType == CHUNK_RESOURCEIDS) { + int chunkSize = m_reader.readInt(); + if (chunkSize < 8 || (chunkSize % 4) != 0) { + throw new IOException("Invalid resource ids size (" + chunkSize + ")."); + } + m_resourceIDs = m_reader.readIntArray(chunkSize / 4 - 2); + continue; + } + + if (chunkType < CHUNK_XML_FIRST || chunkType > CHUNK_XML_LAST) { + throw new IOException("Invalid chunk type (" + chunkType + ")."); + } + + // Fake START_DOCUMENT event. + if (chunkType == CHUNK_XML_START_TAG && event == -1) { + m_event = START_DOCUMENT; + break; + } + + // Common header. + /*chunkSize*/ m_reader.skipInt(); + int lineNumber = m_reader.readInt(); + /*0xFFFFFFFF*/ m_reader.skipInt(); + + if (chunkType == CHUNK_XML_START_NAMESPACE + || chunkType == CHUNK_XML_END_NAMESPACE) { + if (chunkType == CHUNK_XML_START_NAMESPACE) { + int prefix = m_reader.readInt(); + int uri = m_reader.readInt(); + m_namespaces.push(prefix, uri); + } else { + /*prefix*/ m_reader.skipInt(); + /*uri*/ m_reader.skipInt(); + m_namespaces.pop(); + } + continue; + } + + m_lineNumber = lineNumber; + + if (chunkType == CHUNK_XML_START_TAG) { + m_namespaceUri = m_reader.readInt(); + m_name = m_reader.readInt(); + /*flags?*/ m_reader.skipInt(); + int attributeCount = m_reader.readInt(); + m_idAttribute = (attributeCount >>> 16) - 1; + attributeCount &= 0xFFFF; + m_classAttribute = m_reader.readInt(); + m_styleAttribute = (m_classAttribute >>> 16) - 1; + m_classAttribute = (m_classAttribute & 0xFFFF) - 1; + m_attributes = m_reader.readIntArray(attributeCount * ATTRIBUTE_LENGHT); + for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < m_attributes.length;) { + m_attributes[i] = (m_attributes[i] >>> 24); + i += ATTRIBUTE_LENGHT; + } + m_namespaces.increaseDepth(); + m_event = START_TAG; + break; + } + + if (chunkType == CHUNK_XML_END_TAG) { + m_namespaceUri = m_reader.readInt(); + m_name = m_reader.readInt(); + m_event = END_TAG; + m_decreaseDepth = true; + break; + } + + if (chunkType == CHUNK_XML_TEXT) { + m_name = m_reader.readInt(); + /*?*/ m_reader.skipInt(); + /*?*/ m_reader.skipInt(); + m_event = TEXT; + break; + } + } + } + + private static String formatArray(int[] array, int min, int max) { + if (max > array.length) { + max = array.length; + } + if (min < 0) { + min = 0; + } + StringBuffer sb = new StringBuffer("["); + int i = min; + while (true) { + sb.append(array[i]); + i++; + if (i < max) { + sb.append(", "); + } else { + sb.append("]"); + break; + } + } + return sb.toString(); + } + + private boolean compareAttr(int[] attr1, int[] attr2) { + //TODO: sort Attrs + /* + * ATTRIBUTE_IX_VALUE_TYPE == TYPE_STRING : ATTRIBUTE_IX_VALUE_STRING : + * ATTRIBUTE_IX_NAMESPACE_URI ATTRIBUTE_IX_NAMESPACE_URI : + * ATTRIBUTE_IX_NAME id + * + */ + if (attr1[ATTRIBUTE_IX_VALUE_TYPE] == TypedValue.TYPE_STRING + && attr1[ATTRIBUTE_IX_VALUE_TYPE] == attr2[ATTRIBUTE_IX_VALUE_TYPE] + && //(m_strings.touch(attr1[ATTRIBUTE_IX_VALUE_STRING], m_name) || + // m_strings.touch(attr2[ATTRIBUTE_IX_VALUE_STRING], m_name)) && + //m_strings.touch(attr1[ATTRIBUTE_IX_VALUE_STRING], m_name) && + attr1[ATTRIBUTE_IX_VALUE_STRING] != attr2[ATTRIBUTE_IX_VALUE_STRING]) { + return (attr1[ATTRIBUTE_IX_VALUE_STRING] < attr2[ATTRIBUTE_IX_VALUE_STRING]); + } else if ((attr1[ATTRIBUTE_IX_NAMESPACE_URI] == attr2[ATTRIBUTE_IX_NAMESPACE_URI]) && (attr1[ATTRIBUTE_IX_NAMESPACE_URI] != -1) + && //(m_strings.touch(attr1[ATTRIBUTE_IX_NAME], m_name) || + // m_strings.touch(attr2[ATTRIBUTE_IX_NAME], m_name)) && + //m_strings.touch(attr1[ATTRIBUTE_IX_NAME], m_name) && + (attr1[ATTRIBUTE_IX_NAME] != attr2[ATTRIBUTE_IX_NAME])) { + return (attr1[ATTRIBUTE_IX_NAME] < attr2[ATTRIBUTE_IX_NAME]); + //} else if (attr1[ATTRIBUTE_IX_NAMESPACE_URI] < attr2[ATTRIBUTE_IX_NAMESPACE_URI]) { + // return true; + } else { + return false; + } + } + + + private void setFirstError(AndrolibException error) { + if (mFirstError == null) { + mFirstError = 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; + + private boolean m_operational = false; + private StringBlock m_strings; + private int[] m_resourceIDs; + private NamespaceStack m_namespaces = new NamespaceStack(); + private boolean m_decreaseDepth; + private int m_event; + private int m_lineNumber; + private int m_name; + private int m_namespaceUri; + private int[] m_attributes; + private int m_idAttribute; + private int m_classAttribute; + private int m_styleAttribute; + + private final static Logger LOGGER = + Logger.getLogger(AXmlResourceParser.class.getName()); + private static final String E_NOT_SUPPORTED = "Method is not supported."; + private static final int + ATTRIBUTE_IX_NAMESPACE_URI = 0, + ATTRIBUTE_IX_NAME = 1, + ATTRIBUTE_IX_VALUE_STRING = 2, + ATTRIBUTE_IX_VALUE_TYPE = 3, + ATTRIBUTE_IX_VALUE_DATA = 4, + ATTRIBUTE_LENGHT = 5; + + private static final int + CHUNK_AXML_FILE = 0x00080003, + CHUNK_RESOURCEIDS = 0x00080180, + CHUNK_XML_FIRST = 0x00100100, + CHUNK_XML_START_NAMESPACE = 0x00100100, + CHUNK_XML_END_NAMESPACE = 0x00100101, + CHUNK_XML_START_TAG = 0x00100102, + CHUNK_XML_END_TAG = 0x00100103, + CHUNK_XML_TEXT = 0x00100104, + CHUNK_XML_LAST = 0x00100104; +} \ No newline at end of file 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 new file mode 100644 index 0000000..53b1b4a --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java @@ -0,0 +1,139 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.AndrolibException; +import brut.androlib.err.CantFind9PatchChunk; +import brut.util.ExtDataInput; +import java.awt.image.BufferedImage; +import java.io.*; +import javax.imageio.ImageIO; +import org.apache.commons.io.IOUtils; + +/** + * @author Ryszard Wiśniewski + */ +public class Res9patchStreamDecoder implements ResStreamDecoder { + public void decode(InputStream in, OutputStream out) + throws AndrolibException { + try { + byte[] data = IOUtils.toByteArray(in); + + BufferedImage im = ImageIO.read(new ByteArrayInputStream(data)); + int w = im.getWidth(), h = im.getHeight(); + + BufferedImage im2 = new BufferedImage( + w + 2, h + 2, BufferedImage.TYPE_4BYTE_ABGR); + if (im.getType() == BufferedImage.TYPE_4BYTE_ABGR) { + im2.getRaster().setRect(1, 1, im.getRaster()); + } else { + im2.getGraphics().drawImage(im, 1, 1, null); + } + + NinePatch np = getNinePatch(data); + drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight); + drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom); + + int[] xDivs = np.xDivs; + for (int i = 0; i < xDivs.length; i += 2) { + drawHLine(im2, 0, xDivs[i] + 1, xDivs[i + 1]); + } + + int[] yDivs = np.yDivs; + for (int i = 0; i < yDivs.length; i += 2) { + drawVLine(im2, 0, yDivs[i] + 1, yDivs[i + 1]); + } + + ImageIO.write(im2, "png", out); + } catch (IOException ex) { + throw new AndrolibException(ex); + } + } + + private NinePatch getNinePatch(byte[] data) + throws AndrolibException, IOException { + ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data)); + find9patchChunk(di); + return NinePatch.decode(di); + } + + private void find9patchChunk(DataInput di) + throws AndrolibException, IOException { + di.skipBytes(8); + while (true) { + int size; + try { + size = di.readInt(); + } catch (IOException ex) { + throw new CantFind9PatchChunk("Cant find nine patch chunk", ex); + } + if (di.readInt() == NP_CHUNK_TYPE) { + return; + } + di.skipBytes(size + 4); + } + } + + private void drawHLine(BufferedImage im, int y, int x1, int x2) { + for (int x = x1; x <= x2; x++) { + im.setRGB(x, y, NP_COLOR); + } + } + + private void drawVLine(BufferedImage im, int x, int y1, int y2) { + for (int y = y1; y <= y2; y++) { + im.setRGB(x, y, NP_COLOR); + } + } + + private static final int NP_CHUNK_TYPE = 0x6e705463; // npTc + private static final int NP_COLOR = 0xff000000; + + + 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); + byte numXDivs = di.readByte(); + byte numYDivs = di.readByte(); + di.skipBytes(1); + di.skipBytes(8); + int padLeft = di.readInt(); + int padRight = di.readInt(); + int padTop = di.readInt(); + int padBottom = di.readInt(); + di.skipBytes(4); + int[] xDivs = di.readIntArray(numXDivs); + int[] yDivs = di.readIntArray(numYDivs); + + return new NinePatch(padLeft, padRight, padTop, padBottom, + xDivs, yDivs); + } + } +} \ No newline at end of file diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResAttrDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResAttrDecoder.java new file mode 100644 index 0000000..75c601e --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResAttrDecoder.java @@ -0,0 +1,55 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResPackage; +import brut.androlib.res.data.value.ResAttr; +import brut.androlib.res.data.value.ResScalarValue; + +/** + * @author Ryszard Wiśniewski + */ +public class ResAttrDecoder { + public String decode(int type, int value, String rawValue, int attrResId) + throws AndrolibException { + ResScalarValue resValue = mCurrentPackage.getValueFactory() + .factory(type, value, rawValue); + + String decoded = null; + if (attrResId != 0) { + ResAttr attr = (ResAttr) getCurrentPackage().getResTable() + .getResSpec(attrResId).getDefaultResource().getValue(); + decoded = attr.convertToResXmlFormat(resValue); + } + + return decoded != null ? decoded : resValue.encodeAsResXmlAttr(); + } + + public ResPackage getCurrentPackage() throws AndrolibException { + if (mCurrentPackage == null) { + throw new AndrolibException("Current package not set"); + } + return mCurrentPackage; + } + + public void setCurrentPackage(ResPackage currentPackage) { + mCurrentPackage = currentPackage; + } + + private ResPackage mCurrentPackage; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResFileDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResFileDecoder.java new file mode 100644 index 0000000..e16f9b5 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResFileDecoder.java @@ -0,0 +1,145 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.AndrolibException; +import brut.androlib.err.CantFind9PatchChunk; +import brut.androlib.res.data.ResResource; +import brut.androlib.res.data.value.ResBoolValue; +import brut.androlib.res.data.value.ResFileValue; +import brut.directory.Directory; +import brut.directory.DirectoryException; +import java.io.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Ryszard Wiśniewski + */ +public class ResFileDecoder { + private final ResStreamDecoderContainer mDecoders; + + public ResFileDecoder(ResStreamDecoderContainer decoders) { + this.mDecoders = decoders; + } + + public void decode(ResResource res, Directory inDir, Directory outDir) + throws AndrolibException { + + ResFileValue fileValue = (ResFileValue) res.getValue(); + String inFileName = fileValue.getStrippedPath(); + String outResName = res.getFilePath(); + String typeName = res.getResSpec().getType().getName(); + + String ext = null; + String outFileName; + int extPos = inFileName.lastIndexOf("."); + if (extPos == -1) { + outFileName = outResName; + } else { + ext = inFileName.substring(extPos); + outFileName = outResName + ext; + } + + try { + if (typeName.equals("raw")) { + decode(inDir, inFileName, outDir, outFileName, "raw"); + return; + } + if (typeName.equals("drawable") || typeName.equals("mipmap")) { + if (inFileName.toLowerCase().endsWith(".9.png")) { + outFileName = outResName + ".9" + ext; + + try { + decode( + inDir, inFileName, outDir, outFileName, "9patch"); + return; + } catch (CantFind9PatchChunk ex) { + LOGGER.log(Level.WARNING, String.format( + "Cant find 9patch chunk in file: \"%s\". Renaming it to *.png.", + inFileName + ), ex); + outDir.removeFile(outFileName); + outFileName = outResName + ext; + } + } + if (! ".xml".equals(ext)) { + decode(inDir, inFileName, outDir, outFileName, "raw"); + return; + } + } + + decode(inDir, inFileName, outDir, outFileName, "xml"); + } catch (AndrolibException ex) { + LOGGER.log(Level.SEVERE, String.format( + "Could not decode file, replacing by FALSE value: %s", + inFileName, outFileName), ex); + res.replace(new ResBoolValue(false, null)); + } + } + + public void decode(Directory inDir, String inFileName, Directory outDir, + String outFileName, String decoder) throws AndrolibException { + InputStream in = null; + OutputStream out = null; + try { + in = inDir.getFileInput(inFileName); + out = outDir.getFileOutput(outFileName); + mDecoders.decode(in, out, decoder); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } finally { + try{ + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException ex) { + throw new AndrolibException(ex); + } + } + } + + public void decodeManifest(Directory inDir, String inFileName, Directory outDir, + String outFileName) throws AndrolibException { + InputStream in = null; + OutputStream out = null; + try { + in = inDir.getFileInput(inFileName); + out = outDir.getFileOutput(outFileName); + ((XmlPullStreamDecoder)mDecoders.getDecoder("xml")).decodeManifest(in, out); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } finally { + try{ + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException ex) { + throw new AndrolibException(ex); + } + } + } + + private final static Logger LOGGER = + Logger.getLogger(ResFileDecoder.class.getName()); +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResRawStreamDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResRawStreamDecoder.java new file mode 100644 index 0000000..6c5d0e3 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResRawStreamDecoder.java @@ -0,0 +1,37 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.AndrolibException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.commons.io.IOUtils; + +/** + * @author Ryszard Wiśniewski + */ +public class ResRawStreamDecoder implements ResStreamDecoder { + public void decode(InputStream in, OutputStream out) + throws AndrolibException { + try { + IOUtils.copy(in, out); + } catch (IOException ex) { + throw new AndrolibException("Could not decode raw stream", ex); + } + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResStreamDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResStreamDecoder.java new file mode 100644 index 0000000..e7bbcd6 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResStreamDecoder.java @@ -0,0 +1,29 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.AndrolibException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @author Ryszard Wiśniewski + */ +public interface ResStreamDecoder { + public void decode(InputStream in, OutputStream out) + throws AndrolibException; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResStreamDecoderContainer.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResStreamDecoderContainer.java new file mode 100644 index 0000000..d478bf9 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResStreamDecoderContainer.java @@ -0,0 +1,48 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.AndrolibException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Ryszard Wiśniewski + */ +public class ResStreamDecoderContainer { + private final Map mDecoders = + new HashMap(); + + public void decode(InputStream in, OutputStream out, String decoderName) + throws AndrolibException { + getDecoder(decoderName).decode(in, out); + } + + public ResStreamDecoder getDecoder(String name) throws AndrolibException { + ResStreamDecoder decoder = mDecoders.get(name); + if (decoder == null) { + throw new AndrolibException("Undefined decoder: " + name); + } + return decoder; + } + + public void setDecoder(String name, ResStreamDecoder decoder) { + mDecoders.put(name, decoder); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java new file mode 100644 index 0000000..36ecda5 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java @@ -0,0 +1,347 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.res.xml.ResXmlEncoders; +import brut.util.ExtDataInput; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Ryszard Wiśniewski + * @author Dmitry Skiba + * + * Block of strings, used in binary xml and arsc. + * + * TODO: + * - implement get() + * + */ +public class StringBlock { + + /** + * Reads whole (including chunk type) string block from stream. + * Stream must be at the chunk type. + */ + public static StringBlock read(ExtDataInput reader) throws IOException { + reader.skipCheckInt(CHUNK_TYPE); + int chunkSize = reader.readInt(); + int stringCount = reader.readInt(); + int styleOffsetCount = reader.readInt(); + int flags = reader.readInt(); + int stringsOffset = reader.readInt(); + int stylesOffset = reader.readInt(); + + StringBlock block = new StringBlock(); + block.m_isUTF8 = (flags & UTF8_FLAG) != 0; + block.m_stringOffsets = reader.readIntArray(stringCount); + block.m_stringOwns = new int[stringCount]; + for (int i=0;i= m_stringOffsets.length) { + return null; + } + int offset = m_stringOffsets[index]; + int length; + + if (! m_isUTF8) { + length = getShort(m_strings, offset) * 2; + offset += 2; + } else { + offset += getVarint(m_strings, offset)[1]; + int[] varint = getVarint(m_strings, offset); + offset += varint[1]; + length = varint[0]; + } + return decodeString(offset, length); + } + + /** + * Not yet implemented. + * + * Returns string with style information (if any). + */ + public CharSequence get(int index) { + return getString(index); + } + + /** + * Returns string with style tags (html-like). + */ + public String getHTML(int index) { + String raw = getString(index); + if (raw == null) { + return raw; + } + int[] style = getStyle(index); + if (style == null) { + return ResXmlEncoders.escapeXmlChars(raw); + } + StringBuilder html = new StringBuilder(raw.length() + 32); + int[] opened = new int[style.length / 3]; + int offset = 0, depth = 0; + while (true) { + int i = -1, j; + for (j = 0; j != style.length; j += 3) { + if (style[j + 1] == -1) { + continue; + } + if (i == -1 || style[i + 1] > style[j + 1]) { + i = j; + } + } + int start = ((i != -1) ? style[i + 1] : raw.length()); + for (j = depth - 1; j >= 0; j--) { + int last = opened[j]; + int end = style[last + 2]; + if (end >= start) { + break; + } + if (offset <= end) { + html.append(ResXmlEncoders.escapeXmlChars( + raw.substring(offset, end + 1))); + offset = end + 1; + } + outputStyleTag(getString(style[last]), html, true); + } + depth = j + 1; + if (offset < start) { + html.append(ResXmlEncoders.escapeXmlChars( + raw.substring(offset, start))); + offset = start; + } + if (i == -1) { + break; + } + outputStyleTag(getString(style[i]), html, false); + style[i + 1] = -1; + opened[depth++] = i; + } + return html.toString(); + } + + private void outputStyleTag(String tag, StringBuilder builder, + boolean close) { + builder.append('<'); + if (close) { + builder.append('/'); + } + + int pos = tag.indexOf(';'); + if (pos == -1) { + builder.append(tag); + } else { + builder.append(tag.substring(0, pos)); + if (! close) { + boolean loop = true; + while (loop) { + int pos2 = tag.indexOf('=', pos + 1); + builder.append(' ').append(tag.substring(pos + 1, pos2)) + .append("=\""); + pos = tag.indexOf(';', pos2 + 1); + + String val; + if (pos != -1) { + val = tag.substring(pos2 + 1, pos); + } else { + loop = false; + val = tag.substring(pos2 + 1); + } + + builder.append(ResXmlEncoders.escapeXmlChars(val)) + .append('"'); + } + } + } + builder.append('>'); + } + + /** + * Finds index of the string. + * Returns -1 if the string was not found. + */ + public int find(String string) { + if (string == null) { + return -1; + } + for (int i = 0; i != m_stringOffsets.length; ++i) { + int offset = m_stringOffsets[i]; + int length = getShort(m_strings, offset); + if (length != string.length()) { + continue; + } + int j = 0; + for (; j != length; ++j) { + offset += 2; + if (string.charAt(j) != getShort(m_strings, offset)) { + break; + } + } + if (j == length) { + return i; + } + } + return -1; + } + + ///////////////////////////////////////////// implementation + private StringBlock() { + } + + /** + * Returns style information - array of int triplets, + * where in each triplet: + * * first int is index of tag name ('b','i', etc.) + * * second int is tag start index in string + * * third int is tag end index in string + */ + private int[] getStyle(int index) { + if (m_styleOffsets == null || m_styles == null + || index >= m_styleOffsets.length) { + return null; + } + int offset = m_styleOffsets[index] / 4; + int style[]; + { + int count = 0; + for (int i = offset; i < m_styles.length; ++i) { + if (m_styles[i] == -1) { + break; + } + count += 1; + } + if (count == 0 || (count % 3) != 0) { + return null; + } + style = new int[count]; + } + for (int i = offset, j = 0; i < m_styles.length;) { + if (m_styles[i] == -1) { + break; + } + style[j++] = m_styles[i++]; + } + return style; + } + + private String decodeString(int offset, int length) { + try { + return (m_isUTF8 ? UTF8_DECODER : UTF16LE_DECODER).decode( + ByteBuffer.wrap(m_strings, offset, length)).toString(); + } catch (CharacterCodingException ex) { + LOGGER.log(Level.WARNING, null, ex); + return null; + } + } + + private static final int getShort(byte[] array, int offset) { + return (array[offset + 1] & 0xff) << 8 | array[offset] & 0xff; + } + + private static final int getShort(int[] array, int offset) { + int value = array[offset / 4]; + if ((offset % 4) / 2 == 0) { + return (value & 0xFFFF); + } else { + return (value >>> 16); + } + } + + private static final int[] getVarint(byte[] array, int offset) { + int val = array[offset]; + boolean more = (val & 0x80) != 0; + val &= 0x7f; + + if (! more) { + return new int[]{val, 1}; + } else { + return new int[]{val << 8 | array[offset + 1] & 0xff, 2}; + } + } + + public boolean touch(int index, int own) { + if (index < 0 + || m_stringOwns == null + || index >= m_stringOwns.length) { + return false; + } + if(m_stringOwns[index] == -1) { + m_stringOwns[index] = own; + return true; + } else if (m_stringOwns[index] == own) { + return true; + } else { + return false; + } + } + + private int[] m_stringOffsets; + private byte[] m_strings; + private int[] m_styleOffsets; + private int[] m_styles; + private boolean m_isUTF8; + private int[] m_stringOwns; + private static final CharsetDecoder UTF16LE_DECODER = + Charset.forName("UTF-16LE").newDecoder(); + private static final CharsetDecoder UTF8_DECODER = + Charset.forName("UTF-8").newDecoder(); + private static final Logger LOGGER = + Logger.getLogger(StringBlock.class.getName()); + private static final int CHUNK_TYPE = 0x001C0001; + private static final int UTF8_FLAG = 0x00000100; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/XmlPullStreamDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/XmlPullStreamDecoder.java new file mode 100644 index 0000000..c7f2146 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/XmlPullStreamDecoder.java @@ -0,0 +1,125 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResTable; +import brut.androlib.res.util.ExtXmlSerializer; +import java.io.*; +import java.util.logging.Logger; +import org.xmlpull.v1.*; +import org.xmlpull.v1.wrapper.*; +import org.xmlpull.v1.wrapper.classic.StaticXmlSerializerWrapper; + +/** + * @author Ryszard Wiśniewski + */ +public class XmlPullStreamDecoder implements ResStreamDecoder { + public XmlPullStreamDecoder(XmlPullParser parser, + ExtXmlSerializer serializer) { + this.mParser = parser; + this.mSerial = serializer; + } + + public void decode(InputStream in, OutputStream out) + throws AndrolibException { + try { + XmlPullWrapperFactory factory = XmlPullWrapperFactory.newInstance(); + XmlPullParserWrapper par = factory.newPullParserWrapper(mParser); + final ResTable resTable = ((AXmlResourceParser)mParser).getAttrDecoder().getCurrentPackage().getResTable(); + + XmlSerializerWrapper ser = new StaticXmlSerializerWrapper(mSerial, factory){ + boolean hideSdkInfo = false; + @Override + public void event(XmlPullParser pp) throws XmlPullParserException, IOException { + int type = pp.getEventType(); + + if (type == XmlPullParser.START_TAG) { + if ("uses-sdk".equalsIgnoreCase(pp.getName())) { + try { + hideSdkInfo = parseAttr(pp); + if(hideSdkInfo) { + return; + } + } catch (AndrolibException e) {} + } + } else if (hideSdkInfo && type == XmlPullParser.END_TAG && + "uses-sdk".equalsIgnoreCase(pp.getName())) { + return; + } + super.event(pp); + } + + private boolean parseAttr(XmlPullParser pp) throws AndrolibException { + ResTable restable = resTable; + for (int i = 0; i < pp.getAttributeCount(); i++) { + final String a_ns = "http://schemas.android.com/apk/res/android"; + String ns = pp.getAttributeNamespace (i); + if (a_ns.equalsIgnoreCase(ns)) { + String name = pp.getAttributeName (i); + String value = pp.getAttributeValue (i); + if (name != null && value != null) { + if (name.equalsIgnoreCase("minSdkVersion") || + name.equalsIgnoreCase("targetSdkVersion") || + name.equalsIgnoreCase("maxSdkVersion")) { + restable.addSdkInfo(name, value); + } else { + restable.clearSdkInfo(); + return false;//Found unknown flags + } + } + } else { + resTable.clearSdkInfo(); + return false;//Found unknown flags + } + } + return true; + } + }; + + par.setInput(in, null); + ser.setOutput(out, null); + + while (par.nextToken() != XmlPullParser.END_DOCUMENT) { + ser.event(par); + } + ser.flush(); + } catch (XmlPullParserException ex) { + throw new AndrolibException("Could not decode XML", ex); + } catch (IOException ex) { + throw new AndrolibException("Could not decode XML", ex); + } + } + + public void decodeManifest(InputStream in, OutputStream out) + throws AndrolibException { + mOptimizeForManifest = true; + try { + decode(in, out); + } finally { + mOptimizeForManifest = false; + } + } + + private final XmlPullParser mParser; + private final ExtXmlSerializer mSerial; + + private boolean mOptimizeForManifest = false; + + private final static Logger LOGGER = + Logger.getLogger(XmlPullStreamDecoder.class.getName()); +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtFile.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtFile.java new file mode 100644 index 0000000..1d042b1 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtFile.java @@ -0,0 +1,63 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.util; + +import brut.directory.Directory; +import brut.directory.DirectoryException; +import brut.directory.FileDirectory; +import brut.directory.ZipRODirectory; +import java.io.File; +import java.net.URI; + +/** + * @author Ryszard Wiśniewski + */ +public class ExtFile extends File { + public ExtFile(File file) { + super(file.getPath()); + } + + public ExtFile(URI uri) { + super(uri); + } + + public ExtFile(File parent, String child) { + super(parent, child); + } + + public ExtFile(String parent, String child) { + super(parent, child); + } + + public ExtFile(String pathname) { + super(pathname); + } + + public Directory getDirectory() throws DirectoryException { + if (mDirectory == null) { + if (isDirectory()) { + mDirectory = new FileDirectory(this); + } else { + mDirectory = new ZipRODirectory(this); + } + } + return mDirectory; + } + + + private Directory mDirectory; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtMXSerializer.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtMXSerializer.java new file mode 100644 index 0000000..4fd001d --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtMXSerializer.java @@ -0,0 +1,79 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.util; + +import java.io.*; +import org.xmlpull.mxp1_serializer.MXSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public class ExtMXSerializer extends MXSerializer implements ExtXmlSerializer { + @Override + public void startDocument(String encoding, Boolean standalone) throws + IOException, IllegalArgumentException, IllegalStateException { + super.startDocument(encoding != null ? encoding : mDefaultEncoding, + standalone); + this.newLine(); + } + + @Override + protected void writeAttributeValue(String value, Writer out) + throws IOException { + if (mIsDisabledAttrEscape) { + out.write(value); + return; + } + super.writeAttributeValue(value, out); + } + + @Override + public void setOutput(OutputStream os, String encoding) throws IOException { + super.setOutput(os, encoding != null ? encoding : mDefaultEncoding); + } + + @Override + public Object getProperty(String name) throws IllegalArgumentException { + if (PROPERTY_DEFAULT_ENCODING.equals(name)) { + return mDefaultEncoding; + } + return super.getProperty(name); + } + + @Override + public void setProperty(String name, Object value) + throws IllegalArgumentException, IllegalStateException { + if (PROPERTY_DEFAULT_ENCODING.equals(name)) { + mDefaultEncoding = (String) value; + } else { + super.setProperty(name, value); + } + } + + public ExtXmlSerializer newLine() throws IOException { + super.out.write(lineSeparator); + return this; + } + + public void setDisabledAttrEscape(boolean disabled) { + mIsDisabledAttrEscape = disabled; + } + + private String mDefaultEncoding; + private boolean mIsDisabledAttrEscape = false; + +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtXmlSerializer.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtXmlSerializer.java new file mode 100644 index 0000000..3c8ffed --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtXmlSerializer.java @@ -0,0 +1,35 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.util; + +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public interface ExtXmlSerializer extends XmlSerializer { + + public ExtXmlSerializer newLine() throws IOException; + public void setDisabledAttrEscape(boolean disabled); + + public static final String PROPERTY_SERIALIZER_INDENTATION = + "http://xmlpull.org/v1/doc/properties.html#serializer-indentation"; + public static final String PROPERTY_SERIALIZER_LINE_SEPARATOR = + "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator"; + public static final String PROPERTY_DEFAULT_ENCODING = "DEFAULT_ENCODING"; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResValuesXmlSerializable.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResValuesXmlSerializable.java new file mode 100644 index 0000000..00d6a0c --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResValuesXmlSerializable.java @@ -0,0 +1,30 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.xml; + +import brut.androlib.AndrolibException; +import brut.androlib.res.data.ResResource; +import java.io.IOException; +import org.xmlpull.v1.XmlSerializer; + +/** + * @author Ryszard Wiśniewski + */ +public interface ResValuesXmlSerializable { + public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) + throws IOException, AndrolibException; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncodable.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncodable.java new file mode 100644 index 0000000..86d40a5 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncodable.java @@ -0,0 +1,27 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.xml; + +import brut.androlib.AndrolibException; + +/** + * @author Ryszard Wiśniewski + */ +public interface ResXmlEncodable { + public String encodeAsResXmlAttr() throws AndrolibException; + public String encodeAsResXmlValue() throws AndrolibException; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java new file mode 100644 index 0000000..46226e3 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java @@ -0,0 +1,202 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.xml; + +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Ryszard Wiśniewski + */ +public final class ResXmlEncoders { + + public static String escapeXmlChars(String str) { + return str.replace("&", "&").replace("<", "<"); + } + + public static String encodeAsResXmlAttr(String str) { + if (str.isEmpty()) { + return str; + } + + char[] chars = str.toCharArray(); + StringBuilder out = new StringBuilder(str.length() + 10); + + switch (chars[0]) { + case '#': + case '@': + case '?': + out.append('\\'); + } + + for (char c : chars) { + switch (c) { + case '\\': + out.append('\\'); + break; + case '"': + out.append("""); + continue; + case '\n': + out.append("\\n"); + continue; + default: + if (!isPrintableChar(c)) { + out.append(String.format("\\u%04x", (int) c)); + continue; + } + } + out.append(c); + } + + return out.toString(); + } + + public static String encodeAsXmlValue(String str) { + if (str.isEmpty()) { + return str; + } + + char[] chars = str.toCharArray(); + StringBuilder out = new StringBuilder(str.length() + 10); + + switch (chars[0]) { + case '#': + case '@': + case '?': + out.append('\\'); + } + + boolean isInStyleTag = false; + int startPos = 0; + boolean enclose = false; + boolean wasSpace = true; + for (char c : chars) { + if (isInStyleTag) { + if (c == '>') { + isInStyleTag = false; + startPos = out.length() + 1; + enclose = false; + } + } else if (c == ' ') { + if (wasSpace) { + enclose = true; + } + wasSpace = true; + } else { + wasSpace = false; + switch (c) { + case '\\': + out.append('\\'); + break; + case '\'': + case '\n': + enclose = true; + break; + case '"': + out.append('\\'); + break; + case '<': + isInStyleTag = true; + if (enclose) { + out.insert(startPos, '"').append('"'); + } + break; + default: + if (!isPrintableChar(c)) { + out.append(String.format("\\u%04x", (int) c)); + continue; + } + } + } + out.append(c); + } + + if (enclose || wasSpace) { + out.insert(startPos, '"').append('"'); + } + + return out.toString(); + } + + public static boolean hasMultipleNonPositionalSubstitutions(String str) { + return findNonPositionalSubstitutions(str, 2).size() > 1; + } + + public static String enumerateNonPositionalSubstitutions(String str) { + List subs = findNonPositionalSubstitutions(str, -1); + if (subs.size() < 2) { + return str; + } + + StringBuilder out = new StringBuilder(); + int pos = 0; + int count = 0; + for (Integer sub : subs) { + out.append(str.substring(pos, ++sub)).append(++count).append('$'); + pos = sub; + } + out.append(str.substring(pos)); + + return out.toString(); + } + + /** + * It searches for "%", but not "%%" nor "%(\d)+\$" + */ + private static List findNonPositionalSubstitutions(String str, + int max) { + int pos = 0; + int pos2 = 0; + int count = 0; + int length = str.length(); + List ret = new ArrayList(); + while((pos2 = (pos = str.indexOf('%', pos2)) + 1) != 0) { + if (pos2 == length) { + break; + } + char c = str.charAt(pos2++); + if (c == '%') { + continue; + } + if (c >= '0' && c <= '9' && pos2 < length) { + do { + c = str.charAt(pos2++); + } while (c >= '0' && c <= '9' && pos2 < length); + if (c == '$') { + continue; + } + } + + ret.add(pos); + if (max != -1 && ++count >= max) { + break; + } + } + + return ret; + } + + private static boolean isPrintableChar(char c) { + Character.UnicodeBlock block = Character.UnicodeBlock.of(c); + return !Character.isISOControl(c) + && c != KeyEvent.CHAR_UNDEFINED + && block != null + && block != Character.UnicodeBlock.SPECIALS; + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/DebugInjector.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/DebugInjector.java new file mode 100644 index 0000000..86c8936 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/DebugInjector.java @@ -0,0 +1,227 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.src; + +import brut.androlib.AndrolibException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.jf.dexlib.Code.Analysis.RegisterType; +import org.jf.dexlib.Code.Opcode; + +/** + * @author Ryszard Wiśniewski + */ +public class DebugInjector { + + public static void inject(ListIterator it, StringBuilder out) + throws AndrolibException { + new DebugInjector(it, out).inject(); + } + + private DebugInjector(ListIterator it, StringBuilder out) { + mIt = it; + mOut = out; + } + + private void inject() throws AndrolibException { + String definition = nextAndAppend(); + if ( + definition.contains(" abstract ") || + definition.contains(" native ") + ) { + nextAndAppend(); + return; + } + injectParameters(definition); + + boolean end = false; + while (!end) { + end = step(); + } + } + + private void injectParameters(String definition) throws AndrolibException { + int pos = definition.indexOf('('); + if (pos == -1) { + throw new AndrolibException(); + } + int pos2 = definition.indexOf(')', pos); + if (pos2 == -1) { + throw new AndrolibException(); + } + String params = definition.substring(pos + 1, pos2); + + int i = definition.contains(" static ") ? 0 : 1; + int argc = TypeName.listFromInternalName(params).size() + i; + while(i < argc) { + mOut.append(".parameter \"p").append(i).append("\"\n"); + i++; + } + } + + private boolean step() { + String line = next(); + if (line.isEmpty()) { + return false; + } + + switch (line.charAt(0)) { + case '#': + return processComment(line); + case ':': + append(line); + return false; + case '.': + return processDirective(line); + default: + return processInstruction(line); + } + } + + private boolean processComment(String line) { + if (mFirstInstruction) { + return false; + } + + Matcher m = REGISTER_INFO_PATTERN.matcher(line); + + while (m.find()) { + String localName = m.group(1); + String localType = null; + switch (RegisterType.Category.valueOf(m.group(2))) { + case Reference: + case Null: + case UninitRef: + case UninitThis: + localType = "Ljava/lang/Object;"; + break; + case Boolean: + localType = "Z"; + break; + case Integer: + case One: + case Unknown: + localType = "I"; + break; + case Uninit: + case Conflicted: + if (mInitializedRegisters.remove(localName)) { + mOut.append(".end local ").append(localName) + .append('\n'); + } + continue; + case Short: + case PosShort: + localType = "S"; + break; + case Byte: + case PosByte: + localType = "B"; + break; + case Char: + localType = "C"; + break; + case Float: + localType = "F"; + break; + case LongHi: + case LongLo: + localType = "J"; + break; + case DoubleHi: + case DoubleLo: + localType = "D"; + break; + default: + assert false; + } + + mInitializedRegisters.add(localName); + mOut.append(".local ").append(localName).append(", ") + .append(localName).append(':').append(localType).append('\n'); + } + + return false; + } + + private boolean processDirective(String line) { + String line2 = line.substring(1); + if ( + line2.startsWith("line ") || + line2.equals("prologue") || + line2.startsWith("parameter") || + line2.startsWith("local ") || + line2.startsWith("end local ") + ) { + return false; + } + + append(line); + if (line2.equals("end method")) { + return true; + } + if ( + line2.startsWith("annotation ") || + line2.equals("sparse-switch") || + line2.startsWith("packed-switch ") || + line2.startsWith("array-data ") + ) { + while(true) { + line2 = nextAndAppend(); + if (line2.startsWith(".end ")) { + break; + } + } + } + return false; + } + + private boolean processInstruction(String line) { + if (mFirstInstruction) { + mOut.append(".prologue\n"); + mFirstInstruction = false; + } + mOut.append(".line ").append(mIt.nextIndex()).append('\n') + .append(line).append('\n'); + + return false; + } + + private String next() { + return mIt.next().trim(); + } + + private String nextAndAppend() { + String line = next(); + append(line); + return line; + } + + private void append(String append) { + mOut.append(append).append('\n'); + } + + private final ListIterator mIt; + private final StringBuilder mOut; + + private boolean mFirstInstruction = true; + private final Set mInitializedRegisters = new HashSet(); + + private static final Pattern REGISTER_INFO_PATTERN = + Pattern.compile("((?:p|v)\\d+)=\\(([^)]+)\\);"); +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/DexFileBuilder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/DexFileBuilder.java new file mode 100644 index 0000000..f8bbf00 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/DexFileBuilder.java @@ -0,0 +1,85 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.src; + +import brut.androlib.AndrolibException; +import brut.androlib.mod.SmaliMod; +import java.io.*; +import org.antlr.runtime.RecognitionException; +import org.jf.dexlib.CodeItem; +import org.jf.dexlib.DexFile; +import org.jf.dexlib.Util.ByteArrayAnnotatedOutput; + +/** + * @author Ryszard Wiśniewski + */ +public class DexFileBuilder { + public void addSmaliFile(File smaliFile) throws AndrolibException { + try { + addSmaliFile(new FileInputStream(smaliFile), + smaliFile.getAbsolutePath()); + } catch (FileNotFoundException ex) { + throw new AndrolibException(ex); + } + } + + public void addSmaliFile(InputStream smaliStream, String name) + throws AndrolibException { + try { + if (! SmaliMod.assembleSmaliFile( + smaliStream, name, mDexFile, false, false, false)) { + throw new AndrolibException( + "Could not smali file: " + smaliStream); + } + } catch (IOException ex) { + throw new AndrolibException(ex); + } catch (RecognitionException ex) { + throw new AndrolibException(ex); + } + } + + public void writeTo(File dexFile) throws AndrolibException { + try { + OutputStream out = new FileOutputStream(dexFile); + out.write(getAsByteArray()); + out.close(); + } catch (IOException ex) { + throw new AndrolibException( + "Could not write dex to file: " + dexFile, ex); + } + } + + public byte[] getAsByteArray() { + mDexFile.place(); + for (CodeItem codeItem: mDexFile.CodeItemsSection.getItems()) { + codeItem.fixInstructions(true, true); + } + + mDexFile.place(); + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + mDexFile.writeTo(out); + byte[] bytes = out.toByteArray(); + + DexFile.calcSignature(bytes); + DexFile.calcChecksum(bytes); + + return bytes; + } + + private final DexFile mDexFile = new DexFile(); +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/SmaliBuilder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/SmaliBuilder.java new file mode 100644 index 0000000..8fd3280 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/SmaliBuilder.java @@ -0,0 +1,116 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.src; + +import brut.androlib.AndrolibException; +import brut.androlib.res.util.ExtFile; +import brut.directory.DirectoryException; +import java.io.*; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.logging.Logger; +import org.apache.commons.io.IOUtils; + +/** + * @author Ryszard Wiśniewski + */ +public class SmaliBuilder { + + public static void build(ExtFile smaliDir, File dexFile, + HashMap flags) + throws AndrolibException { + new SmaliBuilder(smaliDir, dexFile, flags).build(); + } + + private SmaliBuilder(ExtFile smaliDir, File dexFile, HashMap flags) { + mSmaliDir = smaliDir; + mDexFile = dexFile; + mFlags = flags; + } + + private void build() throws AndrolibException { + try { + mDexBuilder = new DexFileBuilder(); + for (String fileName : mSmaliDir.getDirectory().getFiles(true)) { + buildFile(fileName); + } + mDexBuilder.writeTo(mDexFile); + } catch (IOException ex) { + throw new AndrolibException(ex); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + private void buildFile(String fileName) throws AndrolibException, + IOException { + File inFile = new File(mSmaliDir, fileName); + InputStream inStream = new FileInputStream(inFile); + + if (fileName.endsWith(".smali")) { + mDexBuilder.addSmaliFile(inFile); + return; + } + if (! fileName.endsWith(".java")) { + LOGGER.warning("Unknown file type, ignoring: " + inFile); + return; + } + + StringBuilder out = new StringBuilder(); + List lines = IOUtils.readLines(inStream); + + if (!mFlags.containsKey("debug")) { + final String[] linesArray = lines.toArray(new String[0]); + for (int i = 2; i < linesArray.length - 2; i++) { + out.append(linesArray[i]).append('\n'); + } + } else { + lines.remove(lines.size() - 1); + lines.remove(lines.size() - 1); + ListIterator it = lines.listIterator(2); + + out.append(".source \"").append(inFile.getName()).append("\"\n"); + while (it.hasNext()) { + String line = it.next().trim(); + if (line.isEmpty() || line.charAt(0) == '#' || + line.startsWith(".source")) { + continue; + } + if (line.startsWith(".method ")) { + it.previous(); + DebugInjector.inject(it, out); + continue; + } + + out.append(line).append('\n'); + } + } + mDexBuilder.addSmaliFile( + IOUtils.toInputStream(out.toString()), fileName); + } + + private final ExtFile mSmaliDir; + private final File mDexFile; + private final HashMap mFlags; + + private DexFileBuilder mDexBuilder; + + + private final static Logger LOGGER = + Logger.getLogger(SmaliBuilder.class.getName()); +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/SmaliDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/SmaliDecoder.java new file mode 100644 index 0000000..134fb88 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/SmaliDecoder.java @@ -0,0 +1,62 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.src; + +import brut.androlib.AndrolibException; +import java.io.File; +import java.io.IOException; +import org.jf.baksmali.baksmali; +import org.jf.baksmali.main; +import org.jf.dexlib.Code.Analysis.ClassPath; +import org.jf.dexlib.DexFile; + +/** + * @author Ryszard Wiśniewski + */ +public class SmaliDecoder { + + public static void decode(File apkFile, File outDir, boolean debug, boolean bakdeb) + throws AndrolibException { + new SmaliDecoder(apkFile, outDir, debug, bakdeb).decode(); + } + + private SmaliDecoder(File apkFile, File outDir, boolean debug, boolean bakdeb) { + mApkFile = apkFile; + mOutDir = outDir; + mDebug = debug; + mBakDeb = bakdeb; + } + + private void decode() throws AndrolibException { + if (mDebug) { + ClassPath.dontLoadClassPath = true; + } + try { + baksmali.disassembleDexFile(mApkFile.getAbsolutePath(), + new DexFile(mApkFile), false, mOutDir.getAbsolutePath(), null, + null, null, false, true, true, mBakDeb, false, false, + mDebug ? main.DIFFPRE: 0, false, false, null); + } catch (IOException ex) { + throw new AndrolibException(ex); + } + } + + private final File mApkFile; + private final File mOutDir; + private final boolean mDebug; + private final boolean mBakDeb; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/TypeName.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/TypeName.java new file mode 100644 index 0000000..ffc7bb1 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/src/TypeName.java @@ -0,0 +1,209 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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.src; + +import brut.androlib.AndrolibException; +import brut.util.Duo; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Ryszard Wiśniewski + */ +public class TypeName { + public final String package_; + public final String type; + public final String innerType; + public final int array; + + public TypeName(String type, int array) { + this(null, type, null, array); + } + + public TypeName(String package_, String type, String innerType, int array) { + this.package_ = package_; + this.type = type; + this.innerType = innerType; + this.array = array; + } + + public String getShortenedName() { + return getName("java.lang".equals(package_), isFileOwner()); + } + + public String getName() { + return getName(false, false); + } + + public String getName(boolean excludePackage, boolean separateInner) { + String name = + (package_ == null || excludePackage ? "" : package_ + '.') + + type + + (innerType != null ? (separateInner ? '$' : '.') + innerType : ""); + for (int i = 0; i < array; i++) { + name += "[]"; + } + return name; + } + + public String getJavaFilePath() { + return getFilePath(isFileOwner()) + ".java"; + } + + public String getSmaliFilePath() { + return getFilePath(true) + ".smali"; + } + + public String getFilePath(boolean separateInner) { + return package_.replace('.', File.separatorChar) + File.separatorChar + + type + (separateInner && isInner() ? "$" + innerType : ""); + } + + public boolean isInner() { + return innerType != null; + } + + public boolean isArray() { + return array != 0; + } + + public boolean isFileOwner() { + if (mIsFileOwner == null) { + mIsFileOwner = true; + if (isInner()) { + char c = innerType.charAt(0); + if (c < '0' || c > '9') { + mIsFileOwner = false; + } + } + } + return mIsFileOwner; + } + + @Override + public String toString() { + return getName(); + } + + public static TypeName fromInternalName(String internal) + throws AndrolibException { + Duo duo = fetchFromInternalName(internal); + if (duo.m2 != internal.length()) { + throw new AndrolibException( + "Invalid internal name: " + internal); + } + return duo.m1; + } + + public static List listFromInternalName(String internal) + throws AndrolibException { + List types = new ArrayList(); + while (! internal.isEmpty()) { + Duo duo = fetchFromInternalName(internal); + types.add(duo.m1); + internal = internal.substring(duo.m2); + } + return types; + } + + public static Duo fetchFromInternalName(String internal) + throws AndrolibException { + String origInternal = internal; + int array = 0; + + boolean isArray = false; + do { + if (internal.isEmpty()) { + throw new AndrolibException( + "Invalid internal name: " + origInternal); + } + isArray = internal.charAt(0) == '['; + if (isArray) { + array++; + internal = internal.substring(1); + } + } while (isArray); + + int length = array + 1; + String package_ = null; + String type = null; + String innerType = null; + switch (internal.charAt(0)) { + case 'B': + type = "byte"; + break; + case 'C': + type = "char"; + break; + case 'D': + type = "double"; + break; + case 'F': + type = "float"; + break; + case 'I': + type = "int"; + break; + case 'J': + type = "long"; + break; + case 'S': + type = "short"; + break; + case 'Z': + type = "boolean"; + break; + case 'V': + type = "void"; + break; + case 'L': + int pos = internal.indexOf(';'); + if (pos == -1) { + throw new AndrolibException( + "Invalid internal name: " + origInternal); + } + length += pos; + internal = internal.substring(1, pos); + + pos = internal.lastIndexOf('/'); + if (pos == -1) { + package_ = ""; + type = internal; + } else { + package_ = internal.substring(0, pos).replace('/', '.'); + type = internal.substring(pos + 1); + } + + pos = type.indexOf('$'); + if (pos != -1) { + innerType = type.substring(pos + 1); + type = type.substring(0, pos); + } + break; + default: + throw new AndrolibException( + "Invalid internal name: " + origInternal); + } + + return new Duo( + new TypeName(package_, type, innerType, array), length); + } + + + private Boolean mIsFileOwner; +} diff --git a/brut.apktool/apktool-lib/src/main/java/com/mindprod/ledatastream/LEDataInputStream.java b/brut.apktool/apktool-lib/src/main/java/com/mindprod/ledatastream/LEDataInputStream.java new file mode 100644 index 0000000..8365634 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/com/mindprod/ledatastream/LEDataInputStream.java @@ -0,0 +1,319 @@ +/* + * @(#)LEDataInputStream.java + * + * Summary: Little-Endian version of DataInputStream. + * + * Copyright: (c) 1998-2010 Roedy Green, Canadian Mind Products, http://mindprod.com + * + * Licence: This software may be copied and used freely for any purpose but military. + * http://mindprod.com/contact/nonmil.html + * + * Requires: JDK 1.1+ + * + * Created with: IntelliJ IDEA IDE. + * + * Version History: + * 1.8 2007-05-24 + */ +package com.mindprod.ledatastream; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Little-Endian version of DataInputStream. + *

+ * Very similar to DataInputStream except it reads + * little-endian instead of big-endian binary data. We can't extend + * DataInputStream directly since it has only final methods, though + * DataInputStream itself is not final. This forces us implement + * LEDataInputStream with a DataInputStream object, and use wrapper methods. + * + * @author Roedy Green, Canadian Mind Products + * @version 1.8 2007-05-24 + * @since 1998 + */ +public final class LEDataInputStream implements DataInput + { + // ------------------------------ CONSTANTS ------------------------------ + + /** + * undisplayed copyright notice. + * + * @noinspection UnusedDeclaration + */ + private static final String EMBEDDED_COPYRIGHT = + "copyright (c) 1999-2010 Roedy Green, Canadian Mind Products, http://mindprod.com"; + + // ------------------------------ FIELDS ------------------------------ + + /** + * to get at the big-Endian methods of a basic DataInputStream + * + * @noinspection WeakerAccess + */ + protected final DataInputStream dis; + + /** + * to get at the a basic readBytes method. + * + * @noinspection WeakerAccess + */ + protected final InputStream is; + + /** + * work array for buffering input. + * + * @noinspection WeakerAccess + */ + protected final byte[] work; + // -------------------------- PUBLIC STATIC METHODS -------------------------- + + /** + * Note. This is a STATIC method! + * + * @param in stream to read UTF chars from (endian irrelevant) + * + * @return string from stream + * @throws IOException if read fails. + */ + public static String readUTF( DataInput in ) throws IOException + { + return DataInputStream.readUTF( in ); + } + + // -------------------------- PUBLIC INSTANCE METHODS -------------------------- + + /** + * constructor. + * + * @param in binary inputstream of little-endian data. + */ + public LEDataInputStream( InputStream in ) + { + this.is = in; + this.dis = new DataInputStream( in ); + work = new byte[8]; + } + + /** + * close. + * + * @throws IOException if close fails. + */ + public final void close() throws IOException + { + dis.close(); + } + + /** + * Read bytes. Watch out, read may return fewer bytes than requested. + * + * @param ba where the bytes go. + * @param off offset in buffer, not offset in file. + * @param len count of bytes to read. + * + * @return how many bytes read. + * @throws IOException if read fails. + */ + public final int read( byte ba[], int off, int len ) throws IOException + { + // For efficiency, we avoid one layer of wrapper + return is.read( ba, off, len ); + } + + /** + * read only a one-byte boolean. + * + * @return true or false. + * @throws IOException if read fails. + * @see java.io.DataInput#readBoolean() + */ + public final boolean readBoolean() throws IOException + { + return dis.readBoolean(); + } + + /** + * read byte. + * + * @return the byte read. + * @throws IOException if read fails. + * @see java.io.DataInput#readByte() + */ + public final byte readByte() throws IOException + { + return dis.readByte(); + } + + /** + * Read on char. like DataInputStream.readChar except little endian. + * + * @return little endian 16-bit unicode char from the stream. + * @throws IOException if read fails. + */ + public final char readChar() throws IOException + { + dis.readFully( work, 0, 2 ); + return ( char ) ( ( work[ 1 ] & 0xff ) << 8 | ( work[ 0 ] & 0xff ) ); + } + + /** + * Read a double. like DataInputStream.readDouble except little endian. + * + * @return little endian IEEE double from the datastream. + * @throws IOException + */ + public final double readDouble() throws IOException + { + return Double.longBitsToDouble( readLong() ); + } + + /** + * Read one float. Like DataInputStream.readFloat except little endian. + * + * @return little endian IEEE float from the datastream. + * @throws IOException if read fails. + */ + public final float readFloat() throws IOException + { + return Float.intBitsToFloat( readInt() ); + } + + /** + * Read bytes until the array is filled. + * + * @see java.io.DataInput#readFully(byte[]) + */ + public final void readFully( byte ba[] ) throws IOException + { + dis.readFully( ba, 0, ba.length ); + } + + /** + * Read bytes until the count is satisfied. + * + * @throws IOException if read fails. + * @see java.io.DataInput#readFully(byte[],int,int) + */ + public final void readFully( byte ba[], + int off, + int len ) throws IOException + { + dis.readFully( ba, off, len ); + } + + /** + * Read an int, 32-bits. Like DataInputStream.readInt except little endian. + * + * @return little-endian binary int from the datastream + * @throws IOException if read fails. + */ + public final int readInt() throws IOException + { + dis.readFully( work, 0, 4 ); + return ( work[ 3 ] ) << 24 + | ( work[ 2 ] & 0xff ) << 16 + | ( work[ 1 ] & 0xff ) << 8 + | ( work[ 0 ] & 0xff ); + } + + /** + * Read a line. + * + * @return a rough approximation of the 8-bit stream as a 16-bit unicode string + * @throws IOException + * @noinspection deprecation + * @deprecated This method does not properly convert bytes to characters. Use a Reader instead with a little-endian + * encoding. + */ + public final String readLine() throws IOException + { + return dis.readLine(); + } + + /** + * read a long, 64-bits. Like DataInputStream.readLong except little endian. + * + * @return little-endian binary long from the datastream. + * @throws IOException + */ + public final long readLong() throws IOException + { + dis.readFully( work, 0, 8 ); + return ( long ) ( work[ 7 ] ) << 56 + | + /* long cast needed or shift done modulo 32 */ + ( long ) ( work[ 6 ] & 0xff ) << 48 + | ( long ) ( work[ 5 ] & 0xff ) << 40 + | ( long ) ( work[ 4 ] & 0xff ) << 32 + | ( long ) ( work[ 3 ] & 0xff ) << 24 + | ( long ) ( work[ 2 ] & 0xff ) << 16 + | ( long ) ( work[ 1 ] & 0xff ) << 8 + | ( long ) ( work[ 0 ] & 0xff ); + } + + /** + * Read short, 16-bits. Like DataInputStream.readShort except little endian. + * + * @return little endian binary short from stream. + * @throws IOException if read fails. + */ + public final short readShort() throws IOException + { + dis.readFully( work, 0, 2 ); + return ( short ) ( ( work[ 1 ] & 0xff ) << 8 | ( work[ 0 ] & 0xff ) ); + } + + /** + * Read UTF counted string. + * + * @return String read. + */ + public final String readUTF() throws IOException + { + return dis.readUTF(); + } + + /** + * Read an unsigned byte. Note: returns an int, even though says Byte (non-Javadoc) + * + * @throws IOException if read fails. + * @see java.io.DataInput#readUnsignedByte() + */ + public final int readUnsignedByte() throws IOException + { + return dis.readUnsignedByte(); + } + + /** + * Read an unsigned short, 16 bits. Like DataInputStream.readUnsignedShort except little endian. Note, returns int + * even though it reads a short. + * + * @return little-endian int from the stream. + * @throws IOException if read fails. + */ + public final int readUnsignedShort() throws IOException + { + dis.readFully( work, 0, 2 ); + return ( ( work[ 1 ] & 0xff ) << 8 | ( work[ 0 ] & 0xff ) ); + } + + /** + * Skip over bytes in the stream. See the general contract of the skipBytes method of + * DataInput. + *

+ * Bytes for this operation are read from the contained input stream. + * + * @param n the number of bytes to be skipped. + * + * @return the actual number of bytes skipped. + * @throws IOException if an I/O error occurs. + */ + public final int skipBytes( int n ) throws IOException + { + return dis.skipBytes( n ); + } + } \ No newline at end of file diff --git a/brut.apktool/apktool-lib/src/main/java/org/xmlpull/mxp1_serializer/MXSerializer.java b/brut.apktool/apktool-lib/src/main/java/org/xmlpull/mxp1_serializer/MXSerializer.java new file mode 100644 index 0000000..7a4f881 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/org/xmlpull/mxp1_serializer/MXSerializer.java @@ -0,0 +1,1144 @@ +package org.xmlpull.mxp1_serializer; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import org.xmlpull.v1.XmlSerializer; + +/** + * Implementation of XmlSerializer interface from XmlPull V1 API. + * This implementation is optimzied for performance and low memory footprint. + * + *

Implemented features:

    + *
  • FEATURE_NAMES_INTERNED - when enabled all returned names + * (namespaces, prefixes) will be interned and it is required that + * all names passed as arguments MUST be interned + *
  • FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE + *
+ *

Implemented properties:

    + *
  • PROPERTY_SERIALIZER_INDENTATION + *
  • PROPERTY_SERIALIZER_LINE_SEPARATOR + *
+ * + */ +public class MXSerializer implements XmlSerializer { + protected final static String XML_URI = "http://www.w3.org/XML/1998/namespace"; + protected final static String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; + private static final boolean TRACE_SIZING = false; + private static final boolean TRACE_ESCAPING = false; + + protected final String FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE = + "http://xmlpull.org/v1/doc/features.html#serializer-attvalue-use-apostrophe"; + protected final String FEATURE_NAMES_INTERNED = + "http://xmlpull.org/v1/doc/features.html#names-interned"; + protected final String PROPERTY_SERIALIZER_INDENTATION = + "http://xmlpull.org/v1/doc/properties.html#serializer-indentation"; + protected final String PROPERTY_SERIALIZER_LINE_SEPARATOR = + "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator"; + protected final static String PROPERTY_LOCATION = + "http://xmlpull.org/v1/doc/properties.html#location"; + + // properties/features + protected boolean namesInterned; + protected boolean attributeUseApostrophe; + protected String indentationString = null; //" "; + protected String lineSeparator = "\n"; + + protected String location; + protected Writer out; + + protected int autoDeclaredPrefixes; + + protected int depth = 0; + + // element stack + protected String elNamespace[] = new String[ 2 ]; + protected String elName[] = new String[ elNamespace.length ]; + protected String elPrefix[] = new String[ elNamespace.length ]; + protected int elNamespaceCount[] = new int[ elNamespace.length ]; + + //namespace stack + protected int namespaceEnd = 0; + protected String namespacePrefix[] = new String[ 8 ]; + protected String namespaceUri[] = new String[ namespacePrefix.length ]; + + protected boolean finished; + protected boolean pastRoot; + protected boolean setPrefixCalled; + protected boolean startTagIncomplete; + + protected boolean doIndent; + protected boolean seenTag; + + protected boolean seenBracket; + protected boolean seenBracketBracket; + + // buffer output if neede to write escaped String see text(String) + private static final int BUF_LEN = Runtime.getRuntime().freeMemory() > 1000000L ? 8*1024 : 256; + protected char buf[] = new char[ BUF_LEN ]; + + + protected static final String precomputedPrefixes[]; + + static { + precomputedPrefixes = new String[32]; //arbitrary number ... + for (int i = 0; i < precomputedPrefixes.length; i++) + { + precomputedPrefixes[i] = ("n"+i).intern(); + } + } + + private boolean checkNamesInterned = false; + + private void checkInterning(String name) { + if(namesInterned && name != name.intern()) { + throw new IllegalArgumentException( + "all names passed as arguments must be interned" + +"when NAMES INTERNED feature is enabled"); + } + } + + protected void reset() { + location = null; + out = null; + autoDeclaredPrefixes = 0; + depth = 0; + + // nullify references on all levels to allow it to be GCed + for (int i = 0; i < elNamespaceCount.length; i++) + { + elName[ i ] = null; + elPrefix[ i ] = null; + elNamespace[ i ] = null; + elNamespaceCount[ i ] = 2; + } + + + namespaceEnd = 0; + + + //NOTE: no need to intern() as all literal strings and string-valued constant expressions + //are interned. String literals are defined in 3.10.5 of the Java Language Specification + // just checking ... + //assert "xmlns" == "xmlns".intern(); + //assert XMLNS_URI == XMLNS_URI.intern(); + + //TODO: how to prevent from reporting this namespace? + // this is special namespace declared for consistensy with XML infoset + namespacePrefix[ namespaceEnd ] = "xmlns"; + namespaceUri[ namespaceEnd ] = XMLNS_URI; + ++namespaceEnd; + + namespacePrefix[ namespaceEnd ] = "xml"; + namespaceUri[ namespaceEnd ] = XML_URI; + ++namespaceEnd; + + finished = false; + pastRoot = false; + setPrefixCalled = false; + startTagIncomplete = false; + //doIntent is not changed + seenTag = false; + + seenBracket = false; + seenBracketBracket = false; + } + + + protected void ensureElementsCapacity() { + final int elStackSize = elName.length; + //assert (depth + 1) >= elName.length; + // we add at least one extra slot ... + final int newSize = (depth >= 7 ? 2 * depth : 8) + 2; // = lucky 7 + 1 //25 + if(TRACE_SIZING) { + System.err.println( + getClass().getName()+" elStackSize "+elStackSize+" ==> "+newSize); + } + final boolean needsCopying = elStackSize > 0; + String[] arr = null; + // reuse arr local variable slot + arr = new String[newSize]; + if(needsCopying) System.arraycopy(elName, 0, arr, 0, elStackSize); + elName = arr; + + arr = new String[newSize]; + if(needsCopying) System.arraycopy(elPrefix, 0, arr, 0, elStackSize); + elPrefix = arr; + + arr = new String[newSize]; + if(needsCopying) System.arraycopy(elNamespace, 0, arr, 0, elStackSize); + elNamespace = arr; + + final int[] iarr = new int[newSize]; + if(needsCopying) { + System.arraycopy(elNamespaceCount, 0, iarr, 0, elStackSize); + } else { + // special initialization + iarr[0] = 0; + } + elNamespaceCount = iarr; + } + + protected void ensureNamespacesCapacity() { //int size) { + //int namespaceSize = namespacePrefix != null ? namespacePrefix.length : 0; + //assert (namespaceEnd >= namespacePrefix.length); + + //if(size >= namespaceSize) { + //int newSize = size > 7 ? 2 * size : 8; // = lucky 7 + 1 //25 + final int newSize = namespaceEnd > 7 ? 2 * namespaceEnd : 8; + if(TRACE_SIZING) { + System.err.println( + getClass().getName()+" namespaceSize "+namespacePrefix.length+" ==> "+newSize); + } + final String[] newNamespacePrefix = new String[newSize]; + final String[] newNamespaceUri = new String[newSize]; + if(namespacePrefix != null) { + System.arraycopy( + namespacePrefix, 0, newNamespacePrefix, 0, namespaceEnd); + System.arraycopy( + namespaceUri, 0, newNamespaceUri, 0, namespaceEnd); + } + namespacePrefix = newNamespacePrefix; + namespaceUri = newNamespaceUri; + + // TODO use hashes for quick namespace->prefix lookups + // if( ! allStringsInterned ) { + // int[] newNamespacePrefixHash = new int[newSize]; + // if(namespacePrefixHash != null) { + // System.arraycopy( + // namespacePrefixHash, 0, newNamespacePrefixHash, 0, namespaceEnd); + // } + // namespacePrefixHash = newNamespacePrefixHash; + // } + //prefixesSize = newSize; + // ////assert nsPrefixes.length > size && nsPrefixes.length == newSize + //} + } + + + public void setFeature(String name, + boolean state) throws IllegalArgumentException, IllegalStateException + { + if(name == null) { + throw new IllegalArgumentException("feature name can not be null"); + } + if(FEATURE_NAMES_INTERNED.equals(name)) { + namesInterned = state; + } else if(FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE.equals(name)) { + attributeUseApostrophe = state; + } else { + throw new IllegalStateException("unsupported feature "+name); + } + } + + public boolean getFeature(String name) throws IllegalArgumentException + { + if(name == null) { + throw new IllegalArgumentException("feature name can not be null"); + } + if(FEATURE_NAMES_INTERNED.equals(name)) { + return namesInterned; + } else if(FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE.equals(name)) { + return attributeUseApostrophe; + } else { + return false; + } + } + + // precomputed variables to simplify writing indentation + protected int offsetNewLine; + protected int indentationJump; + protected char[] indentationBuf; + protected int maxIndentLevel; + protected boolean writeLineSepartor; //should end-of-line be written + protected boolean writeIndentation; // is indentation used? + + /** + * For maximum efficiency when writing indents the required output is pre-computed + * This is internal function that recomputes buffer after user requested chnages. + */ + protected void rebuildIndentationBuf() { + if(doIndent == false) return; + final int maxIndent = 65; //hardcoded maximum indentation size in characters + int bufSize = 0; + offsetNewLine = 0; + if(writeLineSepartor) { + offsetNewLine = lineSeparator.length(); + bufSize += offsetNewLine; + } + maxIndentLevel = 0; + if(writeIndentation) { + indentationJump = indentationString.length(); + maxIndentLevel = maxIndent / indentationJump; + bufSize += maxIndentLevel * indentationJump; + } + if(indentationBuf == null || indentationBuf.length < bufSize) { + indentationBuf = new char[bufSize + 8]; + } + int bufPos = 0; + if(writeLineSepartor) { + for (int i = 0; i < lineSeparator.length(); i++) + { + indentationBuf[ bufPos++ ] = lineSeparator.charAt(i); + } + } + if(writeIndentation) { + for (int i = 0; i < maxIndentLevel; i++) + { + for (int j = 0; j < indentationString.length(); j++) + { + indentationBuf[ bufPos++ ] = indentationString.charAt(j); + } + } + } + } + + // if(doIndent) writeIndent(); + protected void writeIndent() throws IOException { + final int start = writeLineSepartor ? 0 : offsetNewLine; + final int level = (depth > maxIndentLevel) ? maxIndentLevel : depth; + out.write( indentationBuf, start, ( (level - 1) * indentationJump) + offsetNewLine); + } + + public void setProperty(String name, + Object value) throws IllegalArgumentException, IllegalStateException + { + if(name == null) { + throw new IllegalArgumentException("property name can not be null"); + } + if(PROPERTY_SERIALIZER_INDENTATION.equals(name)) { + indentationString = (String)value; + } else if(PROPERTY_SERIALIZER_LINE_SEPARATOR.equals(name)) { + lineSeparator = (String)value; + } else if(PROPERTY_LOCATION.equals(name)) { + location = (String) value; + } else { + throw new IllegalStateException("unsupported property "+name); + } + writeLineSepartor = lineSeparator != null && lineSeparator.length() > 0; + writeIndentation = indentationString != null && indentationString.length() > 0; + // optimize - do not write when nothing to write ... + doIndent = indentationString != null && (writeLineSepartor || writeIndentation); + //NOTE: when indentationString == null there is no indentation + // (even though writeLineSeparator may be true ...) + rebuildIndentationBuf(); + seenTag = false; // for consistency + } + + public Object getProperty(String name) throws IllegalArgumentException + { + if(name == null) { + throw new IllegalArgumentException("property name can not be null"); + } + if(PROPERTY_SERIALIZER_INDENTATION.equals(name)) { + return indentationString; + } else if(PROPERTY_SERIALIZER_LINE_SEPARATOR.equals(name)) { + return lineSeparator; + } else if(PROPERTY_LOCATION.equals(name)) { + return location; + } else { + return null; + } + } + + private String getLocation() { + return location != null ? " @"+location : ""; + } + + // this is special method that can be accessed directly to retrieve Writer serializer is using + public Writer getWriter() + { + return out; + } + + public void setOutput(Writer writer) + { + reset(); + out = writer; + } + + public void setOutput(OutputStream os, String encoding) throws IOException + { + if(os == null) throw new IllegalArgumentException("output stream can not be null"); + reset(); + if(encoding != null) { + out = new OutputStreamWriter(os, encoding); + } else { + out = new OutputStreamWriter(os); + } + } + + public void startDocument (String encoding, Boolean standalone) throws IOException + { + char apos = attributeUseApostrophe ? '\'' : '"'; + if(attributeUseApostrophe) { + out.write(""); + } + + public void endDocument() throws IOException + { + // close all unclosed tag; + while(depth > 0) { + endTag(elNamespace[ depth ], elName[ depth ]); + } + //assert depth == 0; + //assert startTagIncomplete == false; + finished = pastRoot = startTagIncomplete = true; + out.flush(); + } + + public void setPrefix(String prefix, String namespace) throws IOException + { + if(startTagIncomplete) closeStartTag(); + //assert prefix != null; + //assert namespace != null; + if (prefix == null) { + prefix = ""; + } + if(!namesInterned) { + prefix = prefix.intern(); //will throw NPE if prefix==null + } else if(checkNamesInterned) { + checkInterning(prefix); + } else if(prefix == null) { + throw new IllegalArgumentException("prefix must be not null"+getLocation()); + } + + //check that prefix is not duplicated ... + for (int i = elNamespaceCount[ depth ]; i < namespaceEnd; i++) + { + if(prefix == namespacePrefix[ i ]) { + throw new IllegalStateException("duplicated prefix "+printable(prefix)+getLocation()); + } + } + + if(!namesInterned) { + namespace = namespace.intern(); + } else if(checkNamesInterned) { + checkInterning(namespace); + } else if(namespace == null) { + throw new IllegalArgumentException("namespace must be not null"+getLocation()); + } + + if(namespaceEnd >= namespacePrefix.length) { + ensureNamespacesCapacity(); + } + namespacePrefix[ namespaceEnd ] = prefix; + namespaceUri[ namespaceEnd ] = namespace; + ++namespaceEnd; + setPrefixCalled = true; + } + + protected String lookupOrDeclarePrefix( String namespace ) { + return getPrefix(namespace, true); + } + + public String getPrefix(String namespace, boolean generatePrefix) + { + return getPrefix(namespace, generatePrefix, false); + } + + protected String getPrefix(String namespace, boolean generatePrefix, boolean nonEmpty) + { + //assert namespace != null; + if(!namesInterned) { + // when String is interned we can do much faster namespace stack lookups ... + namespace = namespace.intern(); + } else if(checkNamesInterned) { + checkInterning(namespace); + //assert namespace != namespace.intern(); + } + if(namespace == null) { + throw new IllegalArgumentException("namespace must be not null"+getLocation()); + } else if(namespace.length() == 0) { + throw new IllegalArgumentException("default namespace cannot have prefix"+getLocation()); + } + + // first check if namespace is already in scope + for (int i = namespaceEnd - 1; i >= 0 ; --i) + { + if(namespace == namespaceUri[ i ]) { + final String prefix = namespacePrefix[ i ]; + if(nonEmpty && prefix.length() == 0) continue; + // now check that prefix is still in scope + for (int p = namespaceEnd - 1; p > i ; --p) + { + if(prefix == namespacePrefix[ p ]) + continue; // too bad - prefix is redeclared with different namespace + } + return prefix; + } + } + + // so not found it ... + if(!generatePrefix) { + return null; + } + return generatePrefix(namespace); + } + + private String generatePrefix(String namespace) { + //assert namespace == namespace.intern(); + while(true) { + ++autoDeclaredPrefixes; + //fast lookup uses table that was pre-initialized in static{} .... + final String prefix = autoDeclaredPrefixes < precomputedPrefixes.length + ? precomputedPrefixes[autoDeclaredPrefixes] : ("n"+autoDeclaredPrefixes).intern(); + // make sure this prefix is not declared in any scope (avoid hiding in-scope prefixes)! + for (int i = namespaceEnd - 1; i >= 0 ; --i) + { + if(prefix == namespacePrefix[ i ]) { + continue; // prefix is already declared - generate new and try again + } + } + // declare prefix + + if(namespaceEnd >= namespacePrefix.length) { + ensureNamespacesCapacity(); + } + namespacePrefix[ namespaceEnd ] = prefix; + namespaceUri[ namespaceEnd ] = namespace; + ++namespaceEnd; + + return prefix; + } + } + + public int getDepth() + { + return depth; + } + + public String getNamespace () + { + return elNamespace[depth]; + } + + public String getName() + { + return elName[depth]; + } + + public XmlSerializer startTag (String namespace, String name) throws IOException + { + if(startTagIncomplete) { + closeStartTag(); + } + seenBracket = seenBracketBracket = false; + ++depth; + if(doIndent && depth > 0 && seenTag) { + writeIndent(); + } + seenTag = true; + setPrefixCalled = false; + startTagIncomplete = true; + if( (depth + 1) >= elName.length) { + ensureElementsCapacity(); + } + ////assert namespace != null; + + if(checkNamesInterned && namesInterned) checkInterning(namespace); + elNamespace[ depth ] = (namesInterned || namespace == null) ? namespace : namespace.intern(); + //assert name != null; + //elName[ depth ] = name; + if(checkNamesInterned && namesInterned) checkInterning(name); + elName[ depth ] = (namesInterned || name == null) ? name : name.intern(); + if(out == null) { + throw new IllegalStateException("setOutput() must called set before serialization can start"); + } + out.write('<'); + if(namespace != null) { + if(namespace.length() > 0) { + //ALEK: in future make this algo a feature on serializer + String prefix = null; + if(depth > 0 && (namespaceEnd - elNamespaceCount[depth-1]) == 1) { + // if only one prefix was declared un-declare it if the prefix is already declared on parent el with the same URI + String uri = namespaceUri[namespaceEnd-1]; + if(uri == namespace || uri.equals(namespace)) { + String elPfx = namespacePrefix[namespaceEnd-1]; + // 2 == to skip predefined namesapces (xml and xmlns ...) + for(int pos = elNamespaceCount[depth-1] - 1; pos >= 2; --pos ) { + String pf = namespacePrefix[pos]; + if(pf == elPfx || pf.equals(elPfx)) { + String n = namespaceUri[pos]; + if(n == uri || n.equals(uri)) { + --namespaceEnd; //un-declare namespace: this is kludge! + prefix = elPfx; + } + break; + } + } + } + } + if(prefix == null) { + prefix = lookupOrDeclarePrefix( namespace ); + } + //assert prefix != null; + // make sure that default ("") namespace to not print ":" + if(prefix.length() > 0) { + elPrefix[ depth ] = prefix; + out.write(prefix); + out.write(':'); + } else { + elPrefix[ depth ] = ""; + } + } else { + // make sure that default namespace can be declared + for (int i = namespaceEnd - 1; i >= 0 ; --i) { + if(namespacePrefix[ i ] == "") { + final String uri = namespaceUri[ i ]; + if(uri == null) { + // declare default namespace + setPrefix("", ""); + } else if(uri.length() > 0) { + throw new IllegalStateException( + "start tag can not be written in empty default namespace "+ + "as default namespace is currently bound to '"+uri+"'"+getLocation()); + } + break; + } + } + elPrefix[ depth ] = ""; + } + } else { + elPrefix[ depth ] = ""; + } + out.write(name); + return this; + } + + public XmlSerializer attribute (String namespace, String name, + String value) throws IOException + { + if(!startTagIncomplete) { + throw new IllegalArgumentException("startTag() must be called before attribute()"+getLocation()); + } + //assert setPrefixCalled == false; + out.write(' '); + ////assert namespace != null; + if(namespace != null && namespace.length() > 0) { + //namespace = namespace.intern(); + if(!namesInterned) { + namespace = namespace.intern(); + } else if(checkNamesInterned) { + checkInterning(namespace); + } + String prefix = getPrefix( namespace, false, true ); + //assert( prefix != null); + //if(prefix.length() == 0) { + if(prefix == null) { + // needs to declare prefix to hold default namespace + //NOTE: attributes such as a='b' are in NO namespace + prefix = generatePrefix(namespace); + } + out.write(prefix); + out.write(':'); + // if(prefix.length() > 0) { + // out.write(prefix); + // out.write(':'); + // } + } + //assert name != null; + out.write(name); + out.write('='); + //assert value != null; + out.write( attributeUseApostrophe ? '\'' : '"'); + writeAttributeValue(value, out); + out.write( attributeUseApostrophe ? '\'' : '"'); + return this; + } + + protected void closeStartTag() throws IOException { + if(finished) { + throw new IllegalArgumentException("trying to write past already finished output"+getLocation()); + } + if(seenBracket) { + seenBracket = seenBracketBracket = false; + } + if( startTagIncomplete || setPrefixCalled ) { + if(setPrefixCalled) { + throw new IllegalArgumentException( + "startTag() must be called immediately after setPrefix()"+getLocation()); + } + if(!startTagIncomplete) { + throw new IllegalArgumentException("trying to close start tag that is not opened"+getLocation()); + } + + // write all namespace delcarations! + writeNamespaceDeclarations(); + out.write('>'); + elNamespaceCount[ depth ] = namespaceEnd; + startTagIncomplete = false; + } + } + + protected void writeNamespaceDeclarations() throws IOException + { + //int start = elNamespaceCount[ depth - 1 ]; + for (int i = elNamespaceCount[ depth - 1 ]; i < namespaceEnd; i++) + { + if(doIndent && namespaceUri[ i ].length() > 40) { + writeIndent(); + out.write(" "); + } + if(namespacePrefix[ i ] != "") { + out.write(" xmlns:"); + out.write(namespacePrefix[ i ]); + out.write('='); + } else { + out.write(" xmlns="); + } + out.write( attributeUseApostrophe ? '\'' : '"'); + + //NOTE: escaping of namespace value the same way as attributes!!!! + writeAttributeValue(namespaceUri[ i ], out); + + out.write( attributeUseApostrophe ? '\'' : '"'); + } + } + + public XmlSerializer endTag(String namespace, String name) throws IOException + { + // check that level is valid + ////assert namespace != null; + //if(namespace != null) { + // namespace = namespace.intern(); + //} + seenBracket = seenBracketBracket = false; + if(namespace != null) { + if(!namesInterned) { + namespace = namespace.intern(); + } else if(checkNamesInterned) { + checkInterning(namespace); + } + } + + if(namespace != elNamespace[ depth ]) + { + throw new IllegalArgumentException( + "expected namespace "+printable(elNamespace[ depth ]) + +" and not "+printable(namespace)+getLocation()); + } + if(name == null) { + throw new IllegalArgumentException("end tag name can not be null"+getLocation()); + } + if(checkNamesInterned && namesInterned) { + checkInterning(name); + } + String startTagName = elName[ depth ]; + if((!namesInterned && !name.equals(startTagName)) + || (namesInterned && name != startTagName )) + { + throw new IllegalArgumentException( + "expected element name "+printable(elName[ depth ])+" and not "+printable(name)+getLocation()); + } + if(startTagIncomplete) { + writeNamespaceDeclarations(); + out.write(" />"); //space is added to make it easier to work in XHTML!!! + --depth; + } else { + //assert startTagIncomplete == false; + if(doIndent && seenTag) { writeIndent(); } + out.write(" 0) { + out.write(startTagPrefix); + out.write(':'); + } + + // if(namespace != null && namespace.length() > 0) { + // //TODO prefix should be alredy known from matching start tag ... + // final String prefix = lookupOrDeclarePrefix( namespace ); + // //assert( prefix != null); + // if(prefix.length() > 0) { + // out.write(prefix); + // out.write(':'); + // } + // } + out.write(name); + out.write('>'); + --depth; + } + namespaceEnd = elNamespaceCount[ depth ]; + startTagIncomplete = false; + seenTag = true; + return this; + } + + public XmlSerializer text (String text) throws IOException + { + //assert text != null; + if(startTagIncomplete || setPrefixCalled) closeStartTag(); + if(doIndent && seenTag) seenTag = false; + writeElementContent(text, out); + return this; + } + + public XmlSerializer text (char [] buf, int start, int len) throws IOException + { + if(startTagIncomplete || setPrefixCalled) closeStartTag(); + if(doIndent && seenTag) seenTag = false; + writeElementContent(buf, start, len, out); + return this; + } + + public void cdsect (String text) throws IOException + { + if(startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag(); + if(doIndent && seenTag) seenTag = false; + out.write(""); + } + + public void entityRef (String text) throws IOException + { + if(startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag(); + if(doIndent && seenTag) seenTag = false; + out.write('&'); + out.write(text); //escape? + out.write(';'); + } + + public void processingInstruction (String text) throws IOException + { + if(startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag(); + if(doIndent && seenTag) seenTag = false; + out.write(""); + } + + public void comment (String text) throws IOException + { + if(startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag(); + if(doIndent && seenTag) seenTag = false; + out.write(""); + } + + public void docdecl (String text) throws IOException + { + if(startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag(); + if(doIndent && seenTag) seenTag = false; + out.write(""); + } + + public void ignorableWhitespace (String text) throws IOException + { + if(startTagIncomplete || setPrefixCalled || seenBracket) closeStartTag(); + if(doIndent && seenTag) seenTag = false; + if(text.length() == 0) { + throw new IllegalArgumentException( + "empty string is not allowed for ignorable whitespace"+getLocation()); + } + out.write(text); //no escape? + } + + public void flush () throws IOException + { + if(!finished && startTagIncomplete) closeStartTag(); + out.flush(); + } + + // --- utility methods + + protected void writeAttributeValue(String value, Writer out) throws IOException + { + //.[apostrophe and <, & escaped], + final char quot = attributeUseApostrophe ? '\'' : '"'; + final String quotEntity = attributeUseApostrophe ? "'" : """; + + int pos = 0; + for (int i = 0; i < value.length(); i++) + { + char ch = value.charAt(i); + if(ch == '&') { + if(i > pos) out.write(value.substring(pos, i)); + out.write("&"); + pos = i + 1; + } if(ch == '<') { + if(i > pos) out.write(value.substring(pos, i)); + out.write("<"); + pos = i + 1; + }else if(ch == quot) { + if(i > pos) out.write(value.substring(pos, i)); + out.write(quotEntity); + pos = i + 1; + } else if(ch < 32) { + //in XML 1.0 only legal character are #x9 | #xA | #xD + // and they must be escaped otherwise in attribute value they are normalized to spaces + if(ch == 13 || ch == 10 || ch == 9) { + if(i > pos) out.write(value.substring(pos, i)); + out.write("&#"); + out.write(Integer.toString(ch)); + out.write(';'); + pos = i + 1; + } else { + if(TRACE_ESCAPING) System.err.println(getClass().getName()+" DEBUG ATTR value.len="+value.length()+" "+printable(value)); + + throw new IllegalStateException( + //"character "+Integer.toString(ch)+" is not allowed in output"+getLocation()); + "character "+printable(ch)+" ("+Integer.toString(ch)+") is not allowed in output"+getLocation() + +" (attr value="+printable(value)+")"); + // in XML 1.1 legal are [#x1-#xD7FF] + // if(ch > 0) { + // if(i > pos) out.write(text.substring(pos, i)); + // out.write("&#"); + // out.write(Integer.toString(ch)); + // out.write(';'); + // pos = i + 1; + // } else { + // throw new IllegalStateException( + // "character zero is not allowed in XML 1.1 output"+getLocation()); + // } + } + } + } + if(pos > 0) { + out.write(value.substring(pos)); + } else { + out.write(value); // this is shortcut to the most common case + } + + } + + protected void writeElementContent(String text, Writer out) throws IOException + { + // esccape '<', '&', ']]>', <32 if necessary + int pos = 0; + for (int i = 0; i < text.length(); i++) + { + //TODO: check if doing char[] text.getChars() would be faster than getCharAt(i) ... + char ch = text.charAt(i); + if(ch == ']') { + if(seenBracket) { + seenBracketBracket = true; + } else { + seenBracket = true; + } + } else { + if(ch == '&') { + if(i > pos) out.write(text.substring(pos, i)); + out.write("&"); + pos = i + 1; + } else if(ch == '<') { + if(i > pos) out.write(text.substring(pos, i)); + out.write("<"); + pos = i + 1; + } else if(seenBracketBracket && ch == '>') { + if(i > pos) out.write(text.substring(pos, i)); + out.write(">"); + pos = i + 1; + } else if(ch < 32) { + //in XML 1.0 only legal character are #x9 | #xA | #xD + if( ch == 9 || ch == 10 || ch == 13) { + // pass through + + // } else if(ch == 13) { //escape + // if(i > pos) out.write(text.substring(pos, i)); + // out.write("&#"); + // out.write(Integer.toString(ch)); + // out.write(';'); + // pos = i + 1; + } else { + if(TRACE_ESCAPING) System.err.println(getClass().getName()+" DEBUG TEXT value.len="+text.length()+" "+printable(text)); + throw new IllegalStateException( + "character "+Integer.toString(ch)+" is not allowed in output"+getLocation() + +" (text value="+printable(text)+")"); + // in XML 1.1 legal are [#x1-#xD7FF] + // if(ch > 0) { + // if(i > pos) out.write(text.substring(pos, i)); + // out.write("&#"); + // out.write(Integer.toString(ch)); + // out.write(';'); + // pos = i + 1; + // } else { + // throw new IllegalStateException( + // "character zero is not allowed in XML 1.1 output"+getLocation()); + // } + } + } + if(seenBracket) { + seenBracketBracket = seenBracket = false; + } + + } + } + if(pos > 0) { + out.write(text.substring(pos)); + } else { + out.write(text); // this is shortcut to the most common case + } + + + + } + + protected void writeElementContent(char[] buf, int off, int len, Writer out) throws IOException + { + // esccape '<', '&', ']]>' + final int end = off + len; + int pos = off; + for (int i = off; i < end; i++) + { + final char ch = buf[i]; + if(ch == ']') { + if(seenBracket) { + seenBracketBracket = true; + } else { + seenBracket = true; + } + } else { + if(ch == '&') { + if(i > pos) { + out.write(buf, pos, i - pos); + } + out.write("&"); + pos = i + 1; + } else if(ch == '<') { + if(i > pos) { + out.write(buf, pos, i - pos); + } + out.write("<"); + pos = i + 1; + + } else if(seenBracketBracket && ch == '>') { + if(i > pos) { + out.write(buf, pos, i - pos); + } + out.write(">"); + pos = i + 1; + } else if(ch < 32) { + //in XML 1.0 only legal character are #x9 | #xA | #xD + if( ch == 9 || ch == 10 || ch == 13) { + // pass through + + + // } else if(ch == 13 ) { //if(ch == '\r') { + // if(i > pos) { + // out.write(buf, pos, i - pos); + // } + // out.write("&#"); + // out.write(Integer.toString(ch)); + // out.write(';'); + // pos = i + 1; + } else { + if(TRACE_ESCAPING) System.err.println( + getClass().getName()+" DEBUG TEXT value.len=" + +len+" "+printable(new String(buf,off,len))); + throw new IllegalStateException( + "character "+printable(ch)+" ("+Integer.toString(ch)+") is not allowed in output"+getLocation()); + // in XML 1.1 legal are [#x1-#xD7FF] + // if(ch > 0) { + // if(i > pos) out.write(text.substring(pos, i)); + // out.write("&#"); + // out.write(Integer.toString(ch)); + // out.write(';'); + // pos = i + 1; + // } else { + // throw new IllegalStateException( + // "character zero is not allowed in XML 1.1 output"+getLocation()); + // } + } + } + if(seenBracket) { + seenBracketBracket = seenBracket = false; + } + // assert seenBracketBracket == seenBracket == false; + } + } + if(end > pos) { + out.write(buf, pos, end - pos); + } + } + + /** simple utility method -- good for debugging */ + protected static final String printable(String s) { + if(s == null) return "null"; + StringBuffer retval = new StringBuffer(s.length() + 16); + retval.append("'"); + char ch; + for (int i = 0; i < s.length(); i++) { + addPrintable(retval, s.charAt(i)); + } + retval.append("'"); + return retval.toString(); + } + + protected static final String printable(char ch) { + StringBuffer retval = new StringBuffer(); + addPrintable(retval, ch); + return retval.toString(); + } + + private static void addPrintable(StringBuffer retval, char ch) + { + switch (ch) + { + case '\b': + retval.append("\\b"); + break; + case '\t': + retval.append("\\t"); + break; + case '\n': + retval.append("\\n"); + break; + case '\f': + retval.append("\\f"); + break; + case '\r': + retval.append("\\r"); + break; + case '\"': + retval.append("\\\""); + break; + case '\'': + retval.append("\\\'"); + break; + case '\\': + retval.append("\\\\"); + break; + default: + if (ch < 0x20 || ch > 0x7e) { + final String ss = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + ss.substring(ss.length() - 4, ss.length())); + } else { + retval.append(ch); + } + } + } + +} + diff --git a/brut.apktool/apktool-lib/src/main/resources/brut/androlib/apktool.properties b/brut.apktool/apktool-lib/src/main/resources/brut/androlib/apktool.properties new file mode 100644 index 0000000..f569a5c --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/resources/brut/androlib/apktool.properties @@ -0,0 +1,2 @@ +application.version=${version} +git.commit.id.abbrev = ${git.commit.id.abbrev} diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java new file mode 100644 index 0000000..13355d0 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/BuildAndDecodeTest.java @@ -0,0 +1,177 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.androlib.res.util.ExtFile; +import brut.common.BrutException; +import brut.util.OS; +import java.io.*; +import java.util.HashMap; +import java.util.logging.Logger; +import org.custommonkey.xmlunit.*; +import org.junit.*; +import static org.junit.Assert.*; +import org.xml.sax.SAXException; + + +/** + * @author Ryszard Wiśniewski + */ +public class BuildAndDecodeTest { + + @BeforeClass + public static void beforeClass() throws BrutException { + sTmpDir = new ExtFile(OS.createTempDirectory()); + sTestOrigDir = new ExtFile(sTmpDir, "testapp-orig"); + sTestNewDir = new ExtFile(sTmpDir, "testapp-new"); + File testApk = new File(sTmpDir, "testapp.apk"); + + LOGGER.info("Unpacking testapp..."); + TestUtils.copyResourceDir(BuildAndDecodeTest.class, + "brut/apktool/testapp/", sTestOrigDir); + + LOGGER.info("Building testapp.apk..."); + ExtFile blank = null; + new Androlib().build(sTestOrigDir, testApk, BuildAndDecodeTest.returnStock(),blank); + + LOGGER.info("Decoding testapp.apk..."); + ApkDecoder apkDecoder = new ApkDecoder(testApk); + apkDecoder.setOutDir(sTestNewDir); + apkDecoder.decode(); + } + + @AfterClass + public static void afterClass() throws BrutException { + OS.rmdir(sTmpDir); + } + + @Test + public void valuesArraysTest() throws BrutException { + compareValuesFiles("values-mcc001/arrays.xml"); + compareValuesFiles("values-mcc002/arrays.xml"); + } + + @Test + public void valuesBoolsTest() throws BrutException { + compareValuesFiles("values-mcc001/bools.xml"); + } + + @Test + public void valuesColorsTest() throws BrutException { + compareValuesFiles("values-mcc001/colors.xml"); + } + + @Test + public void valuesDimensTest() throws BrutException { + compareValuesFiles("values-mcc001/dimens.xml"); + } + + @Test + public void valuesIdsTest() throws BrutException { + compareValuesFiles("values-mcc001/ids.xml"); + } + + @Test + public void valuesIntegersTest() throws BrutException { + compareValuesFiles("values-mcc001/integers.xml"); + } + + @Test + public void valuesStringsTest() throws BrutException { + compareValuesFiles("values-mcc001/strings.xml"); + } + + @Test + public void valuesReferencesTest() throws BrutException { + compareValuesFiles("values-mcc002/strings.xml"); + } + + @Test + public void crossTypeTest() throws BrutException { + compareValuesFiles("values-mcc003/strings.xml"); + compareValuesFiles("values-mcc003/integers.xml"); + compareValuesFiles("values-mcc003/bools.xml"); + } + + @Test + public void xmlLiteralsTest() throws BrutException { + compareXmlFiles("res/xml/literals.xml"); + } + + @Test + public void xmlReferencesTest() throws BrutException { + compareXmlFiles("res/xml/references.xml"); + } + + @Test + public void qualifiersTest() throws BrutException { + compareValuesFiles("values-mcc004-mnc4-en-rUS-sw100dp-w200dp-h300dp" + + "-xlarge-long-land-desk-night-xhdpi-finger-keyssoft-12key" + + "-navhidden-dpad/strings.xml"); + } + + private void compareValuesFiles(String path) throws BrutException { + compareXmlFiles("res/" + path, + new ElementNameAndAttributeQualifier("name")); + } + + private void compareXmlFiles(String path) throws BrutException { + compareXmlFiles(path, null); + } + + private void compareXmlFiles(String path, + ElementQualifier qualifier) throws BrutException { + DetailedDiff diff; + try { + Reader control = new FileReader( + new File(sTestOrigDir, path)); + Reader test = new FileReader(new File(sTestNewDir, path)); + + diff = new DetailedDiff(new Diff(control, test)); + } catch (SAXException ex) { + throw new BrutException(ex); + } catch (IOException ex) { + throw new BrutException(ex); + } + + if (qualifier != null) { + diff.overrideElementQualifier(qualifier); + } + + assertTrue(path + ": " + + diff.getAllDifferences().toString(), diff.similar()); + } + + private static HashMap returnStock() throws BrutException { + HashMap tmp = new HashMap(); + tmp.put("forceBuildAll", false); + tmp.put("debug", false); + tmp.put("verbose", false); + tmp.put("injectOriginal", false); + tmp.put("framework", false); + tmp.put("update", false); + + return tmp; + } + + private static ExtFile sTmpDir; + private static ExtFile sTestOrigDir; + private static ExtFile sTestNewDir; + + private final static Logger LOGGER = + Logger.getLogger(BuildAndDecodeTest.class.getName()); +} diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java new file mode 100644 index 0000000..d6efc27 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java @@ -0,0 +1,138 @@ + /** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 + * + * http://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; + +import brut.common.BrutException; +import brut.directory.*; +import java.io.*; +import java.net.URL; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Map; +import org.custommonkey.xmlunit.ElementQualifier; +import org.w3c.dom.Element; +import org.xmlpull.v1.*; + +/** + * @author Ryszard Wiśniewski + */ +public abstract class TestUtils { + + public static Map parseStringsXml(File file) + throws BrutException { + try { + XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser(); + xpp.setInput(new FileReader(file)); + + int eventType; + String key = null; + Map map = new HashMap(); + while ((eventType = xpp.next()) != XmlPullParser.END_DOCUMENT) { + switch (eventType) { + case XmlPullParser.START_TAG: + if ("string".equals(xpp.getName())) { + int attrCount = xpp.getAttributeCount(); + for (int i = 0; i < attrCount; i++) { + if ("name".equals(xpp.getAttributeName(i))) { + key = xpp.getAttributeValue(i); + break; + } + } + } + break; + case XmlPullParser.END_TAG: + if ("string".equals(xpp.getName())) { + key = null; + } + break; + case XmlPullParser.TEXT: + if (key != null) { + map.put(key, xpp.getText()); + } + break; + } + } + + return map; + } catch (IOException ex) { + throw new BrutException(ex); + } catch (XmlPullParserException ex) { + throw new BrutException(ex); + } + } + + /* TODO: move to brut.util.Jar - it's not possible for now, because below + * implementation uses brut.dir. I think I should merge all my projects to + * single brut.common . + */ + public static void copyResourceDir(Class class_, String dirPath, File out) + throws BrutException { + if (! out.exists()) { + out.mkdirs(); + } + copyResourceDir(class_, dirPath, new FileDirectory(out)); + } + + public static void copyResourceDir(Class class_, String dirPath, + Directory out) throws BrutException { + if (class_ == null) { + class_ = Class.class; + } + + URL dirURL = class_.getClassLoader().getResource(dirPath); + if (dirURL != null && dirURL.getProtocol().equals("file")) { + DirUtil.copyToDir(new FileDirectory(dirURL.getFile()), out); + return; + } + + if (dirURL == null) { + String className = class_.getName().replace(".", "/") + ".class"; + dirURL = class_.getClassLoader().getResource(className); + } + + + if (dirURL.getProtocol().equals("jar")) { + String jarPath; + try { + jarPath = URLDecoder.decode(dirURL.getPath().substring( + 5, dirURL.getPath().indexOf("!")), "UTF-8"); + } catch (UnsupportedEncodingException ex) { + throw new BrutException(ex); + } + DirUtil.copyToDir(new FileDirectory(jarPath), out); + } + } + + + public static class ResValueElementQualifier implements ElementQualifier { + + public boolean qualifyForComparison(Element control, Element test) { + String controlType = control.getTagName(); + if ("item".equals(controlType)) { + controlType = control.getAttribute("type"); + } + + String testType = test.getTagName(); + if ("item".equals(testType)) { + testType = test.getAttribute("type"); + } + + return controlType.equals(testType) && control.getAttribute("name") + .equals(test.getAttribute("name")); + } + } +} diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/AndroidManifest.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/AndroidManifest.xml new file mode 100644 index 0000000..d79f618 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/apktool.yml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/apktool.yml new file mode 100644 index 0000000..dbe200b --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/apktool.yml @@ -0,0 +1,6 @@ +version: 1.5.0 +apkFileName: testapp.apk +isFrameworkApk: false +usesFramework: + ids: + - 1 diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/arrays.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/arrays.xml new file mode 100644 index 0000000..8cc3d15 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/arrays.xml @@ -0,0 +1,23 @@ + + + + TEST1 + TEST2 + TEST3 + %2$s foo %1$d + + + -1 + 0 + 1 + + + + true + TEST + 5 + 5.5 + 10.0sp + #ff123456 + + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/bools.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/bools.xml new file mode 100644 index 0000000..787bfca --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/bools.xml @@ -0,0 +1,5 @@ + + + false + true + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/colors.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/colors.xml new file mode 100644 index 0000000..00dfdb0 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/colors.xml @@ -0,0 +1,4 @@ + + + #ff123456 + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/dimens.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/dimens.xml new file mode 100644 index 0000000..2a3a644 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/dimens.xml @@ -0,0 +1,9 @@ + + + 10.0dip + 10.0sp + 10.0pt + 10.0px + 10.0mm + 10.0in + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/ids.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/ids.xml new file mode 100644 index 0000000..1d2724a --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/ids.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/integers.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/integers.xml new file mode 100644 index 0000000..d672d18 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/integers.xml @@ -0,0 +1,6 @@ + + + -1 + 0 + 1 + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml new file mode 100644 index 0000000..6beed51 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc001/strings.xml @@ -0,0 +1,27 @@ + + + + Lorem ipsum... + \@ + \? + \#ff123456 + & + "'" + \" + \u0005 + " foo bar " + "foo +bar" + " foo"bar baz foo + foobarbaz + foobar"b + az"foo + %d of %d + foo %d bar % + %2$s foo %1$d + %-e foo %,d + %2$-e foo %1$,d + %02d foo %01d + %d foo %1 + %1% foo %2% + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/arrays.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/arrays.xml new file mode 100644 index 0000000..96e2701 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/arrays.xml @@ -0,0 +1,7 @@ + + + + @string/test_string1 + @string/test_string2 + + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/strings.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/strings.xml new file mode 100644 index 0000000..2017d36 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc002/strings.xml @@ -0,0 +1,6 @@ + + + @string/test1 + @*android:string/ok + ?android:textStyle + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/bools.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/bools.xml new file mode 100644 index 0000000..cf6de94 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/bools.xml @@ -0,0 +1,4 @@ + + + TEST + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/integers.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/integers.xml new file mode 100644 index 0000000..2540c7b --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/integers.xml @@ -0,0 +1,4 @@ + + + TEST + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/strings.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/strings.xml new file mode 100644 index 0000000..b97cd2f --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc003/strings.xml @@ -0,0 +1,5 @@ + + + true + 5 + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc004-mnc4-en-rUS-sw100dp-w200dp-h300dp-xlarge-long-land-desk-night-xhdpi-finger-keyssoft-12key-navhidden-dpad/strings.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc004-mnc4-en-rUS-sw100dp-w200dp-h300dp-xlarge-long-land-desk-night-xhdpi-finger-keyssoft-12key-navhidden-dpad/strings.xml new file mode 100644 index 0000000..00ec9a4 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values-mcc004-mnc4-en-rUS-sw100dp-w200dp-h300dp-xlarge-long-land-desk-night-xhdpi-finger-keyssoft-12key-navhidden-dpad/strings.xml @@ -0,0 +1,4 @@ + + + + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values/strings.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values/strings.xml new file mode 100644 index 0000000..00ec9a4 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/values/strings.xml @@ -0,0 +1,4 @@ + + + + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/xml/literals.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/xml/literals.xml new file mode 100644 index 0000000..aa360a7 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/xml/literals.xml @@ -0,0 +1,16 @@ + + diff --git a/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/xml/references.xml b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/xml/references.xml new file mode 100644 index 0000000..ac200d2 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/brut/apktool/testapp/res/xml/references.xml @@ -0,0 +1,6 @@ + + diff --git a/brut.apktool/src/templates/apache2.0-header.txt b/brut.apktool/src/templates/apache2.0-header.txt new file mode 100644 index 0000000..5d34994 --- /dev/null +++ b/brut.apktool/src/templates/apache2.0-header.txt @@ -0,0 +1,14 @@ + Copyright 2011 Ryszard Wiśniewski + + 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 + + http://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. +