├── settings.gradle ├── src ├── main │ ├── resources │ │ ├── CERT.RSA │ │ ├── demo.keystore │ │ ├── tap_unsign.apk │ │ └── ic_launcher.png │ └── java │ │ └── com │ │ └── zzzmode │ │ └── apkeditor │ │ ├── axmleditor │ │ ├── decode │ │ │ ├── IVisitable.java │ │ │ ├── IVisitor.java │ │ │ ├── IAXMLSerialize.java │ │ │ ├── BTXTNode.java │ │ │ ├── ResBlock.java │ │ │ ├── BNSNode.java │ │ │ ├── BXMLNode.java │ │ │ ├── BXMLTree.java │ │ │ ├── IntWriter.java │ │ │ ├── IntReader.java │ │ │ ├── XMLVisitor.java │ │ │ ├── AXMLDoc.java │ │ │ ├── BTagNode.java │ │ │ └── StringBlock.java │ │ ├── utils │ │ │ ├── Pair.java │ │ │ ├── Cast.java │ │ │ ├── CSString.java │ │ │ ├── TypedValue.java │ │ │ └── NodeValue.java │ │ └── editor │ │ │ ├── XEditor.java │ │ │ ├── BaseEditor.java │ │ │ ├── MetaDataEditor.java │ │ │ ├── ApplicationInfoEditor.java │ │ │ ├── PermissionEditor.java │ │ │ └── PackageInfoEditor.java │ │ ├── ApkEdirotMain.java │ │ ├── utils │ │ ├── BinaryEncoder.java │ │ ├── BinaryDecoder.java │ │ ├── Encoder.java │ │ ├── Decoder.java │ │ ├── DecoderException.java │ │ ├── EncoderException.java │ │ ├── FileUtils.java │ │ ├── HexDumpEncoder.java │ │ ├── Charsets.java │ │ ├── StringUtils.java │ │ ├── HexEncoder.java │ │ └── BaseNCodec.java │ │ ├── apksigner │ │ ├── KeyHelper.java │ │ ├── SignApk.java │ │ └── ZipManager.java │ │ └── ApkEditor.java └── test │ ├── resources │ ├── CERT.RSA │ ├── demo.keystore │ ├── tap_unsign.apk │ └── ic_launcher.png │ └── java │ └── com │ └── zzzmode │ └── apkeditor │ ├── TestUtils.java │ ├── SignApkTest.java │ └── AxmlTest.java ├── .travis.yml ├── README.md └── .gitignore /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'apkeditor' 2 | 3 | -------------------------------------------------------------------------------- /src/main/resources/CERT.RSA: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8enet/apkeditor/HEAD/src/main/resources/CERT.RSA -------------------------------------------------------------------------------- /src/test/resources/CERT.RSA: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8enet/apkeditor/HEAD/src/test/resources/CERT.RSA -------------------------------------------------------------------------------- /src/main/resources/demo.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8enet/apkeditor/HEAD/src/main/resources/demo.keystore -------------------------------------------------------------------------------- /src/main/resources/tap_unsign.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8enet/apkeditor/HEAD/src/main/resources/tap_unsign.apk -------------------------------------------------------------------------------- /src/test/resources/demo.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8enet/apkeditor/HEAD/src/test/resources/demo.keystore -------------------------------------------------------------------------------- /src/test/resources/tap_unsign.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8enet/apkeditor/HEAD/src/test/resources/tap_unsign.apk -------------------------------------------------------------------------------- /src/main/resources/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8enet/apkeditor/HEAD/src/main/resources/ic_launcher.png -------------------------------------------------------------------------------- /src/test/resources/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8enet/apkeditor/HEAD/src/test/resources/ic_launcher.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: java 4 | 5 | jdk: 6 | - oraclejdk7 7 | - openjdk7 8 | 9 | script: gradle build -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/IVisitable.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | public interface IVisitable { 4 | public void accept(IVisitor v); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/IVisitor.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | public interface IVisitor { 4 | public void visit(BNSNode node); 5 | public void visit(BTagNode node); 6 | public void visit(BTXTNode node); 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/com/zzzmode/apkeditor/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * Created by zl on 15/11/17. 7 | */ 8 | public class TestUtils { 9 | 10 | public static File getResourceFile(String resourceName) throws Exception { 11 | return new File(ClassLoader.getSystemClassLoader().getResource(resourceName).toURI()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/utils/Pair.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.utils; 2 | 3 | public class Pair { 4 | public T1 first; 5 | public T2 second; 6 | 7 | public Pair(T1 t1, T2 t2){ 8 | first = t1; 9 | second = t2; 10 | } 11 | 12 | public Pair(){} 13 | 14 | 15 | public static Pair create(A a, B b) { 16 | return new Pair(a, b); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/utils/Cast.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.utils; 2 | 3 | 4 | /** 5 | * @author Dmitry Skiba 6 | * 7 | */ 8 | public class Cast { 9 | 10 | public static final CharSequence toCharSequence(String string) { 11 | if (string==null) { 12 | return null; 13 | } 14 | return new CSString(string); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/IAXMLSerialize.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | import java.io.IOException; 4 | 5 | public interface IAXMLSerialize { 6 | public int getSize(); 7 | public int getType(); 8 | 9 | public void setSize(int size); 10 | public void setType(int type); 11 | 12 | public void read(IntReader reader) throws IOException; 13 | public void write(IntWriter writer) throws IOException; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/editor/XEditor.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.editor; 2 | 3 | /** 4 | * Created by zl on 15/9/8. 5 | */ 6 | public interface XEditor { 7 | 8 | String NAME_SPACE = "http://schemas.android.com/apk/res/android"; 9 | 10 | String NODE_MANIFEST="manifest"; 11 | String NODE_APPLICATION="application"; 12 | String NODE_METADATA="meta-data"; 13 | 14 | String NODE_USER_PREMISSION ="uses-permission"; 15 | String NODE_SUPPORTS_SCREENS="supports-screens"; 16 | 17 | String NAME = "name"; 18 | String VALUE = "value"; 19 | 20 | void setEditor(String attrName, String attrValue); 21 | 22 | void commit(); 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/zzzmode/apkeditor/SignApkTest.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor; 2 | 3 | import com.zzzmode.apkeditor.apksigner.*; 4 | import org.junit.*; 5 | 6 | import java.io.*; 7 | 8 | /** 9 | * Created by zl on 15/11/17. 10 | */ 11 | public class SignApkTest { 12 | 13 | @Test 14 | public void signApkTest() throws Exception { 15 | File outFile = File.createTempFile("tap_sign", ".apk",null); 16 | 17 | SignApk signApk = new SignApk(KeyHelper.privateKey,KeyHelper.sigPrefix); 18 | signApk.sign(TestUtils.getResourceFile("tap_unsign.apk"),outFile.getAbsolutePath()); 19 | 20 | assert SignApk.verifyJar(outFile.getAbsolutePath()); 21 | outFile.delete(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ApkEditor 2 | [![Build Status](https://travis-ci.org/8enet/apkeditor.svg?branch=master)](https://travis-ci.org/8enet/apkeditor) [![Software License](https://img.shields.io/badge/license-GPLv3-blue.svg)](LICENSE) 3 | 4 | 可以运行于jdk和android平台的apk修改器,支持修改AndroidManifest.xml文件,添加删除节点数据,不依赖aapt和jdk即可生成和签名apk. 5 | 6 | ## Thanks 7 | [signapk](https://github.com/android/platform_build/tree/master/tools/signapk) 8 | [AXMLEditor](https://github.com/ntop001/AXMLEditor) 9 | [commons-codec](https://github.com/apache/commons-codec) 10 | [zip-signer](https://code.google.com/p/zip-signer/) 11 | [Bouncy Castle](http://www.bouncycastle.org/java.html) 12 | ## License 13 | Code is under the [GPL v3](https://gnu.org/licenses/gpl.html) [LICENSE](https://github.com/8enet/apkeditor/tree/master/LICENSE.txt) 14 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/utils/CSString.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.utils; 2 | 3 | /** 4 | * @author Dmitry Skiba 5 | * 6 | * Helper class, used in Cast.toCharSequence. 7 | */ 8 | public class CSString implements CharSequence { 9 | 10 | public CSString(String string) { 11 | if (string==null) { 12 | string=""; 13 | } 14 | m_string=string; 15 | } 16 | 17 | public int length() { 18 | return m_string.length(); 19 | } 20 | 21 | public char charAt(int index) { 22 | return m_string.charAt(index); 23 | } 24 | 25 | public CharSequence subSequence(int start,int end) { 26 | return new CSString(m_string.substring(start,end)); 27 | } 28 | 29 | public String toString() { 30 | return m_string; 31 | } 32 | 33 | private String m_string; 34 | } -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/ApkEdirotMain.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor; 2 | 3 | 4 | import com.zzzmode.apkeditor.apksigner.KeyHelper; 5 | 6 | import java.io.File; 7 | 8 | /** 9 | * Created by zl on 15/9/8. 10 | */ 11 | public class ApkEdirotMain { 12 | 13 | 14 | public static void main(String[] args)throws Exception{ 15 | 16 | //test code 17 | //如何生成 key,具体方法见KeyHelper 18 | ApkEditor editor=new ApkEditor(KeyHelper.privateKey,KeyHelper.sigPrefix); 19 | 20 | File unsignFile=new File(ClassLoader.getSystemClassLoader().getResource("tap_unsign.apk").toURI()); 21 | File tempFile = File.createTempFile("tap_sign", ".apk",ApkEditor.getWorkDir()); 22 | 23 | editor.setOrigFile(unsignFile.getAbsolutePath()); 24 | editor.setOutFile(tempFile.getAbsolutePath()); 25 | editor.setAppName("custom app name"); 26 | editor.setAppIcon(new File(ClassLoader.getSystemClassLoader().getResource("ic_launcher.png").toURI()).getAbsolutePath()); 27 | final boolean b = editor.create(); 28 | System.out.println("apkeditor rebuild "+b+" output apk path "+tempFile.getAbsolutePath()); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/editor/BaseEditor.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.editor; 2 | 3 | import com.zzzmode.apkeditor.axmleditor.decode.AXMLDoc; 4 | import com.zzzmode.apkeditor.axmleditor.decode.BXMLNode; 5 | import com.zzzmode.apkeditor.axmleditor.decode.StringBlock; 6 | 7 | /** 8 | * Created by zl on 15/9/8. 9 | */ 10 | public abstract class BaseEditor implements XEditor { 11 | 12 | public BaseEditor(AXMLDoc doc){ 13 | this.doc=doc; 14 | } 15 | 16 | protected AXMLDoc doc; 17 | 18 | protected String attrName; 19 | protected String arrrValue; 20 | 21 | protected int namespace; 22 | 23 | protected int attr_name; 24 | protected int attr_value; 25 | 26 | protected T editorInfo; 27 | 28 | public void setEditorInfo(T editorInfo) { 29 | this.editorInfo = editorInfo; 30 | } 31 | 32 | @Override 33 | public void setEditor(String attrName, String attrValue) { 34 | this.attrName=attrName; 35 | this.arrrValue=attrValue; 36 | } 37 | 38 | @Override 39 | public void commit() { 40 | if(editorInfo != null) { 41 | registStringBlock(doc.getStringBlock()); 42 | editor(); 43 | } 44 | } 45 | 46 | public abstract String getEditorName(); 47 | 48 | protected abstract void editor(); 49 | 50 | protected abstract BXMLNode findNode(); 51 | 52 | protected abstract void registStringBlock(StringBlock block); 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/BinaryEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.zzzmode.apkeditor.utils; 19 | 20 | /** 21 | * Defines common encoding methods for byte array encoders. 22 | * 23 | * @version $Id$ 24 | */ 25 | public interface BinaryEncoder extends Encoder { 26 | 27 | /** 28 | * Encodes a byte array and return the encoded data as a byte array. 29 | * 30 | * @param source 31 | * Data to be encoded 32 | * @return A byte array containing the encoded data 33 | * @throws EncoderException 34 | * thrown if the Encoder encounters a failure condition during the encoding process. 35 | */ 36 | byte[] encode(byte[] source) throws EncoderException; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/BTXTNode.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | public class BTXTNode extends BXMLNode implements IVisitable{ 7 | private final int TAG = 0x00100104; 8 | private int mRawName; 9 | 10 | public void checkTag(int value) throws IOException{ 11 | super.checkTag(TAG, value); 12 | } 13 | 14 | @SuppressWarnings("unused") 15 | public void readStart(IntReader reader) throws IOException{ 16 | super.readStart(reader); 17 | 18 | mRawName = reader.readInt(); 19 | 20 | int skip0 = reader.readInt(); 21 | int skip1 = reader.readInt(); 22 | } 23 | 24 | public void readEnd(IntReader reader) throws IOException{ 25 | } 26 | 27 | public void prepare(){ 28 | 29 | } 30 | 31 | public void writeStart(IntWriter writer) throws IOException{ 32 | writer.writeInt(TAG); 33 | super.writeStart(writer); 34 | writer.writeInt(mRawName); 35 | 36 | writer.writeInt(0);//skiped 37 | writer.writeInt(0);//skiped 38 | } 39 | 40 | public void writeEnd(IntWriter writer){ 41 | 42 | } 43 | 44 | public int getName(){ 45 | return mRawName; 46 | } 47 | 48 | public boolean hasChild(){ 49 | return false; 50 | } 51 | 52 | public List getChildren(){ 53 | throw new RuntimeException("Text node has no child"); 54 | } 55 | 56 | public void addChild(BXMLNode node){ 57 | throw new RuntimeException("Can't add child to Text node"); 58 | } 59 | 60 | @Override 61 | public void accept(IVisitor v) { 62 | v.visit(this); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/BinaryDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.zzzmode.apkeditor.utils; 19 | 20 | /** 21 | * Defines common decoding methods for byte array decoders. 22 | * 23 | * @version $Id$ 24 | */ 25 | public interface BinaryDecoder extends Decoder { 26 | 27 | /** 28 | * Decodes a byte array and returns the results as a byte array. 29 | * 30 | * @param source 31 | * A byte array which has been encoded with the appropriate encoder 32 | * @return a byte array that contains decoded content 33 | * @throws DecoderException 34 | * A decoder exception is thrown if a Decoder encounters a failure condition during the decode process. 35 | */ 36 | byte[] decode(byte[] source) throws DecoderException; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/utils/TypedValue.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.utils; 2 | 3 | /** 4 | * @author Dmitry Skiba 5 | * 6 | */ 7 | public class TypedValue { 8 | 9 | public int type; 10 | public CharSequence string; 11 | public int data; 12 | public int assetCookie; 13 | public int resourceId; 14 | public int changingConfigurations; 15 | 16 | public static final int 17 | TYPE_NULL =0, 18 | TYPE_REFERENCE =1, 19 | TYPE_ATTRIBUTE =2, 20 | TYPE_STRING =3, 21 | TYPE_FLOAT =4, 22 | TYPE_DIMENSION =5, 23 | TYPE_FRACTION =6, 24 | TYPE_FIRST_INT =16, 25 | TYPE_INT_DEC =16, 26 | TYPE_INT_HEX =17, 27 | TYPE_INT_BOOLEAN =18, 28 | TYPE_FIRST_COLOR_INT =28, 29 | TYPE_INT_COLOR_ARGB8 =28, 30 | TYPE_INT_COLOR_RGB8 =29, 31 | TYPE_INT_COLOR_ARGB4 =30, 32 | TYPE_INT_COLOR_RGB4 =31, 33 | TYPE_LAST_COLOR_INT =31, 34 | TYPE_LAST_INT =31; 35 | 36 | public static final int 37 | COMPLEX_UNIT_PX =0, 38 | COMPLEX_UNIT_DIP =1, 39 | COMPLEX_UNIT_SP =2, 40 | COMPLEX_UNIT_PT =3, 41 | COMPLEX_UNIT_IN =4, 42 | COMPLEX_UNIT_MM =5, 43 | COMPLEX_UNIT_SHIFT =0, 44 | COMPLEX_UNIT_MASK =15, 45 | COMPLEX_UNIT_FRACTION =0, 46 | COMPLEX_UNIT_FRACTION_PARENT=1, 47 | COMPLEX_RADIX_23p0 =0, 48 | COMPLEX_RADIX_16p7 =1, 49 | COMPLEX_RADIX_8p15 =2, 50 | COMPLEX_RADIX_0p23 =3, 51 | COMPLEX_RADIX_SHIFT =4, 52 | COMPLEX_RADIX_MASK =3, 53 | COMPLEX_MANTISSA_SHIFT =8, 54 | COMPLEX_MANTISSA_MASK =0xFFFFFF; 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/test/java/com/zzzmode/apkeditor/AxmlTest.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor; 2 | 3 | import com.zzzmode.apkeditor.apksigner.*; 4 | import com.zzzmode.apkeditor.axmleditor.decode.*; 5 | import com.zzzmode.apkeditor.axmleditor.editor.*; 6 | import com.zzzmode.apkeditor.utils.*; 7 | import org.junit.*; 8 | 9 | import java.io.*; 10 | 11 | /** 12 | * Created by zl on 15/11/17. 13 | */ 14 | public class AxmlTest { 15 | 16 | @Test 17 | public void axmlReaderTest() throws Exception { 18 | File apkFile=TestUtils.getResourceFile("tap_unsign.apk"); 19 | 20 | File axmlFile=File.createTempFile("AndroidManifest", ".xml",null); 21 | ZipManager.extraZipEntry(apkFile, new String[]{"AndroidManifest.xml"}, new String[]{axmlFile.getAbsolutePath()}); 22 | AXMLDoc doc = new AXMLDoc(); 23 | doc.parse(FileUtils.openInputStream(axmlFile)); 24 | doc.print(); 25 | } 26 | 27 | @Test 28 | public void axmlEditTest()throws Exception{ 29 | File apkFile=TestUtils.getResourceFile("tap_unsign.apk"); 30 | File axmlFile=File.createTempFile("AndroidManifest", ".xml",null); 31 | ZipManager.extraZipEntry(apkFile, new String[]{"AndroidManifest.xml"}, new String[]{axmlFile.getAbsolutePath()}); 32 | AXMLDoc doc = new AXMLDoc(); 33 | doc.parse(FileUtils.openInputStream(axmlFile)); 34 | 35 | ApplicationInfoEditor applicationInfoEditor = new ApplicationInfoEditor(doc); 36 | applicationInfoEditor.setEditorInfo(new ApplicationInfoEditor.EditorInfo("testAppName", false)); 37 | applicationInfoEditor.commit(); 38 | 39 | doc.build(new FileOutputStream(axmlFile)); 40 | doc.print(); 41 | doc.release(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/Encoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.zzzmode.apkeditor.utils; 19 | 20 | /** 21 | * Provides the highest level of abstraction for Encoders. 22 | *

23 | * This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this 24 | * common generic interface which allows a user to pass a generic Object to any Encoder implementation 25 | * in the codec package. 26 | * 27 | * @version $Id$ 28 | */ 29 | public interface Encoder { 30 | 31 | /** 32 | * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be 33 | * byte[] or Strings depending on the implementation used. 34 | * 35 | * @param source 36 | * An object to encode 37 | * @return An "encoded" Object 38 | * @throws EncoderException 39 | * An encoder exception is thrown if the encoder experiences a failure condition during the encoding 40 | * process. 41 | */ 42 | Object encode(Object source) throws EncoderException; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/ResBlock.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | import java.io.IOException; 4 | 5 | public class ResBlock implements IAXMLSerialize{ 6 | private static final int TAG = 0x00080180; 7 | 8 | private int mChunkSize; 9 | private int[] mRawResIds; 10 | 11 | public void print(){ 12 | StringBuilder sb = new StringBuilder(); 13 | 14 | for(int id : getResourceIds()){ 15 | sb.append(id); 16 | sb.append(" "); 17 | } 18 | 19 | System.out.println(sb.toString()); 20 | } 21 | 22 | public void read(IntReader reader) throws IOException{ 23 | mChunkSize = reader.readInt(); 24 | 25 | if(mChunkSize < 8 || (mChunkSize % 4)!= 0){ 26 | throw new IOException("Invalid resource ids size ("+mChunkSize+")."); 27 | } 28 | 29 | mRawResIds = reader.readIntArray(mChunkSize/4 - 2);//subtract base offset (type + size) 30 | } 31 | 32 | private final int INT_SIZE = 4; 33 | public void prepare(){ 34 | int base = 2*INT_SIZE; 35 | int resSize = mRawResIds == null ? 0:mRawResIds.length*INT_SIZE; 36 | mChunkSize = base + resSize; 37 | } 38 | 39 | @Override 40 | public void write(IntWriter writer) throws IOException { 41 | writer.writeInt(TAG); 42 | writer.writeInt(mChunkSize); 43 | 44 | if(mRawResIds != null){ 45 | for(int id : mRawResIds){ 46 | writer.writeInt(id); 47 | } 48 | } 49 | } 50 | 51 | public int[] getResourceIds(){ 52 | return mRawResIds; 53 | } 54 | 55 | public int getResourceIdAt(int index){ 56 | return mRawResIds[index]; 57 | } 58 | 59 | @Override 60 | public int getSize() { 61 | return mChunkSize; 62 | } 63 | 64 | @Override 65 | public int getType() { 66 | return TAG; 67 | } 68 | 69 | @Override 70 | public void setSize(int size) { 71 | // TODO Auto-generated method stub 72 | 73 | } 74 | 75 | @Override 76 | public void setType(int type) { 77 | // TODO Auto-generated method stub 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/BNSNode.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | import java.io.IOException; 4 | 5 | public class BNSNode extends BXMLNode { 6 | private final int TAG_START = 0x00100100; 7 | private final int TAG_END = 0x00100101; 8 | 9 | private int mPrefix; 10 | private int mUri; 11 | 12 | public void checkStartTag(int tag) throws IOException{ 13 | checkTag(TAG_START, tag); 14 | } 15 | 16 | public void checkEndTag(int tag) throws IOException{ 17 | checkTag(TAG_END, tag); 18 | } 19 | 20 | @SuppressWarnings("unused") 21 | public void readStart(IntReader reader) throws IOException{ 22 | super.readStart(reader); 23 | 24 | int ffffx0 = reader.readInt(); //unused int value(0xFFFF) 25 | mPrefix = reader.readInt(); 26 | mUri = reader.readInt(); 27 | } 28 | 29 | @SuppressWarnings("unused") 30 | public void readEnd(IntReader reader) throws IOException{ 31 | super.readEnd(reader); 32 | 33 | int ffffx0 = reader.readInt();//skip unused value 34 | int prefix = reader.readInt(); 35 | int uri = reader.readInt(); 36 | 37 | if((prefix != mPrefix) || (uri != mUri) ){ 38 | throw new IOException("Invalid end element"); 39 | } 40 | } 41 | 42 | public void prepare(){ 43 | //TODO line number 44 | } 45 | 46 | public void writeStart(IntWriter writer) throws IOException{ 47 | writer.writeInt(TAG_START); 48 | super.writeStart(writer); 49 | 50 | writer.writeInt(0xFFFFFFFF); 51 | writer.writeInt(mPrefix); 52 | writer.writeInt(mUri); 53 | } 54 | 55 | public void writeEnd(IntWriter writer) throws IOException{ 56 | writer.writeInt(TAG_END); 57 | super.writeEnd(writer); 58 | 59 | writer.writeInt(0xFFFFFFFF); 60 | writer.writeInt(mPrefix); 61 | writer.writeInt(mUri); 62 | } 63 | 64 | public int getPrefix(){ 65 | return mPrefix; 66 | } 67 | 68 | public int getUri(){ 69 | return mUri; 70 | } 71 | 72 | @Override 73 | public void accept(IVisitor v) { 74 | v.visit(this); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/BXMLNode.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | 4 | import com.zzzmode.apkeditor.axmleditor.utils.Pair; 5 | 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | 11 | public abstract class BXMLNode implements IVisitable{ 12 | public Pair mChunkSize = new Pair(); 13 | public Pair mLineNumber= new Pair(); 14 | { 15 | mLineNumber.first = 0; 16 | mLineNumber.second = 0; 17 | } 18 | private List mChild; 19 | 20 | public void checkTag(int expect, int value) throws IOException{ 21 | if(value != expect){ 22 | throw new IOException("Can't read current node"); 23 | } 24 | } 25 | 26 | public void readStart(IntReader reader) throws IOException{ 27 | mChunkSize.first = reader.readInt(); 28 | mLineNumber.first = reader.readInt(); 29 | } 30 | 31 | public void readEnd(IntReader reader) throws IOException{ 32 | mChunkSize.second = reader.readInt(); 33 | mLineNumber.second = reader.readInt(); 34 | } 35 | 36 | public void writeStart(IntWriter writer) throws IOException{ 37 | writer.writeInt(mChunkSize.first); 38 | writer.writeInt(mLineNumber.first); 39 | } 40 | 41 | public void writeEnd(IntWriter writer) throws IOException{ 42 | writer.writeInt(mChunkSize.second); 43 | writer.writeInt(mLineNumber.second); 44 | } 45 | 46 | public boolean hasChild(){ 47 | return (mChild != null && !mChild.isEmpty()); 48 | } 49 | 50 | public List getChildren(){ 51 | return mChild; 52 | } 53 | 54 | public void addChild(BXMLNode node){ 55 | if(mChild == null) mChild = new ArrayList(); 56 | if(node != null){ 57 | mChild.add(node); 58 | } 59 | } 60 | 61 | public abstract void prepare(); 62 | 63 | public Pair getSize(){ 64 | return mChunkSize; 65 | } 66 | 67 | public Pair getLineNumber(){ 68 | return mLineNumber; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### OSX template 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must end with two \r 8 | Icon 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | 21 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk 27 | 28 | 29 | ### JetBrains template 30 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion 31 | 32 | *.iml 33 | 34 | ## Directory-based project format: 35 | .idea/ 36 | # if you remove the above rule, at least ignore the following: 37 | 38 | # User-specific stuff: 39 | # .idea/workspace.xml 40 | # .idea/tasks.xml 41 | # .idea/dictionaries 42 | 43 | # Sensitive or high-churn files: 44 | # .idea/dataSources.ids 45 | # .idea/dataSources.xml 46 | # .idea/sqlDataSources.xml 47 | # .idea/dynamic.xml 48 | # .idea/uiDesigner.xml 49 | 50 | # Gradle: 51 | # .idea/gradle.xml 52 | # .idea/libraries 53 | 54 | # Mongo Explorer plugin: 55 | # .idea/mongoSettings.xml 56 | 57 | ## File-based project format: 58 | *.ipr 59 | *.iws 60 | 61 | ## Plugin-specific files: 62 | 63 | # IntelliJ 64 | /out/ 65 | 66 | # mpeltonen/sbt-idea plugin 67 | .idea_modules/ 68 | 69 | # JIRA plugin 70 | atlassian-ide-plugin.xml 71 | 72 | # Crashlytics plugin (for Android Studio and IntelliJ) 73 | com_crashlytics_export_strings.xml 74 | crashlytics.properties 75 | crashlytics-build.properties 76 | 77 | 78 | ### Gradle template 79 | .gradle 80 | build/ 81 | 82 | # Ignore Gradle GUI config 83 | gradle-app.setting 84 | 85 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 86 | !gradle-wrapper.jar 87 | 88 | 89 | ### Java template 90 | *.class 91 | 92 | # Mobile Tools for Java (J2ME) 93 | .mtj.tmp/ 94 | 95 | # Package Files # 96 | *.jar 97 | *.war 98 | *.ear 99 | 100 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 101 | hs_err_pid* 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/Decoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.zzzmode.apkeditor.utils; 19 | 20 | /** 21 | * Provides the highest level of abstraction for Decoders. 22 | *

23 | * This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface. 24 | * Allows a user to pass a generic Object to any Decoder implementation in the codec package. 25 | *

26 | * One of the two interfaces at the center of the codec package. 27 | * 28 | * @version $Id$ 29 | */ 30 | public interface Decoder { 31 | 32 | /** 33 | * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will 34 | * try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a 35 | * {@link ClassCastException} occurs this decode method will throw a DecoderException. 36 | * 37 | * @param source 38 | * the object to decode 39 | * @return a 'decoded" object 40 | * @throws DecoderException 41 | * a decoder exception can be thrown for any number of reasons. Some good candidates are that the 42 | * parameter passed to this method is null, a param cannot be cast to the appropriate type for a 43 | * specific encoder. 44 | */ 45 | Object decode(Object source) throws DecoderException; 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/utils/NodeValue.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.utils; 2 | 3 | /** 4 | * Created by zl on 15/9/9. 5 | */ 6 | public final class NodeValue { 7 | private NodeValue(){} 8 | 9 | public static final String NAME="name"; 10 | public static final String VALUE="value"; 11 | public static final String LABEL="label"; 12 | 13 | public static final class Manifest{ 14 | public static final String VERSION_CODE="versionCode"; 15 | public static final String VERSION_NAME="versionName"; 16 | public static final String INSTALL_LOCATION="installLocation"; 17 | public static final String SHARDE_USER_ID="sharedUserId"; 18 | public static final String SHARED_USER_LABEL="sharedUserLabel"; 19 | public static final String PACKAGE="package"; 20 | } 21 | 22 | public static final class UsesSDK{ 23 | public static final String MAX_SDK_VERSION="maxSdkVersion"; 24 | public static final String MIN_SDK_VERSION="minSdkVersion"; 25 | public static final String TARGET_SDK_VERSION="targetSdkVersion"; 26 | } 27 | 28 | public static final class UsesPermission{ 29 | public static final String NAME="name"; 30 | public static final String MAX_SDK_VERSION="maxSdkVersion"; 31 | } 32 | 33 | public static final class MetaData{ 34 | public static final String NAME="name"; 35 | public static final String VALUE="value"; 36 | public static final String RESOURCE="resource"; 37 | } 38 | 39 | 40 | public static final class Application{ 41 | public static final String NAME="name"; 42 | public static final String VALUE="value"; 43 | public static final String LABEL="label"; 44 | public static final String theme="theme"; 45 | public static final String icon="icon"; 46 | public static final String persistent="persistent"; 47 | public static final String allowBackup="allowBackup"; 48 | public static final String largeHeap="largeHeap"; 49 | public static final String debuggable="debuggable"; 50 | public static final String hardwareAccelerated="hardwareAccelerated"; 51 | public static final String fullBackupOnly="fullBackupOnly"; 52 | public static final String vmSafeMode="vmSafeMode"; 53 | public static final String enabled="enabled"; 54 | public static final String description="description"; 55 | public static final String process="process"; 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/BXMLTree.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | import com.zzzmode.apkeditor.axmleditor.utils.Pair; 4 | 5 | import java.io.IOException; 6 | import java.util.Stack; 7 | 8 | 9 | public class BXMLTree implements IAXMLSerialize{ 10 | private final int NS_START = 0x00100100; 11 | private final int NS_END = 0x00100101; 12 | private final int NODE_START= 0x00100102; 13 | private final int NODE_END = 0x00100103; 14 | private final int TEXT = 0x00100104; 15 | 16 | private Stack mVisitor; 17 | private BNSNode mRoot; 18 | private int mSize; 19 | 20 | public BXMLTree(){ 21 | mRoot = new BNSNode(); 22 | mVisitor = new Stack(); 23 | } 24 | 25 | public void print(IVisitor visitor){ 26 | mRoot.accept(visitor); 27 | } 28 | 29 | public void write(IntWriter writer) throws IOException{ 30 | write(mRoot, writer); 31 | } 32 | 33 | public void prepare(){ 34 | mSize = 0; 35 | prepare(mRoot); 36 | } 37 | 38 | private void write(BXMLNode node, IntWriter writer) throws IOException{ 39 | node.writeStart(writer); 40 | 41 | if(node.hasChild()){ 42 | for(BXMLNode child : node.getChildren()){ 43 | write(child, writer); 44 | } 45 | } 46 | node.writeEnd(writer); 47 | } 48 | 49 | private void prepare(BXMLNode node){ 50 | node.prepare(); 51 | Pair p = node.getSize(); 52 | mSize += p.first + p.second; 53 | 54 | if(node.hasChild()){ 55 | for(BXMLNode child:node.getChildren()){ 56 | prepare(child); 57 | } 58 | } 59 | } 60 | 61 | public int getSize(){ 62 | return mSize; 63 | } 64 | 65 | public BXMLNode getRoot(){ 66 | return mRoot; 67 | } 68 | 69 | public void read(IntReader reader) throws IOException{ 70 | mRoot.checkStartTag(NS_START); 71 | mVisitor.push(mRoot); 72 | mRoot.readStart(reader); 73 | 74 | int chunkType; 75 | 76 | end:while(true){ 77 | chunkType = reader.readInt(); 78 | 79 | switch(chunkType){ 80 | case NODE_START: 81 | { 82 | BTagNode node = new BTagNode(); 83 | node.checkStartTag(NODE_START); 84 | BXMLNode parent = mVisitor.peek(); 85 | parent.addChild(node); 86 | mVisitor.push(node); 87 | 88 | node.readStart(reader); 89 | } 90 | break; 91 | case NODE_END: 92 | { 93 | BTagNode node = (BTagNode)mVisitor.pop(); 94 | node.checkEndTag(NODE_END); 95 | node.readEnd(reader); 96 | } 97 | break; 98 | case TEXT: 99 | { 100 | System.out.println("Hello Text"); 101 | 102 | } 103 | break; 104 | case NS_END: 105 | break end; 106 | } 107 | } 108 | 109 | if( !mRoot.equals(mVisitor.pop())){ 110 | throw new IOException("doc has invalid end"); 111 | } 112 | 113 | mRoot.checkEndTag(chunkType); 114 | mRoot.readEnd(reader); 115 | } 116 | 117 | @Override 118 | public int getType() { 119 | return 0; 120 | } 121 | 122 | @Override 123 | public void setSize(int size) { 124 | } 125 | 126 | @Override 127 | public void setType(int type) { 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/IntWriter.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.nio.ByteBuffer; 7 | import java.nio.ByteOrder; 8 | 9 | public class IntWriter implements Closeable{ 10 | public IntWriter() { 11 | } 12 | public IntWriter(OutputStream stream,boolean bigEndian) { 13 | reset(stream,bigEndian); 14 | } 15 | 16 | public final void reset(OutputStream stream,boolean bigEndian) { 17 | m_stream=stream; 18 | m_bigEndian=bigEndian; 19 | m_position=0; 20 | 21 | ByteOrder order = m_bigEndian ? ByteOrder.BIG_ENDIAN: ByteOrder.LITTLE_ENDIAN; 22 | shortBB.order(order); 23 | intBB.order(order); 24 | } 25 | 26 | public final void close() { 27 | if (m_stream==null) { 28 | return; 29 | } 30 | try { 31 | m_stream.flush(); 32 | m_stream.close(); 33 | } 34 | catch (IOException e) { 35 | } 36 | reset(null,false); 37 | } 38 | 39 | public final OutputStream getStream() { 40 | return m_stream; 41 | } 42 | 43 | public final boolean isBigEndian() { 44 | return m_bigEndian; 45 | } 46 | public final void setBigEndian(boolean bigEndian) { 47 | m_bigEndian=bigEndian; 48 | } 49 | 50 | public final void writeByte(byte b) throws IOException { 51 | m_stream.write(b); 52 | m_position += 1; 53 | } 54 | 55 | public final int writeShort(short s) throws IOException { 56 | shortBB.clear(); 57 | shortBB.putShort(s); 58 | 59 | m_stream.write(shortBB.array()); 60 | m_position += 2; 61 | 62 | return 2; 63 | } 64 | 65 | public final int writeInt(int i) throws IOException { 66 | intBB.clear(); 67 | intBB.putInt(i); 68 | 69 | m_stream.write(intBB.array()); 70 | m_position += 4; 71 | 72 | return 4; 73 | } 74 | 75 | public final void writeIntArray(int[] array) throws IOException { 76 | for(int i : array){ 77 | writeInt(i); 78 | } 79 | } 80 | 81 | public final void writeIntArray(int[] array,int offset,int length) throws IOException { 82 | int limit = offset + length; 83 | for(int i = offset; i< limit; i++){ 84 | writeInt(i); 85 | } 86 | } 87 | 88 | public final int writeByteArray(byte[] array) throws IOException { 89 | m_stream.write(array); 90 | m_position += array.length; 91 | 92 | return array.length; 93 | } 94 | 95 | public final void skip(int n, byte def) throws IOException { 96 | for(int i =0; i< n; i++){ 97 | m_stream.write(def); 98 | } 99 | 100 | m_position += n; 101 | } 102 | 103 | public final void skipIntFFFF() throws IOException { 104 | writeInt(Integer.MAX_VALUE); 105 | } 106 | 107 | public final void skipInt0000() throws IOException { 108 | writeInt(0); 109 | } 110 | 111 | public final int getPosition() { 112 | return m_position; 113 | } 114 | 115 | /////////////////////////////////// data 116 | 117 | private OutputStream m_stream; 118 | private boolean m_bigEndian; 119 | private int m_position; 120 | 121 | private ByteBuffer shortBB = ByteBuffer.allocate(2); 122 | private ByteBuffer intBB = ByteBuffer.allocate(4); 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/DecoderException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.zzzmode.apkeditor.utils; 19 | 20 | /** 21 | * Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder} 22 | * encounters a decoding specific exception such as invalid data, or characters outside of the expected range. 23 | * 24 | * @version $Id$ 25 | */ 26 | public class DecoderException extends Exception { 27 | 28 | /** 29 | * Declares the Serial Version Uid. 30 | * 31 | * @see Always Declare Serial Version Uid 32 | */ 33 | private static final long serialVersionUID = 1L; 34 | 35 | /** 36 | * Constructs a new exception with null as its detail message. The cause is not initialized, and may 37 | * subsequently be initialized by a call to {@link #initCause}. 38 | * 39 | * @since 1.4 40 | */ 41 | public DecoderException() { 42 | super(); 43 | } 44 | 45 | /** 46 | * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently 47 | * be initialized by a call to {@link #initCause}. 48 | * 49 | * @param message 50 | * The detail message which is saved for later retrieval by the {@link #getMessage()} method. 51 | */ 52 | public DecoderException(final String message) { 53 | super(message); 54 | } 55 | 56 | /** 57 | * Constructs a new exception with the specified detail message and cause. 58 | *

59 | * Note that the detail message associated with cause is not automatically incorporated into this 60 | * exception's detail message. 61 | * 62 | * @param message 63 | * The detail message which is saved for later retrieval by the {@link #getMessage()} method. 64 | * @param cause 65 | * The cause which is saved for later retrieval by the {@link #getCause()} method. A null 66 | * value is permitted, and indicates that the cause is nonexistent or unknown. 67 | * @since 1.4 68 | */ 69 | public DecoderException(final String message, final Throwable cause) { 70 | super(message, cause); 71 | } 72 | 73 | /** 74 | * Constructs a new exception with the specified cause and a detail message of (cause==null ? 75 | * null : cause.toString()) (which typically contains the class and detail message of cause). 76 | * This constructor is useful for exceptions that are little more than wrappers for other throwables. 77 | * 78 | * @param cause 79 | * The cause which is saved for later retrieval by the {@link #getCause()} method. A null 80 | * value is permitted, and indicates that the cause is nonexistent or unknown. 81 | * @since 1.4 82 | */ 83 | public DecoderException(final Throwable cause) { 84 | super(cause); 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/EncoderException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.zzzmode.apkeditor.utils; 19 | 20 | /** 21 | * Thrown when there is a failure condition during the encoding process. This exception is thrown when an 22 | * {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum, 23 | * characters outside of the expected range. 24 | * 25 | * @version $Id$ 26 | */ 27 | public class EncoderException extends Exception { 28 | 29 | /** 30 | * Declares the Serial Version Uid. 31 | * 32 | * @see Always Declare Serial Version Uid 33 | */ 34 | private static final long serialVersionUID = 1L; 35 | 36 | /** 37 | * Constructs a new exception with null as its detail message. The cause is not initialized, and may 38 | * subsequently be initialized by a call to {@link #initCause}. 39 | * 40 | * @since 1.4 41 | */ 42 | public EncoderException() { 43 | super(); 44 | } 45 | 46 | /** 47 | * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently 48 | * be initialized by a call to {@link #initCause}. 49 | * 50 | * @param message 51 | * a useful message relating to the encoder specific error. 52 | */ 53 | public EncoderException(final String message) { 54 | super(message); 55 | } 56 | 57 | /** 58 | * Constructs a new exception with the specified detail message and cause. 59 | * 60 | *

61 | * Note that the detail message associated with cause is not automatically incorporated into this 62 | * exception's detail message. 63 | *

64 | * 65 | * @param message 66 | * The detail message which is saved for later retrieval by the {@link #getMessage()} method. 67 | * @param cause 68 | * The cause which is saved for later retrieval by the {@link #getCause()} method. A null 69 | * value is permitted, and indicates that the cause is nonexistent or unknown. 70 | * @since 1.4 71 | */ 72 | public EncoderException(final String message, final Throwable cause) { 73 | super(message, cause); 74 | } 75 | 76 | /** 77 | * Constructs a new exception with the specified cause and a detail message of (cause==null ? 78 | * null : cause.toString()) (which typically contains the class and detail message of cause). 79 | * This constructor is useful for exceptions that are little more than wrappers for other throwables. 80 | * 81 | * @param cause 82 | * The cause which is saved for later retrieval by the {@link #getCause()} method. A null 83 | * value is permitted, and indicates that the cause is nonexistent or unknown. 84 | * @since 1.4 85 | */ 86 | public EncoderException(final Throwable cause) { 87 | super(cause); 88 | } 89 | } -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.utils; 2 | 3 | import java.io.*; 4 | import java.nio.channels.FileChannel; 5 | 6 | /** 7 | * Created by zl on 15/9/7. 8 | */ 9 | public class FileUtils { 10 | private static final long FILE_COPY_BUFFER_SIZE = 1024 * 1024 * 1; 11 | 12 | public static FileInputStream openInputStream(final File file) throws IOException { 13 | if (file.exists()) { 14 | if (file.isDirectory()) { 15 | throw new IOException("File '" + file + "' exists but is a directory"); 16 | } 17 | if (file.canRead() == false) { 18 | throw new IOException("File '" + file + "' cannot be read"); 19 | } 20 | } else { 21 | throw new FileNotFoundException("File '" + file + "' does not exist"); 22 | } 23 | return new FileInputStream(file); 24 | } 25 | 26 | public static void copyFile(final File srcFile, final File destFile) throws IOException { 27 | checkFileRequirements(srcFile, destFile); 28 | if (srcFile.isDirectory()) { 29 | throw new IOException("Source '" + srcFile + "' exists but is a directory"); 30 | } 31 | if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { 32 | throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); 33 | } 34 | final File parentFile = destFile.getParentFile(); 35 | if (parentFile != null) { 36 | if (!parentFile.mkdirs() && !parentFile.isDirectory()) { 37 | throw new IOException("Destination '" + parentFile + "' directory cannot be created"); 38 | } 39 | } 40 | if (destFile.exists() && destFile.canWrite() == false) { 41 | throw new IOException("Destination '" + destFile + "' exists but is read-only"); 42 | } 43 | doCopyFile(srcFile, destFile); 44 | } 45 | 46 | private static void doCopyFile(final File srcFile, final File destFile) 47 | throws IOException { 48 | if (destFile.exists() && destFile.isDirectory()) { 49 | throw new IOException("Destination '" + destFile + "' exists but is a directory"); 50 | } 51 | 52 | FileInputStream fis = null; 53 | FileOutputStream fos = null; 54 | FileChannel input = null; 55 | FileChannel output = null; 56 | try { 57 | fis = new FileInputStream(srcFile); 58 | fos = new FileOutputStream(destFile); 59 | input = fis.getChannel(); 60 | output = fos.getChannel(); 61 | final long size = input.size(); // TODO See IO-386 62 | long pos = 0; 63 | long count = 0; 64 | while (pos < size) { 65 | final long remain = size - pos; 66 | count = remain > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : remain; 67 | final long bytesCopied = output.transferFrom(input, pos, count); 68 | if (bytesCopied == 0) { // IO-385 - can happen if file is truncated after caching the size 69 | break; // ensure we don't loop forever 70 | } 71 | pos += bytesCopied; 72 | } 73 | } finally { 74 | IOUtils.closeQuietly(output, fos, input, fis); 75 | } 76 | 77 | final long srcLen = srcFile.length(); // TODO See IO-386 78 | final long dstLen = destFile.length(); // TODO See IO-386 79 | if (srcLen != dstLen) { 80 | throw new IOException("Failed to copy full contents from '" + 81 | srcFile + "' to '" + destFile + "' Expected length: " + srcLen + " Actual: " + dstLen); 82 | } 83 | 84 | } 85 | 86 | private static void checkFileRequirements(File src, File dest) throws FileNotFoundException { 87 | if (src == null) { 88 | throw new NullPointerException("Source must not be null"); 89 | } 90 | if (dest == null) { 91 | throw new NullPointerException("Destination must not be null"); 92 | } 93 | if (!src.exists()) { 94 | throw new FileNotFoundException("Source '" + src + "' does not exist"); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/HexDumpEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Ken Ellinwood. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | package com.zzzmode.apkeditor.utils; 19 | 20 | import java.io.ByteArrayOutputStream; 21 | import java.io.IOException; 22 | import java.util.Arrays; 23 | 24 | /** 25 | * Produces the classic hex dump with an address column, hex data 26 | * section (16 bytes per row) and right-column printable character dislpay. 27 | */ 28 | public class HexDumpEncoder { 29 | 30 | static final HexEncoder encoder = new HexEncoder(); 31 | 32 | public static String encode(byte[] data) { 33 | ByteArrayOutputStream baos =null; 34 | try { 35 | baos= new ByteArrayOutputStream(); 36 | encoder.encode(data, 0, data.length, baos); 37 | byte[] hex = baos.toByteArray(); 38 | 39 | StringBuilder hexDumpOut = new StringBuilder(); 40 | 41 | StringBuilder hexOut = new StringBuilder(); 42 | StringBuilder chrOut = new StringBuilder(); 43 | 44 | for (int i = 0; i < hex.length; i += 32) { 45 | 46 | int max = Math.min(i + 32, hex.length); 47 | 48 | hexOut.setLength(0); 49 | chrOut.setLength(0); 50 | 51 | //hexOut.append(String.format("%08x: ", (i / 2))); 52 | //System.out.println(format08x(i / 2)); 53 | hexOut.append(format08x(i / 2)).append(':').append(' '); 54 | for (int j = i; j < max; j += 2) { 55 | hexOut.append((char) hex[j]); 56 | hexOut.append((char) hex[j + 1]); 57 | if ((j + 2) % 4 == 0) hexOut.append(' '); 58 | 59 | int dataChar = data[j / 2]; 60 | if (dataChar >= 32 && dataChar < 127) 61 | chrOut.append((char) dataChar); 62 | else 63 | chrOut.append('.'); 64 | 65 | } 66 | 67 | hexDumpOut.append(hexOut.toString()); 68 | for (int k = hexOut.length(); k < 50; k++) 69 | hexDumpOut.append(' '); 70 | hexDumpOut.append(" "); 71 | hexDumpOut.append(chrOut); 72 | hexDumpOut.append('\n'); 73 | } 74 | 75 | return hexDumpOut.toString(); 76 | } catch (IOException x) { 77 | throw new IllegalStateException(x.getClass().getName() + ": " + x.getMessage()); 78 | }finally { 79 | try { 80 | if (baos != null) { 81 | baos.close(); 82 | } 83 | }catch (Exception e){ 84 | } 85 | } 86 | } 87 | 88 | /** 89 | * replace String.format("%08x",int), 90 | * @param value 91 | * @return 92 | */ 93 | private static String format08x(int value){ 94 | char[] buf = new char[32]; 95 | final int shift=4; 96 | int charPos = 32; 97 | final int fillLength=8; 98 | final char fillChar='0'; 99 | final int radix = 1 << shift; 100 | final int mask = radix - 1; 101 | do { 102 | buf[--charPos] = HexEncoder.digits[value & mask]; 103 | value >>>= shift; 104 | } while (value != 0); 105 | 106 | char[] c = new char[fillLength]; 107 | char[] s = Arrays.copyOfRange(buf, charPos, 32); 108 | final int len = s.length; 109 | final int fl = fillLength - len; 110 | for (int i = 0; i < fl; i++) { 111 | c[i] = fillChar; 112 | } 113 | System.arraycopy(s, 0, c, fl, len); 114 | return new String(c); 115 | } 116 | } -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/editor/MetaDataEditor.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.editor; 2 | 3 | import com.zzzmode.apkeditor.axmleditor.decode.AXMLDoc; 4 | import com.zzzmode.apkeditor.axmleditor.decode.BTagNode; 5 | import com.zzzmode.apkeditor.axmleditor.decode.BXMLNode; 6 | import com.zzzmode.apkeditor.axmleditor.decode.StringBlock; 7 | import com.zzzmode.apkeditor.axmleditor.utils.TypedValue; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * 添加或修改 meta-data 信息 13 | * 14 | * MetaDataEditor metaDataEditor = new MetaDataEditor(doc); 15 | * metaDataEditor.setEditorInfo(new MetaDataEditor.EditorInfo("UMENG_CHANNEL", "apkeditor")); // meta-data name 和value 16 | * metaDataEditor.commit(); 17 | * 18 | * Created by zl on 15/9/8. 19 | */ 20 | public class MetaDataEditor extends BaseEditor { 21 | 22 | public MetaDataEditor(AXMLDoc doc) { 23 | super(doc); 24 | setEditor(NAME,VALUE); 25 | } 26 | 27 | private int meta_data; 28 | 29 | 30 | @Override 31 | public String getEditorName() { 32 | return NODE_METADATA; 33 | } 34 | 35 | @Override 36 | protected void editor() { 37 | BXMLNode application = doc.getApplicationNode(); //manifest node 38 | List children = application.getChildren(); 39 | 40 | BTagNode meta_data = (BTagNode) findNode(); 41 | 42 | //如果有 直接修改 43 | if(meta_data != null){ 44 | meta_data.setAttrStringForKey(attr_value, editorInfo.metaValue_Index); 45 | }else{ 46 | BTagNode.Attribute name_attr = new BTagNode.Attribute(namespace, attr_name, TypedValue.TYPE_STRING); 47 | name_attr.setString(editorInfo.metaName_Index); 48 | BTagNode.Attribute value_attr = new BTagNode.Attribute(namespace, attr_value, TypedValue.TYPE_STRING); 49 | value_attr.setString(editorInfo.metaValue_Index); 50 | 51 | //没有 新建节点插入 52 | meta_data = new BTagNode(-1, this.meta_data); 53 | meta_data.setAttribute(name_attr); 54 | meta_data.setAttribute(value_attr); 55 | children.add(meta_data); 56 | } 57 | doc.getStringBlock().setString(editorInfo.metaValue_Index, editorInfo.metaValue); 58 | } 59 | 60 | @Override 61 | protected BXMLNode findNode() { 62 | BXMLNode application = doc.getApplicationNode(); //manifest node 63 | List children = application.getChildren(); 64 | 65 | BTagNode meta_data = null; 66 | 67 | end:for(BXMLNode node : children){ 68 | BTagNode m = (BTagNode)node; 69 | //it's a risk that the value for "android:name" maybe not String 70 | if((this.meta_data == m.getName()) && (m.getAttrStringForKey(attr_name) == editorInfo.metaName_Index)){ 71 | meta_data = m; 72 | break end; 73 | } 74 | } 75 | return meta_data; 76 | } 77 | 78 | 79 | @Override 80 | protected void registStringBlock(StringBlock sb) { 81 | namespace = sb.putString(NAME_SPACE); 82 | meta_data = sb.putString(NODE_METADATA); 83 | 84 | attr_name = sb.putString(NAME); 85 | attr_value = sb.putString(VALUE); 86 | 87 | // if(metaName_Value != null) 88 | // meta_name = sb.putString(metaName_Value); 89 | 90 | 91 | editorInfo.metaName_Index=sb.putString(editorInfo.metaName); 92 | 93 | editorInfo.metaValue_Index=sb.addString(editorInfo.metaValue); 94 | 95 | // if(metaName_Value !=null && meta_value == -1){ 96 | // if(metaValue_Value == null){ 97 | // metaValue_Value=""; 98 | // } 99 | // 100 | // meta_value = sb.addString(metaValue_Value);//now we have a seat in StringBlock 101 | // } 102 | } 103 | 104 | public static class EditorInfo{ 105 | private String metaName; 106 | private String metaValue; 107 | 108 | private int metaName_Index; 109 | private int metaValue_Index; 110 | 111 | private boolean metaNameHasEditor; 112 | private boolean metaValueHasEditor; 113 | 114 | public EditorInfo(){} 115 | 116 | public EditorInfo(String metaName, String metaValue) { 117 | this.metaName = metaName; 118 | this.metaValue = metaValue; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/apksigner/KeyHelper.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.apksigner; 2 | 3 | 4 | import com.zzzmode.apkeditor.utils.Base64; 5 | 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.net.URISyntaxException; 10 | import java.security.Key; 11 | import java.security.KeyStore; 12 | 13 | /** 14 | * 用于生成 PrivateKey 和 SigPrefix 15 | * 用法,先用keytool生成keystore,在用这个keystore签名任意一个apk, 16 | * 解压出这个apk的META-INF/CERT.RSA文件 17 | * 复制这生成的keystore文件和CERT.RSA到src/resources目录 18 | */ 19 | public final class KeyHelper { 20 | 21 | /** 22 | * 生成keystore 命令 23 | * keytool -genkey -alias mytestkey -keyalg RSA -keysize 512 -validity 40000 -keystore demo.keystore 24 | * 25 | * alias mytestkey 26 | * pwd 123456 27 | * 28 | * 签名apk 29 | * jarsigner -verbose -keystore demo.keystore -digestalg SHA1 -sigalg sha1withrsa -signedjar signed.apk unsign.apk mytestkey 30 | * 31 | * 验证apk是否签名成功 32 | * jarsigner -verify ~/sign.apk 33 | */ 34 | 35 | 36 | public static void main(String[] args) { 37 | try { 38 | getPrivateKey(); 39 | getSigPrefix(); 40 | } catch (Exception e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | 45 | 46 | /** 47 | * 读取 PrivateKey 48 | * @throws Exception 49 | */ 50 | public static void getPrivateKey() throws Exception { 51 | 52 | String keystoreFileName = "demo.keystore"; //resources 目录下的keystore文件 53 | String keystorePassword = "123456"; 54 | String alias = "mytestkey"; 55 | String keyPassword = "123456"; 56 | 57 | KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); 58 | keystore.load(ClassLoader.getSystemClassLoader().getResourceAsStream(keystoreFileName), keystorePassword.toCharArray()); 59 | Key key = keystore.getKey(alias, keyPassword.toCharArray()); 60 | String string = new String(Base64.encodeBase64(key.getEncoded()), "UTF-8"); 61 | System.out.println("PrivateKey " + string); 62 | } 63 | 64 | /** 65 | * 签名前缀 66 | * 首先用上面生成的keystore签名任意一个apk,解压出这个apk里面 META-INF/CERT.RSA 的文件 67 | * @throws IOException 68 | */ 69 | private static void getSigPrefix() throws IOException, URISyntaxException { 70 | System.out.println("----------"); 71 | String rsaFileName="CERT.RSA"; 72 | File file = new File(ClassLoader.getSystemClassLoader().getResource(rsaFileName).toURI()); 73 | FileInputStream fis = new FileInputStream(file); 74 | 75 | /** 76 | * RSA-keysize signature-length 77 | # 512 64 78 | # 1024 128 79 | # 2048 256 80 | */ 81 | 82 | int same = (int) (file.length() - 64); //当前-keysize 512 83 | 84 | byte[] buff = new byte[same]; 85 | fis.read(buff, 0, same); 86 | fis.close(); 87 | 88 | String string = new String(Base64.encodeBase64(buff), "UTF-8"); 89 | System.out.println("sigPrefix -->> " + string); 90 | 91 | 92 | } 93 | 94 | 95 | public static String privateKey = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAi3iIMEMo0ogaeXFEtooya6n+FZqxyRWpiKD9jg/LE5CxdTy7RlQesmfQ+uVJvMlF4ebpOhQp+aIHOp+UxZAUfQIDAQABAkAMt7jzbaxTRkXjvQhe/MsMNjwNDEYZ5/fFlaiJQ7do2S5dtCZQ966Vb1dLZlVWjPWnR99YiCYxd5qOenyTujgBAiEAwMDm9zEFN/YkFewdi0/4bW0OONlCJWCHqkN0poLIJsECIQC5O/AnP4vr/92+tcdemfROgfmlvK/NWnuEzSRJ1uF4vQIhALTgkBxo0MfZ37T+tB619Z7h1pW8MlkWw1ggIsfaM+5BAiBHSllAUcXBW6V1S7LipvAO8xko/3jN2SAm2Wk4/fmjJQIgEKdiL/87EQkL3unusUZgLyonz7d7FjHonUARloYZboU="; 96 | public static String sigPrefix = "MIICwwYJKoZIhvcNAQcCoIICtDCCArACAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCAckwggHFMIIBb6ADAgECAgQTmjt5MA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAmNuMREwDwYDVQQIEwhzaGFuZ2hhaTERMA8GA1UEBxMIc2hhbmdoYWkxCjAIBgNVBAoTAXoxCjAIBgNVBAsTAXoxCjAIBgNVBAMTAXowIBcNMTUwOTIyMTIyMDUxWhgPMjEyNTAzMjkxMjIwNTFaMFcxCzAJBgNVBAYTAmNuMREwDwYDVQQIEwhzaGFuZ2hhaTERMA8GA1UEBxMIc2hhbmdoYWkxCjAIBgNVBAoTAXoxCjAIBgNVBAsTAXoxCjAIBgNVBAMTAXowXDANBgkqhkiG9w0BAQEFAANLADBIAkEAi3iIMEMo0ogaeXFEtooya6n+FZqxyRWpiKD9jg/LE5CxdTy7RlQesmfQ+uVJvMlF4ebpOhQp+aIHOp+UxZAUfQIDAQABoyEwHzAdBgNVHQ4EFgQUnjtnyFJuunyPb+Ez8F3wuJUxqCgwDQYJKoZIhvcNAQELBQADQQBHWscfuo5oEsCkeva0xg6Ub7qOfOUDqr1R3HJ4x5M17fN2GbBK7j0Wu7aRtabNHnEhGQNvVhLTWzrXpxQj+1/mMYHDMIHAAgEBMF8wVzELMAkGA1UEBhMCY24xETAPBgNVBAgTCHNoYW5naGFpMREwDwYDVQQHEwhzaGFuZ2hhaTEKMAgGA1UEChMBejEKMAgGA1UECxMBejEKMAgGA1UEAxMBegIEE5o7eTAJBgUrDgMCGgUAMA0GCSqGSIb3DQEBAQUABEA="; 97 | 98 | 99 | } -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/Charsets.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.zzzmode.apkeditor.utils; 18 | 19 | import java.nio.charset.Charset; 20 | import java.util.Collections; 21 | import java.util.SortedMap; 22 | import java.util.TreeMap; 23 | 24 | /** 25 | * Charsets required of every implementation of the Java platform. 26 | * 27 | * From the Java documentation 28 | * Standard charsets: 29 | *

30 | * Every implementation of the Java platform is required to support the following character encodings. Consult 31 | * the release documentation for your implementation to see if any other encodings are supported. Consult the release 32 | * documentation for your implementation to see if any other encodings are supported. 33 | *

34 | * 35 | *
    36 | *
  • US-ASCII
    37 | * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
  • 38 | *
  • ISO-8859-1
    39 | * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
  • 40 | *
  • UTF-8
    41 | * Eight-bit Unicode Transformation Format.
  • 42 | *
  • UTF-16BE
    43 | * Sixteen-bit Unicode Transformation Format, big-endian byte order.
  • 44 | *
  • UTF-16LE
    45 | * Sixteen-bit Unicode Transformation Format, little-endian byte order.
  • 46 | *
  • UTF-16
    47 | * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order 48 | * accepted on input, big-endian used on output.)
  • 49 | *
50 | * 51 | * @see Standard charsets 52 | * @since 2.3 53 | * @version $Id$ 54 | */ 55 | public class Charsets { 56 | // 57 | // This class should only contain Charset instances for required encodings. This guarantees that it will load 58 | // correctly and without delay on all Java platforms. 59 | // 60 | 61 | 62 | 63 | /** 64 | * Returns the given Charset or the default Charset if the given Charset is null. 65 | * 66 | * @param charset 67 | * A charset or null. 68 | * @return the given Charset or the default Charset if the given Charset is null 69 | */ 70 | public static Charset toCharset(final Charset charset) { 71 | return charset == null ? Charset.defaultCharset() : charset; 72 | } 73 | 74 | /** 75 | * Returns a Charset for the named charset. If the name is null, return the default Charset. 76 | * 77 | * @param charset 78 | * The name of the requested charset, may be null. 79 | * @return a Charset for the named charset 80 | * @throws java.nio.charset.UnsupportedCharsetException 81 | * If the named charset is unavailable 82 | */ 83 | public static Charset toCharset(final String charset) { 84 | return charset == null ? Charset.defaultCharset() : Charset.forName(charset); 85 | } 86 | 87 | 88 | /** 89 | *

90 | * Eight-bit Unicode Transformation Format. 91 | *

92 | *

93 | * Every implementation of the Java platform is required to support this character encoding. 94 | *

95 | * 96 | * @see Standard charsets 97 | * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} 98 | */ 99 | @Deprecated 100 | public static final Charset UTF_8 = Charset.forName("UTF-8"); 101 | } -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/IntReader.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * @author Dmitry Skiba 7 | * 8 | * Simple helper class that allows reading of integers. 9 | */ 10 | public final class IntReader implements Closeable { 11 | 12 | public IntReader() { 13 | } 14 | public IntReader(InputStream stream,boolean bigEndian) { 15 | reset(stream,bigEndian); 16 | } 17 | 18 | public final void reset(InputStream stream,boolean bigEndian) { 19 | m_stream= new BufferedInputStream(stream); 20 | m_bigEndian=bigEndian; 21 | m_position=0; 22 | } 23 | 24 | public final void close() { 25 | if (m_stream==null) { 26 | return; 27 | } 28 | try { 29 | m_stream.close(); 30 | } 31 | catch (IOException e) { 32 | } 33 | reset(null,false); 34 | } 35 | 36 | public final InputStream getStream() { 37 | return m_stream; 38 | } 39 | 40 | public final boolean isBigEndian() { 41 | return m_bigEndian; 42 | } 43 | public final void setBigEndian(boolean bigEndian) { 44 | m_bigEndian=bigEndian; 45 | } 46 | 47 | public final int readByte() throws IOException { 48 | return readInt(1); 49 | } 50 | public final int readShort() throws IOException { 51 | return readInt(2); 52 | } 53 | public final int readInt() throws IOException { 54 | return readInt(4); 55 | } 56 | 57 | public final int readInt(int length) throws IOException { 58 | if (length<0 || length>4) { 59 | throw new IllegalArgumentException(); 60 | } 61 | int result=0; 62 | if (m_bigEndian) { 63 | for (int i=(length-1)*8;i>=0;i-=8) { 64 | int b=m_stream.read(); 65 | if (b==-1) { 66 | throw new EOFException(); 67 | } 68 | m_position+=1; 69 | result|=(b<0;length-=1) { 94 | array[offset++]=readInt(); 95 | } 96 | } 97 | 98 | public final byte[] readByteArray(int length) throws IOException { 99 | byte[] array=new byte[length]; 100 | int read=m_stream.read(array); 101 | m_position+=read; 102 | if (read!=length) { 103 | throw new EOFException(); 104 | } 105 | return array; 106 | } 107 | 108 | public final void skip(int bytes) throws IOException { 109 | if (bytes<=0) { 110 | return; 111 | } 112 | long skipped=m_stream.skip(bytes); 113 | m_position+=skipped; 114 | if (skipped!=bytes) { 115 | throw new EOFException(); 116 | } 117 | } 118 | 119 | public final void skipInt() throws IOException { 120 | skip(4); 121 | } 122 | 123 | public final int available() throws IOException { 124 | return m_stream.available(); 125 | } 126 | 127 | public final int getPosition() { 128 | return m_position; 129 | } 130 | 131 | /////////////////////////////////// data 132 | 133 | private InputStream m_stream; 134 | private boolean m_bigEndian; 135 | private int m_position; 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/XMLVisitor.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | 4 | import com.zzzmode.apkeditor.axmleditor.utils.TypedValue; 5 | 6 | public class XMLVisitor implements IVisitor{ 7 | private StringBlock mStrings; 8 | private ResBlock mRes; 9 | 10 | private int depth; 11 | 12 | public XMLVisitor(StringBlock sb){ 13 | mStrings = sb; 14 | } 15 | 16 | /** 17 | * print header 18 | * print child 19 | * print tail 20 | */ 21 | @Override 22 | public void visit(BNSNode node) { 23 | int prefix = node.getPrefix(); 24 | int uri = node.getUri(); 25 | 26 | String line1 = String.format("xmlns:%s=%s", getStringAt(prefix) , getStringAt(uri)); 27 | 28 | System.out.println(line1); 29 | 30 | if(node.hasChild()){ 31 | for(BXMLNode child : node.getChildren()){ 32 | child.accept(this); 33 | } 34 | } 35 | } 36 | 37 | @Override 38 | public void visit(BTagNode node) { 39 | if(!node.hasChild()){ 40 | print("<"+ getStringAt(node.getName())); 41 | printAttribute(node.getAttribute()); 42 | print("/>"); 43 | }else{ 44 | print("<"+ getStringAt(node.getName())); 45 | depth ++; 46 | printAttribute(node.getAttribute()); 47 | print(">"); 48 | 49 | for(BXMLNode child : node.getChildren()){ 50 | child.accept(this); 51 | } 52 | depth --; 53 | print(""); 54 | } 55 | } 56 | 57 | public void visit(BTXTNode node){ 58 | print("Text node"); 59 | } 60 | 61 | private void printAttribute(BTagNode.Attribute[] attrs){ 62 | for(BTagNode.Attribute attr : attrs){ 63 | StringBuilder sb = new StringBuilder(); 64 | 65 | if(attr.hasNamespace()){ 66 | sb.append("android").append(':'); 67 | } 68 | String name = getStringAt(attr.mName); 69 | if("id".equals(name)){ 70 | System.out.println("hehe"); 71 | } 72 | sb.append(name).append('='); 73 | sb.append('\"').append(getAttributeValue(attr)).append('\"'); 74 | 75 | print(sb.toString()); 76 | } 77 | } 78 | final String intent = " "; 79 | final int step = 4; 80 | private void print(String str){ 81 | System.out.println(intent.substring(0, depth*step)+str); 82 | } 83 | 84 | private String getStringAt(int index){ 85 | return mStrings.getStringFor(index); 86 | } 87 | 88 | @SuppressWarnings("unused") 89 | private int getResIdAt(int index){ 90 | //TODO final res result in resources.arsc 91 | return mRes.getResourceIdAt(index); 92 | } 93 | 94 | private String getAttributeValue(BTagNode.Attribute attr) { 95 | int type = attr.mType >> 24; 96 | int data = attr.mValue; 97 | 98 | if (type== TypedValue.TYPE_STRING) { 99 | return mStrings.getStringFor(attr.mString); 100 | } 101 | if (type==TypedValue.TYPE_ATTRIBUTE) { 102 | return String.format("?%s%08X",getPackage(data),data); 103 | } 104 | if (type==TypedValue.TYPE_REFERENCE) { 105 | return String.format("@%s%08X",getPackage(data),data); 106 | } 107 | if (type==TypedValue.TYPE_FLOAT) { 108 | return String.valueOf(Float.intBitsToFloat(data)); 109 | } 110 | if (type==TypedValue.TYPE_INT_HEX) { 111 | return String.format("0x%08X",data); 112 | } 113 | if (type==TypedValue.TYPE_INT_BOOLEAN) { 114 | return data!=0?"true":"false"; 115 | } 116 | if (type==TypedValue.TYPE_DIMENSION) { 117 | return Float.toString(complexToFloat(data))+ 118 | DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK]; 119 | } 120 | if (type==TypedValue.TYPE_FRACTION) { 121 | return Float.toString(complexToFloat(data))+ 122 | FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK]; 123 | } 124 | if (type>=TypedValue.TYPE_FIRST_COLOR_INT && type<=TypedValue.TYPE_LAST_COLOR_INT) { 125 | return String.format("#%08X",data); 126 | } 127 | if (type>=TypedValue.TYPE_FIRST_INT && type<=TypedValue.TYPE_LAST_INT) { 128 | return String.valueOf(data); 129 | } 130 | return String.format("<0x%X, type 0x%02X>",data,type); 131 | } 132 | 133 | private String getPackage(int id) { 134 | if (id>>>24==1) { 135 | return "android:"; 136 | } 137 | return ""; 138 | } 139 | 140 | /////////////////////////////////// ILLEGAL STUFF, DONT LOOK :) 141 | 142 | public static float complexToFloat(int complex) { 143 | return (float)(complex & 0xFFFFFF00)*RADIX_MULTS[(complex>>4) & 3]; 144 | } 145 | 146 | private static final float RADIX_MULTS[]={ 147 | 0.00390625F,3.051758E-005F,1.192093E-007F,4.656613E-010F 148 | }; 149 | private static final String DIMENSION_UNITS[]={ 150 | "px","dip","sp","pt","in","mm","","" 151 | }; 152 | private static final String FRACTION_UNITS[]={ 153 | "%","%p","","","","","","" 154 | }; 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/editor/ApplicationInfoEditor.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.editor; 2 | 3 | import com.zzzmode.apkeditor.axmleditor.decode.AXMLDoc; 4 | import com.zzzmode.apkeditor.axmleditor.decode.BTagNode; 5 | import com.zzzmode.apkeditor.axmleditor.decode.BXMLNode; 6 | import com.zzzmode.apkeditor.axmleditor.decode.StringBlock; 7 | import com.zzzmode.apkeditor.axmleditor.utils.TypedValue; 8 | 9 | /** 10 | * 修改节点属性 11 | * 12 | * ApplicationInfoEditor applicationInfoEditor = new ApplicationInfoEditor(doc); 13 | * applicationInfoEditor.setEditorInfo(new ApplicationInfoEditor.EditorInfo("app_name", false)); //设置app name 和是否开启debuggable 14 | * applicationInfoEditor.commit(); 15 | * 16 | * Created by zl on 15/9/8. 17 | */ 18 | public class ApplicationInfoEditor extends BaseEditor { 19 | 20 | 21 | public ApplicationInfoEditor(AXMLDoc doc) { 22 | super(doc); 23 | } 24 | 25 | @Override 26 | public String getEditorName() { 27 | return NODE_APPLICATION; 28 | } 29 | 30 | @Override 31 | protected void editor() { 32 | BTagNode node = (BTagNode) findNode(); 33 | if(node != null){ 34 | final StringBlock stringBlock = doc.getStringBlock(); 35 | if(editorInfo.labelHasEditor){ 36 | node.setAttrStringForKey(editorInfo.label_Name,editorInfo.label_Value); 37 | stringBlock.setString(editorInfo.label_Value, editorInfo.label); 38 | } 39 | if(editorInfo.debuggableHasEditor) { 40 | if(editorInfo.debuggableNewEditor){ 41 | BTagNode.Attribute debug_attr = new BTagNode.Attribute(namespace, editorInfo.debuggable_Index, TypedValue.TYPE_STRING); 42 | debug_attr.setValue(TypedValue.TYPE_INT_BOOLEAN, editorInfo.debuggable ? 1 : 0); 43 | node.setAttribute(debug_attr); 44 | }else { 45 | final BTagNode.Attribute[] attributes = node.getAttribute(); 46 | for (BTagNode.Attribute attr : attributes) { 47 | if (attr.mName == editorInfo.debuggable_Index) { 48 | attr.setValue(TypedValue.TYPE_INT_BOOLEAN, editorInfo.debuggable ? 1 : 0); 49 | break; 50 | } 51 | } 52 | } 53 | stringBlock.setString(editorInfo.debuggable_Value, String.valueOf(editorInfo.debuggable)); 54 | } 55 | } 56 | } 57 | 58 | @Override 59 | protected BXMLNode findNode() { 60 | return doc.getApplicationNode(); 61 | } 62 | 63 | @Override 64 | protected void registStringBlock(StringBlock sb) { 65 | namespace = sb.putString(NAME_SPACE); 66 | 67 | attr_name = sb.putString(NAME); 68 | attr_value = sb.putString(VALUE); 69 | 70 | editorInfo.label_Name=sb.putString(EditorInfo.LABEL); 71 | 72 | if(sb.containsString(EditorInfo.DEBUGGABLE)){ 73 | editorInfo.debuggable_Index=sb.getStringMapping(EditorInfo.DEBUGGABLE); 74 | editorInfo.debuggable_Value=sb.putString(String.valueOf(editorInfo.debuggable)); 75 | editorInfo.debuggableNewEditor=false; 76 | editorInfo.debuggableHasEditor=true; 77 | }else { 78 | editorInfo.debuggableHasEditor=false; 79 | if(editorInfo.debuggable){ 80 | editorInfo.debuggable_Index=sb.addString(EditorInfo.DEBUGGABLE); 81 | editorInfo.debuggable_Value=sb.putString(String.valueOf(editorInfo.debuggable)); 82 | editorInfo.debuggableHasEditor=true; 83 | editorInfo.debuggableNewEditor=true; 84 | } 85 | } 86 | 87 | 88 | if(editorInfo.label != null){ 89 | editorInfo.labelHasEditor=true; 90 | editorInfo.label_Value=sb.addString(String.valueOf(editorInfo.label)); 91 | } 92 | 93 | } 94 | 95 | 96 | public static class EditorInfo { 97 | 98 | public static final String LABEL="label"; 99 | public static final String DEBUGGABLE="debuggable"; 100 | 101 | 102 | private String label; 103 | 104 | private boolean debuggable=false; 105 | 106 | private int label_Name; 107 | private int label_Value; 108 | 109 | private int debuggable_Index; 110 | private int debuggable_Value; 111 | 112 | private boolean labelHasEditor=false; 113 | 114 | private boolean debuggableHasEditor=false; 115 | private boolean debuggableNewEditor=false; 116 | 117 | public EditorInfo(String label,boolean debuggable) { 118 | this.label = label; 119 | this.debuggable=debuggable; 120 | } 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.zzzmode.apkeditor.utils; 19 | 20 | import java.nio.charset.Charset; 21 | 22 | /** 23 | * Converts String to and from bytes using the encodings required by the Java specification. These encodings are 24 | * specified in 25 | * Standard charsets. 26 | * 27 | *

This class is immutable and thread-safe.

28 | * 29 | * @see Standard charsets 30 | * @version $Id$ 31 | * @since 1.4 32 | */ 33 | public class StringUtils { 34 | 35 | 36 | /** 37 | * Calls {@link String#getBytes(Charset)} 38 | * 39 | * @param string 40 | * The string to encode (if null, return null). 41 | * @param charset 42 | * The {@link Charset} to encode the String 43 | * @return the encoded bytes 44 | */ 45 | private static byte[] getBytes(final String string, final Charset charset) { 46 | if (string == null) { 47 | return null; 48 | } 49 | return string.getBytes(charset); 50 | } 51 | 52 | 53 | 54 | /** 55 | * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte 56 | * array. 57 | * 58 | * @param string 59 | * the String to encode, may be null 60 | * @return encoded bytes, or null if the input string was null 61 | * @throws NullPointerException 62 | * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is 63 | * required by the Java platform specification. 64 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException 65 | * @see Standard charsets 66 | */ 67 | public static byte[] getBytesUtf8(final String string) { 68 | return getBytes(string, Charsets.UTF_8); 69 | } 70 | 71 | 72 | /** 73 | * Constructs a new String by decoding the specified array of bytes using the given charset. 74 | * 75 | * @param bytes 76 | * The bytes to be decoded into characters 77 | * @param charset 78 | * The {@link Charset} to encode the String 79 | * @return A new String decoded from the specified array of bytes using the given charset, 80 | * or null if the input byte array was null. 81 | * @throws NullPointerException 82 | * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is 83 | * required by the Java platform specification. 84 | */ 85 | private static String newString(final byte[] bytes, final Charset charset) { 86 | return bytes == null ? null : new String(bytes, charset); 87 | } 88 | 89 | 90 | /** 91 | * Constructs a new String by decoding the specified array of bytes using the UTF-8 charset. 92 | * 93 | * @param bytes 94 | * The bytes to be decoded into characters 95 | * @return A new String decoded from the specified array of bytes using the UTF-8 charset, 96 | * or null if the input byte array was null. 97 | * @throws NullPointerException 98 | * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is 99 | * required by the Java platform specification. 100 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException 101 | */ 102 | public static String newStringUtf8(final byte[] bytes) { 103 | return newString(bytes, Charsets.UTF_8); 104 | } 105 | 106 | } -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/AXMLDoc.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | import com.zzzmode.apkeditor.utils.IOUtils; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.OutputStream; 8 | import java.util.List; 9 | 10 | import static java.lang.System.out; 11 | 12 | public class AXMLDoc { 13 | private final int MAGIC_NUMBER = 0X00080003; 14 | private final int CHUNK_STRING_BLOCK = 0X001C0001; 15 | private final int CHUNK_RESOURCE_ID = 0X00080180; 16 | private final int CHUNK_XML_TREE = 0X00100100; 17 | 18 | private final String MANIFEST = "manifest"; 19 | private final String APPLICATION = "application"; 20 | 21 | private int mDocSize ; 22 | private StringBlock mStringBlock; 23 | private ResBlock mResBlock; 24 | 25 | private BXMLTree mXMLTree; 26 | 27 | private InputStream is; 28 | 29 | public AXMLDoc(){ 30 | } 31 | 32 | public StringBlock getStringBlock(){ 33 | return mStringBlock; 34 | } 35 | 36 | public ResBlock getResBlock(){ 37 | return mResBlock; 38 | } 39 | 40 | public BXMLTree getBXMLTree(){ 41 | return mXMLTree; 42 | } 43 | 44 | public BXMLNode getManifestNode(){ 45 | List children = mXMLTree.getRoot().getChildren(); 46 | 47 | for(BXMLNode node : children){ 48 | if( MANIFEST.equals( mStringBlock.getStringFor(((BTagNode)node).getName() ) )){ 49 | return node; 50 | } 51 | } 52 | 53 | return null; 54 | } 55 | 56 | public BXMLNode getApplicationNode(){ 57 | BXMLNode manifest = getManifestNode(); 58 | if(manifest == null){ 59 | return null; 60 | } 61 | 62 | for(BXMLNode node : manifest.getChildren()){ 63 | if( APPLICATION.equals( mStringBlock.getStringFor(((BTagNode)node).getName() ))){ 64 | return node; 65 | } 66 | } 67 | 68 | return null; 69 | } 70 | 71 | /** 72 | * Prepare() should be called, if any resource has changes. 73 | * @param os 74 | * @throws IOException 75 | */ 76 | public void build(OutputStream os) throws IOException{ 77 | 78 | IntWriter writer =null; 79 | try { 80 | writer = new IntWriter(os, false); 81 | mStringBlock.prepare(); 82 | mResBlock.prepare(); 83 | mXMLTree.prepare(); 84 | 85 | int base = 8; 86 | mDocSize = base + mStringBlock.getSize() + mResBlock.getSize() + mXMLTree.getSize(); 87 | 88 | writer.writeInt(MAGIC_NUMBER); 89 | writer.writeInt(mDocSize); 90 | 91 | mStringBlock.write(writer); 92 | mResBlock.write(writer); 93 | mXMLTree.write(writer); 94 | 95 | os.flush(); 96 | }catch (IOException e){ 97 | e.printStackTrace(); 98 | throw new IOException(e); 99 | }finally { 100 | IOUtils.closeQuietly(writer,os); 101 | } 102 | } 103 | 104 | public void testSize() throws IOException{ 105 | out.println("string block size:" + mStringBlock.getSize()); 106 | mStringBlock.prepare(); 107 | out.println("string block size:" + mStringBlock.getSize()); 108 | 109 | out.println("res block size:" + mResBlock.getSize()); 110 | mResBlock.prepare(); 111 | out.println("res size:" + mResBlock.getSize()); 112 | 113 | out.println("xml size:" + mXMLTree.getSize()); 114 | mXMLTree.prepare(); 115 | out.println("xml size:" + mXMLTree.getSize()); 116 | 117 | out.println("doc size:" + mDocSize); 118 | int base = 8; 119 | int size = base + mStringBlock.getSize() + mResBlock.getSize() + mXMLTree.getSize(); 120 | out.println("doc size:" + size); 121 | } 122 | 123 | public void print(){ 124 | out.println("size:" + mDocSize); 125 | mXMLTree.print(new XMLVisitor(mStringBlock)); 126 | } 127 | 128 | public void parse(InputStream is) throws Exception{ 129 | this.is=is; 130 | IntReader reader = new IntReader(is, false); 131 | 132 | int magicNum = reader.readInt(); 133 | 134 | if(magicNum != MAGIC_NUMBER){ 135 | throw new RuntimeException("Not valid AXML format"); 136 | } 137 | 138 | int size = reader.readInt(); 139 | 140 | mDocSize = size; 141 | 142 | int chunkType = reader.readInt(); 143 | 144 | if(chunkType == CHUNK_STRING_BLOCK){ 145 | parseStringBlock(reader); 146 | } 147 | 148 | chunkType = reader.readInt(); 149 | 150 | if(chunkType == CHUNK_RESOURCE_ID){ 151 | parseResourceBlock(reader); 152 | } 153 | 154 | chunkType = reader.readInt(); 155 | 156 | if(chunkType == CHUNK_XML_TREE){ 157 | parseXMLTree(reader); 158 | } 159 | } 160 | 161 | public void release(){ 162 | try{ 163 | if(is != null) 164 | is.close(); 165 | }catch (Exception e){ 166 | e.printStackTrace(); 167 | } 168 | } 169 | 170 | private void parseStringBlock(IntReader reader) throws Exception{ 171 | StringBlock block = new StringBlock(); 172 | block.read(reader); 173 | 174 | mStringBlock = block; 175 | } 176 | 177 | private void parseResourceBlock(IntReader reader) throws IOException{ 178 | ResBlock block = new ResBlock(); 179 | block.read(reader); 180 | mResBlock = block; 181 | } 182 | 183 | private void parseXMLTree(IntReader reader) throws Exception{ 184 | BXMLTree tree = new BXMLTree(); 185 | tree.read(reader); 186 | 187 | mXMLTree = tree; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/editor/PermissionEditor.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.editor; 2 | 3 | import com.zzzmode.apkeditor.axmleditor.decode.AXMLDoc; 4 | import com.zzzmode.apkeditor.axmleditor.decode.BTagNode; 5 | import com.zzzmode.apkeditor.axmleditor.decode.BXMLNode; 6 | import com.zzzmode.apkeditor.axmleditor.decode.StringBlock; 7 | import com.zzzmode.apkeditor.axmleditor.utils.TypedValue; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Iterator; 11 | import java.util.List; 12 | 13 | /** 14 | * 添加或删除apk权限 15 | * 16 | * 用法 17 | * PermissionEditor permissionEditor=new PermissionEditor(doc); 18 | * PermissionEditor.EditorInfo editorInfo=new PermissionEditor.EditorInfo(); 19 | * editorInfo.with(new PermissionEditor.PermissionOpera("android.permission.INTERNET").remove()); //删除权限 20 | * editorInfo.with(new PermissionEditor.PermissionOpera("android.permission.WRITE_SETTINGS").add()); //添加权限 21 | * //and more ... 22 | * permissionEditor.setEditorInfo(editorInfo); 23 | * 24 | * 25 | * Created by zl on 15/9/9. 26 | */ 27 | public class PermissionEditor extends BaseEditor { 28 | public PermissionEditor(AXMLDoc doc) { 29 | super(doc); 30 | } 31 | 32 | private int user_permission; 33 | 34 | @Override 35 | public String getEditorName() { 36 | return NODE_USER_PREMISSION; 37 | } 38 | 39 | @Override 40 | protected void editor() { 41 | List children = findNode().getChildren(); 42 | for (PermissionOpera opera:editorInfo.editors){ 43 | if(opera.isRemove()){ 44 | final Iterator iterator = children.iterator(); 45 | while (iterator.hasNext()){ 46 | BTagNode n= (BTagNode) iterator.next(); 47 | if((user_permission == n.getName()) && (n.getAttrStringForKey(attr_name) == opera.permissionValue_Index)){ 48 | System.out.println("删除 -->>> " + opera.permission); 49 | iterator.remove(); 50 | //doc.getStringBlock().removeString(opera.permissionValue_Index); 51 | break; 52 | } 53 | } 54 | }else if(opera.isAdd()){ 55 | BTagNode.Attribute permission_attr = new BTagNode.Attribute(namespace, attr_name, TypedValue.TYPE_STRING); 56 | permission_attr.setString(opera.permissionValue_Index); 57 | BTagNode permission_node = new BTagNode(-1, user_permission); 58 | permission_node.setAttribute(permission_attr); 59 | children.add(permission_node); 60 | System.out.println("添加 -->> "+opera.permission); 61 | doc.getStringBlock().setString(opera.permissionValue_Index, opera.permission); 62 | } 63 | } 64 | } 65 | 66 | 67 | @Override 68 | protected BXMLNode findNode() { 69 | return doc.getManifestNode(); 70 | } 71 | 72 | @Override 73 | protected void registStringBlock(StringBlock sb) { 74 | namespace = sb.putString(NAME_SPACE); 75 | user_permission = sb.putString(NODE_USER_PREMISSION); 76 | 77 | attr_name = sb.putString(NAME); 78 | 79 | final Iterator iterator = editorInfo.editors.iterator(); 80 | while (iterator.hasNext()){ 81 | final PermissionOpera opera = iterator.next(); 82 | if(opera.isAdd()){ 83 | if(sb.containsString(opera.permission)){ 84 | iterator.remove(); //添加,已经有了不处理 85 | }else { 86 | opera.permissionValue_Index=sb.addString(opera.permission); 87 | } 88 | }else if(opera.isRemove()){ 89 | if(!sb.containsString(opera.permission)){ 90 | iterator.remove(); //删除,没有不处理 91 | }else { 92 | opera.permissionValue_Index= sb.getStringMapping(opera.permission); 93 | } 94 | } 95 | } 96 | } 97 | 98 | public static class EditorInfo { 99 | private List editors=new ArrayList(); 100 | 101 | public final EditorInfo with(PermissionOpera opera){ 102 | editors.add(opera); 103 | return this; 104 | } 105 | } 106 | 107 | public static class PermissionOpera{ 108 | private static final int ADD=0x00000001; 109 | private static final int REMOVE=0x00000002; 110 | 111 | private int opera=0x00000000; 112 | 113 | private String permission; 114 | 115 | private int permissionValue_Index; 116 | 117 | public PermissionOpera(String permission){ 118 | this.permission=permission; 119 | } 120 | 121 | public final PermissionOpera add(){ 122 | opera = opera & ~ REMOVE; //移除remove 标记 123 | opera = opera | ADD; //添加add 标记 124 | return this; 125 | } 126 | 127 | public final PermissionOpera remove(){ 128 | opera = opera & ~ ADD; 129 | opera = opera | REMOVE; 130 | return this; 131 | } 132 | 133 | final boolean isAdd(){ 134 | return (opera & ADD) == ADD; 135 | } 136 | 137 | final boolean isRemove(){ 138 | return (opera & REMOVE) == REMOVE; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/ApkEditor.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor; 2 | 3 | 4 | import com.zzzmode.apkeditor.apksigner.SignApk; 5 | import com.zzzmode.apkeditor.apksigner.ZipManager; 6 | import com.zzzmode.apkeditor.axmleditor.editor.MetaDataEditor; 7 | import com.zzzmode.apkeditor.axmleditor.editor.PackageInfoEditor; 8 | import com.zzzmode.apkeditor.axmleditor.editor.PermissionEditor; 9 | import com.zzzmode.apkeditor.axmleditor.utils.NodeValue; 10 | import com.zzzmode.apkeditor.utils.FileUtils; 11 | import com.zzzmode.apkeditor.axmleditor.decode.AXMLDoc; 12 | import com.zzzmode.apkeditor.axmleditor.editor.ApplicationInfoEditor; 13 | 14 | import java.io.File; 15 | import java.io.FileInputStream; 16 | import java.io.FileOutputStream; 17 | 18 | /** 19 | * Created by zl on 15/9/11. 20 | */ 21 | public class ApkEditor { 22 | 23 | private static final String WORK_DIR; 24 | static { 25 | String dir=null; 26 | try { 27 | dir = File.createTempFile(ApkEditor.class.getName(),null).getParentFile()+"/apkeditor_work"; 28 | }catch (Throwable e){ 29 | e.printStackTrace(); 30 | throw new RuntimeException(e); 31 | }finally { 32 | WORK_DIR = dir; 33 | } 34 | 35 | } 36 | private static final String A_XML = WORK_DIR + "/AndroidManifest.xml"; 37 | 38 | public ApkEditor(String privateKey,String sigPrefix){ 39 | File file=new File(WORK_DIR); 40 | if(!file.exists()){ 41 | file.mkdirs(); 42 | } 43 | this.privateKey=privateKey; 44 | this.sigPrefix = sigPrefix; 45 | } 46 | 47 | private String privateKey; 48 | private String sigPrefix; 49 | 50 | private String origFile; 51 | private String outFile; 52 | private String appName; 53 | private String appIcon; 54 | 55 | public void setOrigFile(String origFile) { 56 | this.origFile = origFile; 57 | } 58 | 59 | public void setOutFile(String outFile) { 60 | this.outFile = outFile; 61 | } 62 | 63 | public void setAppName(String appName) { 64 | this.appName = appName; 65 | } 66 | 67 | public void setAppIcon(String appIcon) { 68 | this.appIcon = appIcon; 69 | } 70 | 71 | 72 | public String getOrigFile() { 73 | return origFile; 74 | } 75 | 76 | public String getOutFile() { 77 | return outFile; 78 | } 79 | 80 | public String getAppName() { 81 | return appName; 82 | } 83 | 84 | public String getAppIcon() { 85 | return appIcon; 86 | } 87 | 88 | public boolean create() throws Exception { 89 | File tmpFile=null; 90 | File newXML=null; 91 | try { 92 | 93 | File origAPK = new File(origFile); 94 | //复制文件 95 | tmpFile = new File(WORK_DIR+"/tmp.apk"); 96 | newXML=new File(A_XML); 97 | 98 | FileUtils.copyFile(origAPK, tmpFile); 99 | //提取AndroidManifest.xml文件 100 | ZipManager.extraZipEntry(tmpFile, new String[]{"AndroidManifest.xml"}, new String[]{A_XML}); 101 | 102 | if (appName != null) { 103 | //修改app name 104 | AXMLDoc doc = new AXMLDoc(); 105 | doc.parse(new FileInputStream(newXML)); 106 | 107 | 108 | ApplicationInfoEditor applicationInfoEditor = new ApplicationInfoEditor(doc); 109 | applicationInfoEditor.setEditorInfo(new ApplicationInfoEditor.EditorInfo(appName, false)); 110 | applicationInfoEditor.commit(); 111 | 112 | //更多修改可以在下面添加 113 | /* 114 | PackageInfoEditor packageInfoEditor = new PackageInfoEditor(doc); 115 | packageInfoEditor.setEditorInfo(new PackageInfoEditor.EditorInfo(12563, "abcde", null)); 116 | packageInfoEditor.commit(); 117 | 118 | 119 | PermissionEditor permissionEditor = new PermissionEditor(doc); 120 | permissionEditor.setEditorInfo(new PermissionEditor.EditorInfo() 121 | .with(new PermissionEditor.PermissionOpera("android.permission.ACCESS_FINE_LOCATION").remove()) 122 | .with(new PermissionEditor.PermissionOpera("android.permission.WRITE_SETTINGS").remove()) 123 | .with(new PermissionEditor.PermissionOpera("android.permission.INTERNET").add()) 124 | ); 125 | permissionEditor.commit(); 126 | 127 | MetaDataEditor metaDataEditor = new MetaDataEditor(doc); 128 | metaDataEditor.setEditorInfo(new MetaDataEditor.EditorInfo("UMENG_CHANNEL", "apkeditor")); 129 | metaDataEditor.commit(); 130 | */ 131 | 132 | doc.build(new FileOutputStream(newXML)); 133 | doc.release(); 134 | 135 | } 136 | // 替换apk 中的文件 137 | ZipManager.replaceZipEntry(tmpFile, new String[]{"AndroidManifest.xml", "res/drawable/ic_launcher.png"}, 138 | new String[]{A_XML, appIcon}); 139 | 140 | //重新签名 141 | SignApk signApk = new SignApk(privateKey,sigPrefix); 142 | 143 | boolean signed= signApk.sign(tmpFile.getAbsolutePath(), outFile); 144 | if (signed){ 145 | //verify signed apk 146 | return SignApk.verifyJar(outFile); 147 | } 148 | } catch (Exception e) { 149 | throw e; 150 | } finally { 151 | if(tmpFile!= null){ 152 | tmpFile.delete(); 153 | } 154 | if(newXML != null){ 155 | newXML.delete(); 156 | } 157 | } 158 | return false; 159 | } 160 | 161 | public static File getWorkDir(){ 162 | return new File(WORK_DIR); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/utils/HexEncoder.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.utils; 2 | 3 | /* 4 | This file is a copy of org.bouncycastle.util.encoders.HexEncoder. 5 | 6 | Please note: our license is an adaptation of the MIT X11 License and should be read as such. 7 | License 8 | 9 | Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle (http://www.bouncycastle.org) 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining 12 | a copy of this software and associated documentation files (the 13 | "Software"), to deal in the Software without restriction, including 14 | without limitation the rights to use, copy, modify, merge, publish, 15 | distribute, sublicense, and/or sell copies of the Software, and to 16 | permit persons to whom the Software is furnished to do so, subject to 17 | the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be 20 | included in all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 26 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 27 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 28 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | */ 31 | 32 | import java.io.IOException; 33 | import java.io.OutputStream; 34 | 35 | public class HexEncoder { 36 | protected final byte[] encodingTable = 37 | { 38 | (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', 39 | (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' 40 | }; 41 | 42 | final static char[] digits = { 43 | '0' , '1' , '2' , '3' , '4' , '5' , 44 | '6' , '7' , '8' , '9' , 'a' , 'b' , 45 | 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 46 | 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 47 | 'o' , 'p' , 'q' , 'r' , 's' , 't' , 48 | 'u' , 'v' , 'w' , 'x' , 'y' , 'z' 49 | }; 50 | 51 | /* 52 | * set up the decoding table. 53 | */ 54 | protected final byte[] decodingTable = new byte[128]; 55 | 56 | protected void initialiseDecodingTable() { 57 | for (int i = 0; i < encodingTable.length; i++) { 58 | decodingTable[encodingTable[i]] = (byte) i; 59 | } 60 | 61 | decodingTable['A'] = decodingTable['a']; 62 | decodingTable['B'] = decodingTable['b']; 63 | decodingTable['C'] = decodingTable['c']; 64 | decodingTable['D'] = decodingTable['d']; 65 | decodingTable['E'] = decodingTable['e']; 66 | decodingTable['F'] = decodingTable['f']; 67 | } 68 | 69 | public HexEncoder() { 70 | initialiseDecodingTable(); 71 | } 72 | 73 | /** 74 | * encode the input data producing a Hex output stream. 75 | * 76 | * @return the number of bytes produced. 77 | */ 78 | public int encode( 79 | byte[] data, 80 | int off, 81 | int length, 82 | OutputStream out) 83 | throws IOException { 84 | for (int i = off; i < (off + length); i++) { 85 | int v = data[i] & 0xff; 86 | 87 | out.write(encodingTable[(v >>> 4)]); 88 | out.write(encodingTable[v & 0xf]); 89 | } 90 | 91 | return length * 2; 92 | } 93 | 94 | private boolean ignore( 95 | char c) { 96 | return (c == '\n' || c == '\r' || c == '\t' || c == ' '); 97 | } 98 | 99 | /** 100 | * decode the Hex encoded byte data writing it to the given output stream, 101 | * whitespace characters will be ignored. 102 | * 103 | * @return the number of bytes produced. 104 | */ 105 | public int decode( 106 | byte[] data, 107 | int off, 108 | int length, 109 | OutputStream out) 110 | throws IOException { 111 | byte b1, b2; 112 | int outLen = 0; 113 | 114 | int end = off + length; 115 | 116 | while (end > off) { 117 | if (!ignore((char) data[end - 1])) { 118 | break; 119 | } 120 | 121 | end--; 122 | } 123 | 124 | int i = off; 125 | while (i < end) { 126 | while (i < end && ignore((char) data[i])) { 127 | i++; 128 | } 129 | 130 | b1 = decodingTable[data[i++]]; 131 | 132 | while (i < end && ignore((char) data[i])) { 133 | i++; 134 | } 135 | 136 | b2 = decodingTable[data[i++]]; 137 | 138 | out.write((b1 << 4) | b2); 139 | 140 | outLen++; 141 | } 142 | 143 | return outLen; 144 | } 145 | 146 | /** 147 | * decode the Hex encoded String data writing it to the given output stream, 148 | * whitespace characters will be ignored. 149 | * 150 | * @return the number of bytes produced. 151 | */ 152 | public int decode( 153 | String data, 154 | OutputStream out) 155 | throws IOException { 156 | byte b1, b2; 157 | int length = 0; 158 | 159 | int end = data.length(); 160 | 161 | while (end > 0) { 162 | if (!ignore(data.charAt(end - 1))) { 163 | break; 164 | } 165 | 166 | end--; 167 | } 168 | 169 | int i = 0; 170 | while (i < end) { 171 | while (i < end && ignore(data.charAt(i))) { 172 | i++; 173 | } 174 | 175 | b1 = decodingTable[data.charAt(i++)]; 176 | 177 | while (i < end && ignore(data.charAt(i))) { 178 | i++; 179 | } 180 | 181 | b2 = decodingTable[data.charAt(i++)]; 182 | 183 | out.write((b1 << 4) | b2); 184 | 185 | length++; 186 | } 187 | 188 | return length; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/editor/PackageInfoEditor.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.editor; 2 | 3 | import com.zzzmode.apkeditor.axmleditor.decode.AXMLDoc; 4 | import com.zzzmode.apkeditor.axmleditor.decode.BTagNode; 5 | import com.zzzmode.apkeditor.axmleditor.decode.BXMLNode; 6 | import com.zzzmode.apkeditor.axmleditor.decode.StringBlock; 7 | import com.zzzmode.apkeditor.axmleditor.utils.TypedValue; 8 | 9 | /** 10 | * 用于修改manifest节点信息,修改包信息 11 | * 12 | * PackageInfoEditor packageInfoEditor = new PackageInfoEditor(doc); 13 | * packageInfoEditor.setEditorInfo(new PackageInfoEditor.EditorInfo(12563, "ver_name_apkeditor", null)); //设置版本号、版本名和包名,不建议修改包名,会导致app 无法运行 14 | * packageInfoEditor.commit(); 15 | * 16 | * Created by zl on 15/9/8. 17 | */ 18 | public class PackageInfoEditor extends BaseEditor { 19 | 20 | public PackageInfoEditor(AXMLDoc doc) { 21 | super(doc); 22 | } 23 | 24 | private int manifest; 25 | private int verCode; 26 | private int verName; 27 | private int pkgName; 28 | 29 | 30 | @Override 31 | public String getEditorName() { 32 | return NODE_MANIFEST; 33 | } 34 | 35 | @Override 36 | protected void editor() { 37 | BTagNode node = (BTagNode) findNode(); 38 | 39 | if(node != null){ 40 | final StringBlock stringBlock = doc.getStringBlock(); 41 | 42 | if(editorInfo.verCodeHasEdit){ 43 | final BTagNode.Attribute[] attributes = node.getAttribute(); 44 | for (BTagNode.Attribute attr:attributes){ 45 | if(attr.mName == verCode){ 46 | attr.setValue(TypedValue.TYPE_INT_DEC,editorInfo.versionCode); //十进制int值直接使用 47 | } 48 | } 49 | stringBlock.setString(editorInfo.verCode_Value, String.valueOf(editorInfo.versionCode)); 50 | } 51 | 52 | if(editorInfo.verNameHasEdit){ 53 | //设置值 attr name --> new value index 54 | node.setAttrStringForKey(verName,editorInfo.verName_Value); 55 | //更新stringblock中new value 的index 56 | stringBlock.setString(editorInfo.verName_Value, editorInfo.versionName); 57 | } 58 | if(editorInfo.pkgNameHasEdit){ 59 | node.setAttrStringForKey(pkgName,editorInfo.pkgName_Value); 60 | stringBlock.setString(editorInfo.pkgName_Value, editorInfo.packageName); 61 | } 62 | 63 | } 64 | } 65 | 66 | @Override 67 | protected BXMLNode findNode() { 68 | return doc.getManifestNode(); 69 | } 70 | 71 | @Override 72 | protected void registStringBlock(StringBlock sb) { 73 | //先找到相关attr name text 对应的索引 74 | namespace = sb.putString(NAME_SPACE); 75 | manifest = sb.putString(NODE_MANIFEST); 76 | 77 | attr_name = sb.putString(NAME); 78 | attr_value = sb.putString(VALUE); 79 | 80 | verCode=sb.putString(EditorInfo.VERSIONCODE); 81 | verName=sb.putString(EditorInfo.VERSIONNAME); 82 | pkgName=sb.putString(EditorInfo.PACKAGE); 83 | 84 | //记录要修改的value 插入的位置 85 | if(editorInfo.versionCode >0){ 86 | editorInfo.verCodeHasEdit=true; 87 | editorInfo.verCode_Value=sb.addString(String.valueOf(editorInfo.versionCode)); 88 | } 89 | if(editorInfo.versionName != null){ 90 | editorInfo.verNameHasEdit=true; 91 | editorInfo.verName_Value=sb.addString(editorInfo.versionName); 92 | } 93 | if(editorInfo.packageName != null){ 94 | editorInfo.pkgNameHasEdit=true; 95 | editorInfo.pkgName_Value=sb.addString(editorInfo.packageName); 96 | } 97 | } 98 | 99 | 100 | public static class EditorInfo { 101 | 102 | public static final String VERSIONCODE="versionCode"; 103 | public static final String VERSIONNAME="versionName"; 104 | public static final String PACKAGE="package"; 105 | 106 | private int versionCode; 107 | private String versionName; 108 | private String packageName; 109 | 110 | 111 | private int verCode_Value; 112 | private int verName_Value; 113 | private int pkgName_Value; 114 | 115 | 116 | private boolean verCodeHasEdit=false; 117 | private boolean verNameHasEdit=false; 118 | private boolean pkgNameHasEdit=false; 119 | 120 | public EditorInfo(){ 121 | 122 | } 123 | 124 | public EditorInfo(int versionCode, String versionName, String packageName) { 125 | this.versionCode = versionCode; 126 | this.versionName = versionName; 127 | this.packageName = packageName; 128 | } 129 | 130 | public int getVersionCode() { 131 | return versionCode; 132 | } 133 | 134 | public void setVersionCode(int versionCode) { 135 | this.versionCode = versionCode; 136 | } 137 | 138 | public String getVersionName() { 139 | return versionName; 140 | } 141 | 142 | public void setVersionName(String versionName) { 143 | this.versionName = versionName; 144 | } 145 | 146 | public String getPackageName() { 147 | return packageName; 148 | } 149 | 150 | public void setPackageName(String packageName) { 151 | this.packageName = packageName; 152 | } 153 | 154 | 155 | @Override 156 | public String toString() { 157 | return "EditorInfo{" + 158 | "versionCode=" + versionCode + 159 | ", versionName='" + versionName + '\'' + 160 | ", packageName='" + packageName + '\'' + 161 | ", verCode_Value=" + verCode_Value + 162 | ", verName_Value=" + verName_Value + 163 | ", pkgName_Value=" + pkgName_Value + 164 | ", verCodeHasEdit=" + verCodeHasEdit + 165 | ", verNameHasEdit=" + verNameHasEdit + 166 | ", pkgNameHasEdit=" + pkgNameHasEdit + 167 | '}'; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/BTagNode.java: -------------------------------------------------------------------------------- 1 | package com.zzzmode.apkeditor.axmleditor.decode; 2 | 3 | import com.zzzmode.apkeditor.axmleditor.utils.TypedValue; 4 | 5 | import java.io.IOException; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | 10 | public class BTagNode extends BXMLNode { 11 | private final int TAG_START = 0x00100102; 12 | private final int TAG_END = 0x00100103; 13 | 14 | private int mRawNSUri; 15 | private int mRawName; 16 | 17 | private short mRawAttrCount; //(id attr)<<16 + (normal attr ?) 18 | 19 | private short mRawClassAttr; //'class=' 20 | private short mRawIdAttr; //'android:id=' 21 | private short mRawStyleAttr; //'style=' 22 | 23 | private List mRawAttrs; 24 | 25 | public BTagNode(){} 26 | public BTagNode(int ns, int name){ 27 | mRawName = name; 28 | mRawNSUri = ns; 29 | } 30 | 31 | public void checkStartTag(int tag) throws IOException{ 32 | checkTag(TAG_START, tag); 33 | } 34 | 35 | public void checkEndTag(int tag) throws IOException{ 36 | checkTag(TAG_END, tag); 37 | } 38 | 39 | @SuppressWarnings("unused") 40 | public void readStart(IntReader reader) throws IOException{ 41 | super.readStart(reader); 42 | 43 | int xffff_ffff = reader.readInt(); //unused int value(0xFFFF_FFFF) 44 | mRawNSUri = reader.readInt(); //TODO maybe not ns uri (0xFFFF) 45 | mRawName = reader.readInt(); //name for element 46 | int x0014_0014 = reader.readInt(); //TODO unknown field 47 | 48 | mRawAttrCount = (short)reader.readShort(); //attribute count 49 | 50 | mRawIdAttr = (short)reader.readShort(); //id attribute 51 | mRawClassAttr = (short)reader.readShort(); //class 52 | mRawStyleAttr = (short)reader.readShort(); 53 | 54 | if(mRawAttrCount > 0){ 55 | if(mRawName == 62 ){ 56 | System.out.println(); 57 | } 58 | mRawAttrs = new ArrayList(); 59 | int [] attrs = reader.readIntArray(mRawAttrCount*Attribute.SIZE); //namespace, name, value(string),value(type),value(data) 60 | for(int i=0; i< mRawAttrCount; i++){ 61 | mRawAttrs.add(new Attribute(subArray(attrs, i*Attribute.SIZE, Attribute.SIZE))); 62 | 63 | Attribute attr = mRawAttrs.get(i); 64 | } 65 | } 66 | } 67 | 68 | @SuppressWarnings("unused") 69 | public void readEnd(IntReader reader) throws IOException{ 70 | super.readEnd(reader); 71 | 72 | int xffff_ffff = reader.readInt(); //unused int value(0xFFFF_FFFF) 73 | int ns_uri = reader.readInt(); 74 | int name = reader.readInt(); 75 | 76 | if((ns_uri != mRawNSUri) || (name != mRawName) ){ 77 | throw new IOException("Invalid end element"); 78 | } 79 | } 80 | 81 | private static final int INT_SIZE = 4; 82 | 83 | //chunsize, attr count 84 | public void prepare(){ 85 | int base_first = INT_SIZE * 9; 86 | //System.out.println("chunksize origin 1:" + mChunkSize.first + " 2:"+mChunkSize.second); 87 | mRawAttrCount =(short)(mRawAttrs == null ? 0: mRawAttrs.size()); 88 | //ignore id, class, style attribute's bee's way 89 | 90 | int attrSize = mRawAttrs == null ? 0: mRawAttrs.size()*Attribute.SIZE*INT_SIZE; 91 | mChunkSize.first = base_first + attrSize; 92 | mChunkSize.second = INT_SIZE*6; 93 | //System.out.println("chunksize after 1:" + mChunkSize.first + " 2:"+mChunkSize.second); 94 | //TODO ~ line number ~ 95 | } 96 | 97 | public void writeStart(IntWriter writer) throws IOException{ 98 | writer.writeInt(TAG_START); 99 | super.writeStart(writer); 100 | writer.writeInt(0xFFFFFFFF); 101 | writer.writeInt(mRawNSUri); 102 | writer.writeInt(mRawName); 103 | writer.writeInt(0x00140014); 104 | 105 | writer.writeShort(mRawAttrCount); 106 | writer.writeShort(mRawIdAttr);//id 107 | writer.writeShort(mRawClassAttr);//class 108 | writer.writeShort(mRawStyleAttr);//style 109 | 110 | if(mRawAttrCount > 0){ 111 | for(Attribute attr : mRawAttrs){ 112 | writer.writeInt(attr.mNameSpace); 113 | writer.writeInt(attr.mName); 114 | writer.writeInt(attr.mString); 115 | writer.writeInt(attr.mType); 116 | writer.writeInt(attr.mValue); 117 | } 118 | } 119 | } 120 | 121 | public void writeEnd(IntWriter writer) throws IOException{ 122 | writer.writeInt(TAG_END); 123 | super.writeEnd(writer); 124 | writer.writeInt(0xFFFFFFFF); 125 | writer.writeInt(mRawNSUri); 126 | writer.writeInt(mRawName); 127 | } 128 | 129 | /** 130 | * Eg:android:id="@+id/xxx". Equivalent to getAttributeValue(null, "id"). 131 | * @return Attribute(name="id").mString 132 | */ 133 | public int getIdAttr(){ 134 | return getAttrStringForKey(mRawIdAttr); 135 | } 136 | /** 137 | * Eg:android:class="com.foo.example". Equivalent to getAttributeValue(null, "class"). 138 | * @return Attribute(name="class").mString 139 | */ 140 | public int getClassAttr(){ 141 | return getAttrStringForKey(mRawClassAttr); 142 | } 143 | /** 144 | * Eg:style=""@style/Button". Equivalent to getAttributeValue(null, "style"). 145 | * @return Attribute(name="style").mString 146 | */ 147 | public int getStyleAttr(){ 148 | return getAttrStringForKey(mRawStyleAttr); 149 | } 150 | 151 | public Attribute[] getAttribute(){ 152 | if(mRawAttrs == null){ 153 | return new Attribute[0]; 154 | }else{ 155 | return mRawAttrs.toArray(new Attribute[mRawAttrs.size()]); 156 | } 157 | } 158 | 159 | public void setAttribute(Attribute attr){ 160 | if(mRawAttrs == null){ 161 | mRawAttrs = new ArrayList(); 162 | } 163 | 164 | mRawAttrs.add(attr); 165 | } 166 | 167 | /** 168 | * 169 | * @param key 170 | * @return String mapping id 171 | */ 172 | public int getAttrStringForKey(int key){ 173 | Attribute[] attrs = getAttribute(); 174 | 175 | for(Attribute attr : attrs){ 176 | if(attr.mName == key){ 177 | return attr.mString; 178 | } 179 | } 180 | 181 | return -1; 182 | } 183 | 184 | public boolean setAttrStringForKey(int key, int string_value){ 185 | final Attribute[] attrs = getAttribute(); 186 | for(Attribute attr : attrs){ 187 | if(attr.mName == key){ 188 | attr.setValue(TypedValue.TYPE_STRING, string_value); 189 | return true; 190 | } 191 | } 192 | return false; 193 | } 194 | 195 | public boolean setAttrIntForKey(int key, int string_value){ 196 | final Attribute[] attrs = getAttribute(); 197 | 198 | for(Attribute attr : attrs){ 199 | if(attr.mName == key){ 200 | attr.setValue(TypedValue.TYPE_INT_DEC, string_value); 201 | return true; 202 | } 203 | } 204 | return false; 205 | } 206 | 207 | /** 208 | * return scalar type for key 209 | * @param key 210 | * @return int[]{Type, Value} 211 | */ 212 | public int[] getAttrValueForKey(int key){ 213 | Attribute[] attrs = getAttribute(); 214 | 215 | for(Attribute attr : attrs){ 216 | if(attr.mName == key){ 217 | int type_value [] = new int[2]; 218 | type_value[0] = attr.mType; 219 | type_value[1] = attr.mValue; 220 | return type_value; 221 | } 222 | } 223 | 224 | return null; 225 | } 226 | 227 | /** 228 | * Don't support now 229 | * @param key 230 | * @param type 231 | * @param value 232 | * @return 233 | */ 234 | public boolean setAttrValueForKey(int key, int type, int value){ 235 | return false; 236 | } 237 | 238 | public int getName(){ 239 | return mRawName; 240 | } 241 | 242 | public void setName(int name){ 243 | mRawName = name; 244 | } 245 | 246 | public int getNamesapce(){ 247 | return mRawNSUri; 248 | } 249 | 250 | public void setNamespace(int ns){ 251 | mRawNSUri = ns; 252 | } 253 | 254 | public static class Attribute { 255 | public static final int SIZE = 5; 256 | 257 | public int mNameSpace; 258 | public int mName; 259 | public int mString; 260 | public int mType; 261 | public int mValue; 262 | 263 | public Attribute(int ns, int name, int type){ 264 | mNameSpace = ns; 265 | mName = name; 266 | mType = type<<24; 267 | } 268 | 269 | public void setString(int str){ 270 | if((mType>>24) != TypedValue.TYPE_STRING){ 271 | throw new RuntimeException("Can't set string for none string type"); 272 | } 273 | 274 | mString = str; 275 | mValue = str; 276 | } 277 | 278 | /** 279 | * TODO type >>> 16 = real type , so how to fix it 280 | * @param type 281 | * @param value 282 | */ 283 | public void setValue(int type, int value){ 284 | mType = type<<24; 285 | if(type == TypedValue.TYPE_STRING){ 286 | mValue = value; 287 | mString = value; 288 | }else{ 289 | mValue = value; 290 | mString = -1; 291 | } 292 | 293 | } 294 | 295 | public Attribute(int[] raw){ 296 | mNameSpace = raw[0]; 297 | mName = raw[1]; 298 | mString = raw[2]; 299 | mType = raw[3]; 300 | mValue = raw[4]; 301 | } 302 | 303 | public boolean hasNamespace(){ 304 | return (mNameSpace != -1); 305 | } 306 | 307 | @Override 308 | public String toString() { 309 | return "Attribute{" + 310 | "mNameSpace=" + mNameSpace + 311 | ", mName=" + mName + 312 | ", mString=" + mString + 313 | ", mType=" + mType + 314 | ", mValue=" + mValue + 315 | '}'; 316 | } 317 | } 318 | 319 | private int[] subArray(int[] src, int start, int len){ 320 | if((start + len) > src.length ){ 321 | throw new RuntimeException("OutOfArrayBound"); 322 | } 323 | 324 | int[] des = new int[len]; 325 | System.arraycopy(src, start, des, 0, len); 326 | 327 | return des; 328 | } 329 | 330 | @Override 331 | public void accept(IVisitor v) { 332 | v.visit(this); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/main/java/com/zzzmode/apkeditor/axmleditor/decode/StringBlock.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2011 Ryszard Wiśniewski 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.zzzmode.apkeditor.axmleditor.decode; 18 | 19 | import java.io.IOException; 20 | import java.io.UnsupportedEncodingException; 21 | import java.nio.charset.Charset; 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | /** 26 | * write and read StringBlock 27 | * @author NTOOOOOP 28 | */ 29 | public class StringBlock implements IAXMLSerialize{ 30 | private static final int TAG = 0x001C0001; 31 | private static final int INT_SIZE = 4; 32 | 33 | private int mChunkSize; 34 | private int mStringsCount; 35 | private int mStylesCount; 36 | private int mEncoder; 37 | 38 | private int mStrBlockOffset; 39 | private int mStyBlockOffset; 40 | 41 | private int[] mPerStrOffset; 42 | private int[] mPerStyOffset; 43 | 44 | /** 45 | * raw String 46 | */ 47 | private List mStrings; 48 | /** 49 | * android can identify HTML tags in a string,all the styles are kept here 50 | */ 51 | private List