├── .DS_Store ├── .classpath ├── .gitignore ├── .project ├── LICENSE ├── README.md ├── baffle.jar ├── libs ├── ant.jar ├── commons-cli-1.2.jar ├── commons-io-2.4.jar ├── commons-lang3-3.1.jar └── luciad-webp-imageio.jar ├── libwebp-imageio.dylib ├── res ├── libwebp-imageio.dylib ├── mapping.map ├── out.rep └── resguard.cfg └── src └── com └── guye └── baffle ├── ant └── BaffleTask.java ├── config ├── BaffleConfig.java ├── ConfigReader.java └── MappingWriter.java ├── decoder ├── ArscData.java ├── ArscFileGenerator.java ├── MakeCsc.java └── StringBlock.java ├── exception ├── BaffleException.java ├── BaffleRuntimeException.java └── UndefinedResObject.java ├── obfuscate ├── ApkBuilder.java ├── Main.java ├── NameFactory.java ├── ObfuscateHelper.java └── Obfuscater.java ├── util ├── AntLogHandle.java ├── ApkFileUtils.java ├── ConsoleHandler.java ├── DataInputDelegate.java ├── Duo.java ├── ExtDataInput.java ├── LEDataInputStream.java ├── LEDataOutputStream.java ├── OS.java └── ZipInfo.java └── webp └── WebpIO.java /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joker535/Baffle/0fcf542fd67b7e16ddccdd87b1c8818fa27853cd/.DS_Store -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | /*/build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | baffle-github 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 joker535 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android apk包res 资源混淆工具 2 | 3 | 功能描述: 4 | 对编译后的APK 进行资源名称混淆。混淆之后apk对比如下图: 5 | ![image](https://raw.githubusercontent.com/joker535/image/master/baffle1.jpg) 6 | 7 | 目前支持命令行操作: 8 | java -jar baffle.jar [-c/--config filepaths list ][-o/--output filepath] ApkFile TargetApkFile 9 | 10 | -c/--config 可选项,支持keep和mapping配置文件。 11 | -o/--output 可选项,支持混淆后mapping文件输出。 12 | 13 | config文件格式见res目录,描述如下: 14 |

15 | \#这个开头的行是注释。
16 | \#key keep 段,比较关键。需要用名字取得资源需要keep,支持正则表达式。 key指的是R文件里的变量名。不是文件名,不包含后缀
17 | ----keep_key
18 | 
19 | notificationsound
20 | newicon
21 | \#下面这个是,java的正则表达式。
22 | ^mini.*
23 | 
24 | ----keep_key
25 | 
26 | \#key 段的mapping
27 | ----map_key
28 | 
29 | activity_login,bx
30 | imexaple,a
31 | icon,b
32 | activity_myinformation,ae
33 | ----map_key
34 | \#大概就是这个样子,暂时不支持include之类的操作。和keep规则冲突以mapping为准
35 | 
36 | 37 | 具体原理可以参见我的博客:[Android apk文件资源混淆原理及实现](http://blog.csdn.net/joker535/article/details/48315257) 38 | -------------------------------------------------------------------------------- /baffle.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joker535/Baffle/0fcf542fd67b7e16ddccdd87b1c8818fa27853cd/baffle.jar -------------------------------------------------------------------------------- /libs/ant.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joker535/Baffle/0fcf542fd67b7e16ddccdd87b1c8818fa27853cd/libs/ant.jar -------------------------------------------------------------------------------- /libs/commons-cli-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joker535/Baffle/0fcf542fd67b7e16ddccdd87b1c8818fa27853cd/libs/commons-cli-1.2.jar -------------------------------------------------------------------------------- /libs/commons-io-2.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joker535/Baffle/0fcf542fd67b7e16ddccdd87b1c8818fa27853cd/libs/commons-io-2.4.jar -------------------------------------------------------------------------------- /libs/commons-lang3-3.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joker535/Baffle/0fcf542fd67b7e16ddccdd87b1c8818fa27853cd/libs/commons-lang3-3.1.jar -------------------------------------------------------------------------------- /libs/luciad-webp-imageio.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joker535/Baffle/0fcf542fd67b7e16ddccdd87b1c8818fa27853cd/libs/luciad-webp-imageio.jar -------------------------------------------------------------------------------- /libwebp-imageio.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joker535/Baffle/0fcf542fd67b7e16ddccdd87b1c8818fa27853cd/libwebp-imageio.dylib -------------------------------------------------------------------------------- /res/libwebp-imageio.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joker535/Baffle/0fcf542fd67b7e16ddccdd87b1c8818fa27853cd/res/libwebp-imageio.dylib -------------------------------------------------------------------------------- /res/mapping.map: -------------------------------------------------------------------------------- 1 | #mapping 文件,同样这个开头的是注释:) 2 | 3 | #key 段的mapping 4 | ----map_key 5 | 6 | activity_login_rb_admin,b444444 7 | imexaple,a 8 | icon,b 9 | activity_myinformation_tv_sex,a799999 10 | 11 | 12 | 13 | ----map_key 14 | 15 | #大概就是这个样子,暂时不支持include之类的操作。和keep规则冲突以mapping为准 -------------------------------------------------------------------------------- /res/resguard.cfg: -------------------------------------------------------------------------------- 1 | #这个开头的行是注释。 2 | #这个必须用utf—8编码。 3 | 4 | #key keep 段,比较关键。需要用名字取得资源需要keep,支持正则表达式。 key指的是R文件里的变量名。不是文件名,不包含后缀 5 | ----keep_key 6 | 7 | #notificationsound 8 | #newblogtoast 9 | #radar_scan 10 | #radar_pop 11 | #^activity_di_di_web.* 12 | #^didi_.* 13 | #^market_.* 14 | #^uxin_.* 15 | #^redbeans_.* 16 | #headlines_button_reward_selector 17 | #empty_answer 18 | #empty_trouble 19 | #list_background 20 | #qa_question_bg_color 21 | #yw_1222 22 | # 23 | # 24 | #^mini.* 25 | #^alipay.* 26 | #^key_.* 27 | #^keyboard_.* 28 | #^flybird_.* 29 | #nopwd_list 30 | #nopwd_limit 31 | #nopwd_limit_default 32 | #title_back_layout 33 | #setting_activity_channel 34 | #channel_list 35 | #text 36 | 37 | .* 38 | 39 | ----keep_key -------------------------------------------------------------------------------- /src/com/guye/baffle/ant/BaffleTask.java: -------------------------------------------------------------------------------- 1 | package com.guye.baffle.ant; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | import org.apache.tools.ant.BuildException; 8 | import org.apache.tools.ant.Task; 9 | import com.guye.baffle.exception.BaffleException; 10 | import com.guye.baffle.obfuscate.Obfuscater; 11 | import com.guye.baffle.util.AntLogHandle; 12 | 13 | public class BaffleTask extends Task { 14 | 15 | private boolean mVerbose; 16 | private boolean mConfig; 17 | private boolean mHelp; 18 | private boolean mOutput; 19 | private String mConfigFilpaths; 20 | private String mOutputFilepath; 21 | private String mApkFilepath; 22 | private String mTargetFilepath; 23 | public static final String helper = "[--config(true/false) filepaths list] [--output(true/false) filepath] ApkFile TargetApkFile"; 24 | 25 | public void setVerbose(boolean verbose) { 26 | this.mVerbose = verbose; 27 | } 28 | 29 | public void setConfig(boolean config) { 30 | this.mConfig = config; 31 | } 32 | 33 | public void setHelp(boolean help) { 34 | this.mHelp = help; 35 | } 36 | 37 | public void setOutput(boolean output) { 38 | this.mOutput = output; 39 | } 40 | 41 | public void setConfigFilepaths(String configfilepaths) { 42 | this.mConfigFilpaths = configfilepaths; 43 | } 44 | 45 | public void setOutputFilepath(String outputfilepath) { 46 | this.mOutputFilepath = outputfilepath; 47 | } 48 | 49 | public void setApkFilepath(String apkfilepath) { 50 | this.mApkFilepath = apkfilepath; 51 | } 52 | 53 | public void setTargetFilepath(String targetFilepath) { 54 | this.mTargetFilepath = targetFilepath; 55 | } 56 | 57 | @Override 58 | public void execute() throws BuildException { 59 | 60 | if (mVerbose) { 61 | Logger.getLogger(Obfuscater.LOG_NAME).setLevel(Level.CONFIG); 62 | } else { 63 | Logger.getLogger(Obfuscater.LOG_NAME).setLevel(Level.OFF); 64 | } 65 | 66 | Logger.getLogger(Obfuscater.LOG_NAME) 67 | .addHandler(new AntLogHandle(this)); 68 | 69 | if (mApkFilepath == null || mTargetFilepath == null 70 | || mApkFilepath.equals("") || mTargetFilepath.equals("")) { 71 | Logger.getLogger(Obfuscater.LOG_NAME).log(Level.CONFIG, 72 | "not specify apk file or taget apk file"); 73 | return; 74 | } 75 | 76 | if (mApkFilepath.equals(mTargetFilepath)) { 77 | Logger.getLogger(Obfuscater.LOG_NAME) 78 | .log(Level.CONFIG, 79 | "apk file can not rewrite , please specify new target file"); 80 | return; 81 | } 82 | 83 | File mApkFile = new File(mApkFilepath); 84 | if (!mApkFile.exists()) { 85 | Logger.getLogger(Obfuscater.LOG_NAME).log(Level.CONFIG, 86 | "apk file not exists"); 87 | return; 88 | } 89 | 90 | if (mHelp) { 91 | Logger.getLogger(Obfuscater.LOG_NAME).log(Level.CONFIG, helper); 92 | return; 93 | } 94 | 95 | File[] mConfigFiles = null; 96 | if (mConfig) { 97 | String[] fs = mConfigFilpaths.split(","); 98 | int len = fs.length; 99 | mConfigFiles = new File[fs.length]; 100 | for (int i = 0; i < len; i++) { 101 | mConfigFiles[i] = new File(fs[i]); 102 | if (!mConfigFiles[i].exists()) { 103 | Logger.getLogger(Obfuscater.LOG_NAME).log(Level.CONFIG, 104 | "config file " + fs[i] + " not exists"); 105 | return; 106 | } 107 | } 108 | } else { 109 | mConfigFiles = null; 110 | } 111 | 112 | File mappingfile = null; 113 | if (mOutput) { 114 | mappingfile = new File(mOutputFilepath); 115 | if (mappingfile.getParentFile() != null) { 116 | mappingfile.getParentFile().mkdirs(); 117 | } 118 | } else { 119 | mappingfile = null; 120 | } 121 | 122 | Obfuscater obfuscater = new Obfuscater(mConfigFiles, mappingfile,null, 123 | mApkFile, mTargetFilepath); 124 | 125 | try { 126 | obfuscater.obfuscate(); 127 | } catch (IOException e) { 128 | getProject().fireBuildFinished(e); 129 | } catch (BaffleException e) { 130 | getProject().fireBuildFinished(e); 131 | } 132 | 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/com/guye/baffle/config/BaffleConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.config; 24 | 25 | import java.util.ArrayList; 26 | import java.util.Collections; 27 | import java.util.HashMap; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.regex.Matcher; 31 | import java.util.regex.Pattern; 32 | 33 | public class BaffleConfig { 34 | 35 | private Map mKeyMapping = new HashMap(); 36 | 37 | private List mKeyKeeps = new ArrayList(); 38 | 39 | BaffleConfig() { 40 | } 41 | 42 | /* 43 | * 根据配置文件获取资源文件混淆后的名字,key不包括后缀。没有特殊配置返回null 44 | */ 45 | public String getMapping(String key) { 46 | String newkey = mKeyMapping.get(key); 47 | if (newkey != null) { 48 | if (newkey.length() == 0) { 49 | throw new IllegalArgumentException("error key config on : " 50 | + key); 51 | } 52 | return newkey; 53 | } else { 54 | return null; 55 | } 56 | } 57 | 58 | public boolean isKeepKey(String key) { 59 | for (Object object : mKeyKeeps) { 60 | if(object instanceof Pattern){ 61 | Pattern pattern = (Pattern) object; 62 | if(pattern.matcher(key).matches()){ 63 | return true; 64 | } 65 | }else{ 66 | if(key.equals(object)){ 67 | return true; 68 | } 69 | } 70 | } 71 | return false; 72 | } 73 | 74 | public void addKeep(Object keep) { 75 | mKeyKeeps.add(keep); 76 | } 77 | 78 | public void addMapping(String key, String map) { 79 | mKeyMapping.put(key, map); 80 | } 81 | 82 | public Map getAllMapping() { 83 | return Collections.unmodifiableMap(mKeyMapping); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/com/guye/baffle/config/ConfigReader.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.config; 24 | 25 | import java.io.BufferedReader; 26 | import java.io.File; 27 | import java.io.FileInputStream; 28 | import java.io.IOException; 29 | import java.io.InputStreamReader; 30 | import java.util.logging.Level; 31 | import java.util.logging.Logger; 32 | import java.util.regex.Pattern; 33 | import java.util.regex.PatternSyntaxException; 34 | 35 | import com.guye.baffle.exception.BaffleException; 36 | import com.guye.baffle.obfuscate.Obfuscater; 37 | 38 | public class ConfigReader { 39 | private static final String COMMENT_PROFIX = "#"; 40 | private static final String FRAGMENT_START_FLAG = "----"; 41 | 42 | private static final String FRAGMENT_KEEP_KEY = "keep_key"; 43 | private static final String FRAGMENT_MAP_KEY = "map_key"; 44 | 45 | private static final int STATUS_NULL = 0; 46 | private static final int STATUS_IN_FARGMENT = 1; 47 | 48 | private Logger log = Logger.getLogger(Obfuscater.LOG_NAME); 49 | private int status = 0; 50 | private String fragmentType; 51 | 52 | public BaffleConfig read(File[] configFiles) throws BaffleException { 53 | BaffleConfig baffleConfig = new BaffleConfig(); 54 | 55 | FileInputStream fileInputStream; 56 | BufferedReader reader; 57 | String line; 58 | if (configFiles != null) { 59 | for (File file : configFiles) { 60 | try { 61 | fileInputStream = new FileInputStream(file); 62 | reader = new BufferedReader(new InputStreamReader( 63 | fileInputStream)); 64 | while ((line = reader.readLine()) != null) { 65 | if (line.startsWith(COMMENT_PROFIX) 66 | || line.length() == 0) { 67 | continue; 68 | } else if (line.startsWith(FRAGMENT_START_FLAG)) { 69 | String fragmentName = line 70 | .substring(FRAGMENT_START_FLAG.length(), 71 | line.length()); 72 | if (status == STATUS_NULL) { 73 | status = STATUS_IN_FARGMENT; 74 | fragmentType = fragmentName; 75 | log.log(Level.CONFIG,"fragmentType::" 76 | + fragmentType); 77 | } else { 78 | if (!fragmentType.equals(fragmentName)) { 79 | throw new BaffleException( 80 | "error config or map config file :" 81 | + file.getName() 82 | + " on line : " + line); 83 | } else { 84 | status = STATUS_NULL; 85 | fragmentType = null; 86 | } 87 | } 88 | } else { 89 | if (status == STATUS_NULL) { 90 | throw new BaffleException( 91 | "error config or map config file :" 92 | + file.getName() 93 | + " on line : " + line); 94 | } else { 95 | dealLine(line, baffleConfig); 96 | } 97 | } 98 | 99 | } 100 | reader.close(); 101 | fileInputStream.close(); 102 | } catch (IOException e) { 103 | throw new BaffleException(e); 104 | } 105 | } 106 | } 107 | return baffleConfig; 108 | 109 | } 110 | 111 | private void dealLine(String line, BaffleConfig baffleConfig) 112 | throws BaffleException { 113 | if (FRAGMENT_KEEP_KEY.equals(fragmentType)) { 114 | if(qulifiedMapValue(line)){ 115 | baffleConfig.addKeep(line); 116 | }else{ 117 | try { 118 | baffleConfig.addKeep(Pattern.compile(line)); 119 | } catch (PatternSyntaxException e) { 120 | throw new BaffleException("error keep java Pattern : " + line, e); 121 | } 122 | } 123 | } else if (FRAGMENT_MAP_KEY.equals(fragmentType)) { 124 | String[] strs = line.split(","); 125 | if (strs.length != 2) { 126 | throw new BaffleException( 127 | "error config or map config on line : " + line); 128 | } 129 | if (qulifiedMapValue(strs[1])) { 130 | baffleConfig.addMapping(strs[0], strs[1]); 131 | } else { 132 | throw new BaffleException( 133 | "error map config about mapvalue on line : " + line); 134 | } 135 | 136 | } 137 | } 138 | 139 | private boolean qulifiedMapValue(String mapValue) { 140 | int length = 0; 141 | if (mapValue.charAt(0) >= 'a' && mapValue.charAt(0) <= 'z') { 142 | length++; 143 | for (int i = 1; i < mapValue.length(); i++) { 144 | if (mapValue.charAt(i) >= 'a' && mapValue.charAt(i) <= 'z' 145 | || mapValue.charAt(i) >= '0' 146 | && mapValue.charAt(i) <= '9' 147 | || mapValue.charAt(i) == '_') { 148 | length++; 149 | } else { 150 | break; 151 | } 152 | } 153 | } 154 | if (length == mapValue.length()) { 155 | return true; 156 | } else { 157 | return false; 158 | } 159 | 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/com/guye/baffle/config/MappingWriter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.config; 24 | 25 | import java.io.File; 26 | import java.io.FileOutputStream; 27 | import java.io.IOException; 28 | import java.util.Map; 29 | import java.util.logging.Level; 30 | import java.util.logging.Logger; 31 | 32 | import com.guye.baffle.exception.BaffleException; 33 | import com.guye.baffle.obfuscate.Obfuscater; 34 | 35 | public class MappingWriter { 36 | 37 | private Map mKeyMaping; 38 | 39 | public MappingWriter(Map mKeyMaping) { 40 | this.mKeyMaping = mKeyMaping; 41 | } 42 | 43 | public void WriteToFile(File file) throws BaffleException, IOException { 44 | 45 | if (file != null) { 46 | if (mKeyMaping.size() != 0) { 47 | StringBuilder stringBuilder = new StringBuilder(); 48 | stringBuilder.append("#mapping 文件\n\n") 49 | .append("#key 段的mapping\n\n").append("---map_key\n\n"); 50 | for (Map.Entry entry : mKeyMaping.entrySet()) { 51 | stringBuilder.append(entry.getKey()).append(",") 52 | .append(entry.getValue()).append("\n"); 53 | } 54 | stringBuilder.append("\n---map_key"); 55 | FileOutputStream fos = new FileOutputStream(file); 56 | fos.write(stringBuilder.toString().getBytes()); 57 | fos.flush(); 58 | fos.close(); 59 | } 60 | } else { 61 | Logger.getLogger(Obfuscater.LOG_NAME).log(Level.CONFIG, 62 | "WriteToFile:file-->" + file); 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/com/guye/baffle/decoder/ArscData.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.decoder; 24 | 25 | import java.io.EOFException; 26 | import java.io.File; 27 | import java.io.FileInputStream; 28 | import java.io.FileOutputStream; 29 | import java.io.IOException; 30 | import java.util.logging.Level; 31 | import java.util.logging.Logger; 32 | import java.util.zip.CRC32; 33 | import java.util.zip.CheckedOutputStream; 34 | 35 | import org.apache.commons.io.input.CountingInputStream; 36 | 37 | import com.guye.baffle.obfuscate.Obfuscater; 38 | import com.guye.baffle.util.ConsoleHandler; 39 | import com.guye.baffle.util.ExtDataInput; 40 | import com.guye.baffle.util.LEDataInputStream; 41 | import com.guye.baffle.util.LEDataOutputStream; 42 | 43 | public class ArscData { 44 | private Header mHeader; 45 | private StringBlock mTableStrings; 46 | private StringBlock mSpecNames; 47 | private StringBlock mTypeNames; 48 | private int mTypeStrStart; 49 | private int mTypeStrEnd; 50 | private int mPkgHeaderIndex; 51 | private int mResIndex; 52 | private PackageHeader mPkgHeader; 53 | private File mFile; 54 | 55 | public static ArscData decode( File file ) throws IOException { 56 | FileInputStream arscStream = new FileInputStream(file); 57 | CountingInputStream countIn; 58 | countIn = new CountingInputStream(arscStream); 59 | ExtDataInput in = new ExtDataInput(new LEDataInputStream(countIn)); 60 | ArscData arscData = readTable(file , countIn, in); 61 | countIn.close(); 62 | arscStream.close(); 63 | return arscData; 64 | } 65 | 66 | private static ArscData readTable(File file , CountingInputStream countIn, ExtDataInput in ) 67 | throws IOException { 68 | ArscData arscData = new ArscData(); 69 | arscData.mFile = file; 70 | arscData.mHeader = Header.read(in); 71 | int packageCount = in.readInt(); 72 | if (packageCount != 1) { 73 | throw new UnsupportedOperationException("not support more then 1 package"); 74 | } 75 | arscData.mTableStrings = StringBlock.read(in); 76 | arscData.mPkgHeaderIndex = (int) countIn.getByteCount(); 77 | arscData.mPkgHeader = PackageHeader.read(in); 78 | arscData.mTypeStrStart = (int) countIn.getByteCount(); 79 | arscData.mTypeNames = StringBlock.read(in); 80 | arscData.mTypeStrEnd = (int) countIn.getByteCount(); 81 | arscData.mSpecNames = StringBlock.read(in); 82 | arscData.mResIndex = (int) countIn.getByteCount(); 83 | return arscData; 84 | } 85 | 86 | public static class Header { 87 | public final short type; 88 | public final short headerSize; 89 | public int chunkSize; 90 | 91 | public Header(short type, short headSize, int size) { 92 | this.type = type; 93 | this.headerSize = headSize; 94 | this.chunkSize = size; 95 | } 96 | 97 | public static Header read( ExtDataInput in ) throws IOException { 98 | short type; 99 | try { 100 | type = in.readShort(); 101 | } catch (EOFException ex) { 102 | return new Header(TYPE_NONE, (short) 0, 0); 103 | } 104 | return new Header(type, in.readShort(), in.readInt()); 105 | } 106 | 107 | public final static short TYPE_NONE = -1, TYPE_TABLE = 0x0002, TYPE_PACKAGE = 0x0200, 108 | TYPE_TYPE = 0x0202, TYPE_CONFIG = 0x0201; 109 | 110 | public void write( LEDataOutputStream out ) throws IOException { 111 | out.writeShort(type); 112 | out.writeShort(headerSize); 113 | out.writeInt(chunkSize); 114 | } 115 | } 116 | 117 | public static class PackageHeader { 118 | public Header header; 119 | public int id; 120 | public byte[] name = new byte[256]; 121 | public int typeNameStrings, typeNameCount, specNameStrings, specNameCount; 122 | 123 | public static PackageHeader read( ExtDataInput in ) throws IOException { 124 | PackageHeader header = new PackageHeader(); 125 | header.header = Header.read(in); 126 | header.id = (byte) in.readInt(); 127 | in.readFully(header.name); 128 | header.typeNameStrings = in.readInt(); 129 | header.typeNameCount = in.readInt(); 130 | header.specNameStrings = in.readInt(); 131 | header.specNameCount = in.readInt(); 132 | return header; 133 | } 134 | 135 | public void write( LEDataOutputStream out ) throws IOException { 136 | header.write(out); 137 | out.writeInt(id); 138 | out.write(name); 139 | out.writeInt(typeNameStrings); 140 | out.writeInt(typeNameCount); 141 | out.writeInt(specNameStrings); 142 | out.writeInt(specNameCount); 143 | } 144 | } 145 | 146 | 147 | public Header getmHeader() { 148 | return mHeader; 149 | } 150 | 151 | public StringBlock getmTableStrings() { 152 | return mTableStrings; 153 | } 154 | 155 | public StringBlock getmSpecNames() { 156 | return mSpecNames; 157 | } 158 | 159 | public int getmTypeStrStart() { 160 | return mTypeStrStart; 161 | } 162 | 163 | public int getmTypeStrEnd() { 164 | return mTypeStrEnd; 165 | } 166 | 167 | public int getmPkgHeaderIndex() { 168 | return mPkgHeaderIndex; 169 | } 170 | 171 | public int getmResIndex() { 172 | return mResIndex; 173 | } 174 | 175 | public PackageHeader getmPkgHeader() { 176 | return mPkgHeader; 177 | } 178 | 179 | public StringBlock getTypeNames(){ 180 | return mTypeNames; 181 | } 182 | 183 | public File getFile(){ 184 | return mFile; 185 | } 186 | 187 | public CRC32 createObfuscateFile(StringBlock tableBlock, 188 | StringBlock keyBlock, File file ) throws IOException { 189 | FileOutputStream fileOutputStream = new FileOutputStream(file); 190 | CRC32 cksum = new CRC32(); 191 | CheckedOutputStream checkedOutputStream = new CheckedOutputStream(fileOutputStream, cksum); 192 | LEDataOutputStream out = new LEDataOutputStream(checkedOutputStream); 193 | 194 | int tableStrChange = getmTableStrings().getSize() - tableBlock.getSize(); 195 | int keyStrChange = getmSpecNames().getSize() - keyBlock.getSize(); 196 | getmHeader().chunkSize -=(tableStrChange + keyStrChange); 197 | getmHeader().write(out); 198 | out.writeInt(1); 199 | tableBlock.write(out); 200 | getmPkgHeader().header.chunkSize -=keyStrChange; 201 | getmPkgHeader().write(out); 202 | getTypeNames().write(out); 203 | keyBlock.write(out); 204 | 205 | byte[] buff = new byte[1024]; 206 | FileInputStream in = new FileInputStream(getFile()); 207 | in.skip(getmResIndex()); 208 | int len ; 209 | while(((len = in.read(buff)) != -1)){ 210 | out.write(buff , 0 , len); 211 | } 212 | 213 | in.close(); 214 | out.close(); 215 | checkedOutputStream.close(); 216 | fileOutputStream.close(); 217 | return cksum; 218 | } 219 | 220 | public static void main(String[] args) throws IOException { 221 | ArscData arscData = ArscData.decode(new File("/Users/nieyu/tempfile/resources.arsc")); 222 | int size = arscData.mSpecNames.getCount(); 223 | for (int i = 0; i < size; i++) { 224 | if(arscData.mSpecNames.getString(i).contains("png")){ 225 | System.out.println(arscData.mSpecNames.getString(i)); 226 | } 227 | } 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /src/com/guye/baffle/decoder/ArscFileGenerator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.decoder; 24 | 25 | import java.io.ByteArrayOutputStream; 26 | import java.io.File; 27 | import java.io.FileInputStream; 28 | import java.io.FileOutputStream; 29 | import java.io.IOException; 30 | import java.nio.charset.Charset; 31 | import java.util.zip.CRC32; 32 | import java.util.zip.CheckedOutputStream; 33 | 34 | import com.guye.baffle.obfuscate.ObfuscateHelper; 35 | import com.guye.baffle.obfuscate.ObfuscateHelper.ObfuscateData; 36 | import com.guye.baffle.util.LEDataOutputStream; 37 | 38 | public class ArscFileGenerator { 39 | private ArscData arscData; 40 | private ObfuscateData obfuscateData; 41 | private static final Charset UTF_8_CHARSET = Charset.forName("UTF-8"); 42 | 43 | public ArscFileGenerator(ArscData arscData, ObfuscateData obfuscateData) { 44 | this.arscData = arscData; 45 | this.obfuscateData = obfuscateData; 46 | } 47 | 48 | private StringBlock createStrings(StringBlock orgTableStrings, 49 | boolean isTableString) { 50 | try { 51 | ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(); 52 | LEDataOutputStream dataOutputStream = new LEDataOutputStream( 53 | arrayOutputStream); 54 | int count = orgTableStrings.getCount(); 55 | int curOffset = 0; 56 | int[] offset = new int[count]; 57 | String newStr; 58 | byte[] strData; 59 | byte[] l = new byte[2]; 60 | byte[] l1 = new byte[2]; 61 | int offsetLen = 1; 62 | int offsetDataLen = 1; 63 | for (int i = 0; i < count; i++) { 64 | if (isTableString) { 65 | newStr = getNewTableString(orgTableStrings.getString(i)); 66 | } else { 67 | newStr = getNewKeyString(orgTableStrings.getString(i)); 68 | } 69 | strData = newStr.getBytes(UTF_8_CHARSET); 70 | offset[i] = curOffset; 71 | if (newStr.length() < 128) { 72 | offsetLen = 1; 73 | l[0] = (byte) (0x7f & (newStr.length())); 74 | } else { 75 | offsetLen = 2; 76 | short len = (short) (newStr.length()); 77 | l[0] = (byte) ((byte) ((len & 0xff00) >> 8) | 0x80); 78 | l[1] = (byte) (len & 0x00ff); 79 | } 80 | 81 | if (strData.length < 128) { 82 | l1[0] = (byte) (0x7f & (strData.length)); 83 | offsetDataLen = 1; 84 | } else { 85 | offsetDataLen = 2; 86 | short len = (short) (strData.length); 87 | l1[0] = (byte) ((byte) ((len & 0xff00) >> 8) | 0x80); 88 | l1[1] = (byte) (len & 0x00ff); 89 | } 90 | dataOutputStream.write(l, 0, offsetLen); 91 | dataOutputStream.write(l1, 0, offsetDataLen); 92 | dataOutputStream.write(strData); 93 | dataOutputStream.write(0); 94 | curOffset += (offsetLen + offsetDataLen + strData.length + 1); 95 | } 96 | 97 | strData = arrayOutputStream.toByteArray(); 98 | dataOutputStream.close(); 99 | arrayOutputStream.close(); 100 | 101 | return new StringBlock(offset, strData, 102 | orgTableStrings.getStyleOffset(), 103 | orgTableStrings.getStyle(), true); 104 | } catch (IOException e) {// not a disk IO option 105 | e.printStackTrace(); 106 | } 107 | return null; 108 | } 109 | 110 | private String getNewKeyString(String orgString) { 111 | return obfuscateData.keyMaping.get(orgString); 112 | } 113 | 114 | private String getNewTableString(String orgString) { 115 | if (orgString.startsWith(ObfuscateHelper.RES_PROFIX)) { 116 | String[] names = orgString.split("/"); 117 | if (names == null || names.length != 3) { 118 | throw new RuntimeException(); // TODO 119 | } 120 | String[] newNames = new String[3]; 121 | newNames[0] = obfuscateData.resName; 122 | newNames[1] = obfuscateData.typeMaping.get(names[1]); 123 | if (newNames[1] == null) { 124 | throw new RuntimeException(); // TODO 125 | } 126 | int index = names[2].indexOf('.'); 127 | String postfix = ""; 128 | if (index > 0) { 129 | postfix = names[2].substring(index, names[2].length()); 130 | names[2] = names[2].substring(0, index); 131 | } 132 | newNames[2] = obfuscateData.keyMaping.get(names[2]); 133 | if (newNames[2] == null) { 134 | throw new RuntimeException(); // TODO 135 | } 136 | String newString = new StringBuilder().append(newNames[0]) 137 | .append('/').append(newNames[1]).append('/') 138 | .append(newNames[2]).append(postfix).toString(); 139 | return newString; 140 | } else { 141 | return orgString; 142 | } 143 | } 144 | 145 | public CRC32 createObfuscateFile( ArscData data, StringBlock tableBlock, 146 | StringBlock keyBlock, File file ) throws IOException { 147 | FileOutputStream fileOutputStream = new FileOutputStream(file); 148 | CRC32 cksum = new CRC32(); 149 | CheckedOutputStream checkedOutputStream = new CheckedOutputStream(fileOutputStream, cksum); 150 | LEDataOutputStream out = new LEDataOutputStream(checkedOutputStream); 151 | 152 | int tableStrChange = data.getmTableStrings().getSize() - tableBlock.getSize(); 153 | int keyStrChange = data.getmSpecNames().getSize() - keyBlock.getSize(); 154 | data.getmHeader().chunkSize -=(tableStrChange + keyStrChange); 155 | data.getmHeader().write(out); 156 | out.writeInt(1); 157 | tableBlock.write(out); 158 | data.getmPkgHeader().header.chunkSize -=keyStrChange; 159 | data.getmPkgHeader().write(out); 160 | data.getTypeNames().write(out); 161 | keyBlock.write(out); 162 | 163 | byte[] buff = new byte[1024]; 164 | FileInputStream in = new FileInputStream(data.getFile()); 165 | in.skip(data.getmResIndex()); 166 | int len ; 167 | while(((len = in.read(buff)) != -1)){ 168 | out.write(buff , 0 , len); 169 | } 170 | 171 | in.close(); 172 | out.close(); 173 | checkedOutputStream.close(); 174 | fileOutputStream.close(); 175 | return cksum; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/com/guye/baffle/decoder/MakeCsc.java: -------------------------------------------------------------------------------- 1 | package com.guye.baffle.decoder; 2 | 3 | /** 4 | * Created by linuxsir on 15/8/12. 5 | */ 6 | 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.util.zip.CRC32; 10 | import java.util.zip.CheckedInputStream; 11 | 12 | public class MakeCsc { 13 | 14 | public static long getFileCRCCode(File file) throws Exception { 15 | 16 | FileInputStream fileinputstream = new FileInputStream(file); 17 | CRC32 crc32 = new CRC32(); 18 | for (CheckedInputStream checkedinputstream = 19 | new CheckedInputStream(fileinputstream, crc32); 20 | checkedinputstream.read() != -1; 21 | ) { 22 | } 23 | return crc32.getValue(); 24 | 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/com/guye/baffle/decoder/StringBlock.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.decoder; 24 | 25 | import java.io.IOException; 26 | import java.nio.ByteBuffer; 27 | import java.nio.charset.CharacterCodingException; 28 | import java.nio.charset.Charset; 29 | import java.nio.charset.CharsetDecoder; 30 | import java.util.logging.Level; 31 | import java.util.logging.Logger; 32 | 33 | import com.guye.baffle.util.ExtDataInput; 34 | import com.guye.baffle.util.LEDataOutputStream; 35 | 36 | public class StringBlock { 37 | 38 | 39 | public static StringBlock read(ExtDataInput reader) throws IOException { 40 | 41 | int chunkType ; 42 | int alignDataCount = 0; 43 | while((chunkType = reader.readInt()) == 0){ 44 | alignDataCount += 1; 45 | } 46 | 47 | // reader.skipCheckInt(CHUNK_TYPE); 48 | int chunkSize = reader.readInt(); 49 | int stringCount = reader.readInt(); 50 | int styleOffsetCount = reader.readInt(); 51 | int flags = reader.readInt(); 52 | int stringsOffset = reader.readInt(); 53 | int stylesOffset = reader.readInt(); 54 | 55 | StringBlock block = new StringBlock(); 56 | block.mAlignDataCount = alignDataCount; 57 | block.m_isUTF8 = (flags & UTF8_FLAG) != 0; 58 | block.m_stringOffsets = reader.readIntArray(stringCount); 59 | block.m_stringOwns = new int[stringCount]; 60 | for (int i = 0; i < stringCount; i++) { 61 | block.m_stringOwns[i] = -1; 62 | } 63 | if (styleOffsetCount != 0) { 64 | block.m_styleOffsets = reader.readIntArray(styleOffsetCount); 65 | } 66 | { 67 | int size = ((stylesOffset == 0) ? chunkSize : stylesOffset) 68 | - stringsOffset; 69 | if ((size % 4) != 0) { 70 | throw new IOException("String data size is not multiple of 4 (" 71 | + size + ")."); 72 | } 73 | block.m_strings = new byte[size]; 74 | reader.readFully(block.m_strings); 75 | } 76 | if (stylesOffset != 0) { 77 | int size = (chunkSize - stylesOffset); 78 | if ((size % 4) != 0) { 79 | throw new IOException("Style data size is not multiple of 4 (" 80 | + size + ")."); 81 | } 82 | block.m_styles = reader.readIntArray(size / 4); 83 | } 84 | 85 | return block; 86 | } 87 | 88 | /** 89 | * Returns number of strings in block. 90 | */ 91 | public int getCount() { 92 | return m_stringOffsets != null ? m_stringOffsets.length : 0; 93 | } 94 | 95 | /** 96 | * Returns raw string (without any styling information) at specified index. 97 | */ 98 | public String getString(int index) { 99 | if (index < 0 || m_stringOffsets == null 100 | || index >= m_stringOffsets.length) { 101 | return null; 102 | } 103 | int offset = m_stringOffsets[index]; 104 | int length; 105 | 106 | if (!m_isUTF8) { 107 | length = getShort(m_strings, offset) * 2; 108 | offset += 2; 109 | } else { 110 | offset += getVarint(m_strings, offset)[1]; 111 | int[] varint = getVarint(m_strings, offset); 112 | offset += varint[1]; 113 | length = varint[0]; 114 | } 115 | return decodeString(offset, length); 116 | } 117 | 118 | /** 119 | * Not yet implemented. 120 | * 121 | * Returns string with style information (if any). 122 | */ 123 | public CharSequence get(int index) { 124 | return getString(index); 125 | } 126 | 127 | /** 128 | * Finds index of the string. Returns -1 if the string was not found. 129 | */ 130 | public int find(String string) { 131 | if (string == null) { 132 | return -1; 133 | } 134 | for (int i = 0; i != m_stringOffsets.length; ++i) { 135 | int offset = m_stringOffsets[i]; 136 | int length = getShort(m_strings, offset); 137 | if (length != string.length()) { 138 | continue; 139 | } 140 | int j = 0; 141 | for (; j != length; ++j) { 142 | offset += 2; 143 | if (string.charAt(j) != getShort(m_strings, offset)) { 144 | break; 145 | } 146 | } 147 | if (j == length) { 148 | return i; 149 | } 150 | } 151 | return -1; 152 | } 153 | 154 | // /////////////////////////////////////////// implementation 155 | private StringBlock() { 156 | } 157 | 158 | /** 159 | * Returns style information - array of int triplets, where in each triplet: 160 | * * first int is index of tag name ('b','i', etc.) * second int is tag 161 | * start index in string * third int is tag end index in string 162 | */ 163 | private int[] getStyle(int index) { 164 | if (m_styleOffsets == null || m_styles == null 165 | || index >= m_styleOffsets.length) { 166 | return null; 167 | } 168 | int offset = m_styleOffsets[index] / 4; 169 | int style[]; 170 | { 171 | int count = 0; 172 | for (int i = offset; i < m_styles.length; ++i) { 173 | if (m_styles[i] == -1) { 174 | break; 175 | } 176 | count += 1; 177 | } 178 | if (count == 0 || (count % 3) != 0) { 179 | return null; 180 | } 181 | style = new int[count]; 182 | } 183 | for (int i = offset, j = 0; i < m_styles.length;) { 184 | if (m_styles[i] == -1) { 185 | break; 186 | } 187 | style[j++] = m_styles[i++]; 188 | } 189 | return style; 190 | } 191 | 192 | private String decodeString(int offset, int length) { 193 | try { 194 | return (m_isUTF8 ? UTF8_DECODER : UTF16LE_DECODER).decode( 195 | ByteBuffer.wrap(m_strings, offset, length)).toString(); 196 | } catch (CharacterCodingException ex) { 197 | LOGGER.log(Level.WARNING, null, ex); 198 | return null; 199 | } 200 | } 201 | 202 | private static final int getShort(byte[] array, int offset) { 203 | return (array[offset + 1] & 0xff) << 8 | array[offset] & 0xff; 204 | } 205 | 206 | private static final int getShort(int[] array, int offset) { 207 | int value = array[offset / 4]; 208 | if ((offset % 4) / 2 == 0) { 209 | return (value & 0xFFFF); 210 | } else { 211 | return (value >>> 16); 212 | } 213 | } 214 | 215 | private static final int[] getVarint(byte[] array, int offset) { 216 | int val = array[offset]; 217 | boolean more = (val & 0x80) != 0; 218 | val &= 0x7f; 219 | 220 | if (!more) { 221 | return new int[] { val, 1 }; 222 | } else { 223 | return new int[] { val << 8 | array[offset + 1] & 0xff, 2 }; 224 | } 225 | } 226 | 227 | public boolean touch(int index, int own) { 228 | if (index < 0 || m_stringOwns == null || index >= m_stringOwns.length) { 229 | return false; 230 | } 231 | if (m_stringOwns[index] == -1) { 232 | m_stringOwns[index] = own; 233 | return true; 234 | } else if (m_stringOwns[index] == own) { 235 | return true; 236 | } else { 237 | return false; 238 | } 239 | } 240 | 241 | public StringBlock(int[] strOffset , byte[] str , int[] styleOffset , int[] style , boolean isUTF8){ 242 | m_stringOffsets = strOffset; 243 | m_strings = str ; 244 | m_styleOffsets = styleOffset==null?new int[0]:styleOffset; 245 | m_styles = style==null?new int[0]:style; 246 | m_isUTF8 = isUTF8; 247 | } 248 | 249 | public void write(LEDataOutputStream out) throws IOException{ 250 | if(m_styleOffsets == null){ 251 | m_styleOffsets = new int[0]; 252 | } 253 | if(m_styles == null){ 254 | m_styles = new int[0]; 255 | } 256 | int modeSize = m_strings.length % 4; 257 | if(modeSize != 0){ 258 | modeSize = 4 - modeSize; 259 | } 260 | 261 | int align = mAlignDataCount; 262 | while(align-- != 0){ 263 | out.writeInt(0); 264 | } 265 | out.writeInt(CHUNK_TYPE); 266 | out.writeInt(getSize()); 267 | out.writeInt(m_stringOffsets.length); 268 | out.writeInt(m_styleOffsets.length); 269 | out.writeInt(m_isUTF8?UTF8_FLAG:0); 270 | out.writeInt(28 + m_stringOffsets.length * 4 + m_styleOffsets.length *4); 271 | out.writeInt(m_styleOffsets.length == 0 ? 0 : 28 + m_stringOffsets.length * 4 + m_styleOffsets.length *4 + m_strings.length + modeSize); 272 | out.writeIntArray(m_stringOffsets); 273 | if(m_styleOffsets != null && m_styleOffsets.length != 0){ 274 | out.writeIntArray(m_styleOffsets); 275 | } 276 | out.write(m_strings); 277 | 278 | 279 | for (int i = 0; i < modeSize; i++) { 280 | out.write(0); 281 | } 282 | 283 | if(m_styles != null && m_styles.length != 0){ 284 | out.writeIntArray(m_styles); 285 | } 286 | out.flush(); 287 | } 288 | 289 | public int getSize() { 290 | int size = 28; 291 | size +=( m_stringOffsets.length * 4); 292 | if(m_styleOffsets != null && m_styleOffsets.length != 0){ 293 | size += (m_styleOffsets.length * 4); 294 | } 295 | size += m_strings.length; 296 | int modeSize = m_strings.length % 4; 297 | if(modeSize != 0){ 298 | modeSize = 4 - modeSize; 299 | size += modeSize; 300 | } 301 | if(m_styles != null && m_styles.length != 0){ 302 | size += (m_styles.length * 4); 303 | } 304 | return size; 305 | } 306 | 307 | public int[] getStyleOffset() { 308 | return m_styleOffsets; 309 | } 310 | 311 | public int[] getStyle() { 312 | return m_styles; 313 | } 314 | 315 | public int getAlignCount() { 316 | return mAlignDataCount; 317 | } 318 | 319 | private int mAlignDataCount; 320 | private int[] m_stringOffsets; 321 | private byte[] m_strings; 322 | private int[] m_styleOffsets; 323 | private int[] m_styles; 324 | private boolean m_isUTF8; 325 | private int[] m_stringOwns; 326 | private static final CharsetDecoder UTF16LE_DECODER = Charset.forName( 327 | "UTF-16LE").newDecoder(); 328 | private static final CharsetDecoder UTF8_DECODER = Charset.forName("UTF-8") 329 | .newDecoder(); 330 | private static final Logger LOGGER = Logger.getLogger(StringBlock.class 331 | .getName()); 332 | private static final int CHUNK_TYPE = 0x001C0001; 333 | private static final int UTF8_FLAG = 0x00000100; 334 | 335 | 336 | } 337 | -------------------------------------------------------------------------------- /src/com/guye/baffle/exception/BaffleException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.exception; 24 | 25 | 26 | 27 | public class BaffleException extends Throwable{ 28 | 29 | public BaffleException(Throwable e) { 30 | super(e); 31 | } 32 | 33 | public BaffleException(String string) { 34 | super(string); 35 | } 36 | 37 | public BaffleException(String string, Throwable ex) { 38 | super(string ,ex); 39 | } 40 | 41 | /** 42 | * 43 | */ 44 | private static final long serialVersionUID = -3246142977285452035L; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/com/guye/baffle/exception/BaffleRuntimeException.java: -------------------------------------------------------------------------------- 1 | /** 2 | /** 3 | * Baffle Project 4 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software 8 | * without restriction, including without limitation the rights to use, copy, modify, 9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | * @author guye 22 | * 23 | **/ 24 | package com.guye.baffle.exception; 25 | 26 | public class BaffleRuntimeException extends RuntimeException { 27 | 28 | /** 29 | * 30 | */ 31 | private static final long serialVersionUID = -9113206701233964331L; 32 | 33 | public BaffleRuntimeException(String string) { 34 | super(string); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/com/guye/baffle/exception/UndefinedResObject.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.exception; 24 | 25 | public class UndefinedResObject extends RuntimeException { 26 | 27 | public UndefinedResObject(String format) { 28 | super(format); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/com/guye/baffle/obfuscate/ApkBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.obfuscate; 24 | 25 | import java.io.BufferedInputStream; 26 | import java.io.File; 27 | import java.io.FileInputStream; 28 | import java.io.FileOutputStream; 29 | import java.io.IOException; 30 | import java.util.List; 31 | import java.util.jar.JarEntry; 32 | import java.util.jar.JarOutputStream; 33 | import java.util.zip.ZipEntry; 34 | 35 | import com.guye.baffle.util.ZipInfo; 36 | 37 | public class ApkBuilder { 38 | 39 | private static final int BUFFER = 4086; 40 | private List zipinfos; 41 | private ZipInfo arscInfo ; 42 | private ObfuscateHelper obfuscater; 43 | 44 | public ApkBuilder(ObfuscateHelper obfuscater , List zipinfos, ZipInfo arscInfo ){ 45 | this.zipinfos = zipinfos; 46 | this.arscInfo = arscInfo; 47 | this.obfuscater = obfuscater; 48 | } 49 | 50 | 51 | public void reBuildapk(String apkFilepath, String dir ) throws IOException { 52 | FileOutputStream fileOutputStream = new FileOutputStream(apkFilepath); 53 | JarOutputStream out = new JarOutputStream(fileOutputStream); 54 | File file; 55 | 56 | for (ZipInfo zipInfo : zipinfos) { 57 | 58 | try { 59 | file = new File(dir + zipInfo.getKey("res")); 60 | JarEntry jarEntry ; 61 | if(zipInfo.type == null){ 62 | jarEntry = new JarEntry(zipInfo.getKey(obfuscater.getResKey())); 63 | }else{ 64 | jarEntry = new JarEntry(getNewKey(zipInfo)); 65 | } 66 | 67 | if(zipInfo != null && zipInfo.name.equals("resources.arsc")){ 68 | file = new File(dir + "resources.n.arsc"); 69 | jarEntry.setMethod(ZipEntry.STORED); 70 | jarEntry.setSize(arscInfo.size); 71 | jarEntry.setCompressedSize(arscInfo.size); 72 | jarEntry.setCrc((long)arscInfo.crc); 73 | }else if(zipInfo!= null && (zipInfo.zipMethod == ZipEntry.STORED)){ 74 | jarEntry.setMethod(ZipEntry.STORED); 75 | jarEntry.setSize(zipInfo.size); 76 | jarEntry.setCompressedSize(zipInfo.size); 77 | jarEntry.setCrc((long)zipInfo.crc); 78 | } 79 | BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); 80 | jarEntry.setTime(file.lastModified()); 81 | out.putNextEntry(jarEntry); 82 | int count; 83 | byte data[] = new byte[BUFFER]; 84 | while ((count = bis.read(data, 0, BUFFER)) != -1) { 85 | out.write(data, 0, count); 86 | } 87 | bis.close(); 88 | } catch (Exception e) { 89 | throw new RuntimeException(e); 90 | } 91 | 92 | } 93 | 94 | out.close(); 95 | fileOutputStream.close(); 96 | 97 | } 98 | 99 | 100 | private String getNewKey( ZipInfo zipInfo ) { 101 | return obfuscater.getNewKey(zipInfo); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/com/guye/baffle/obfuscate/Main.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.obfuscate; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.util.logging.Level; 28 | import java.util.logging.Logger; 29 | 30 | import org.apache.commons.cli.CommandLine; 31 | import org.apache.commons.cli.CommandLineParser; 32 | import org.apache.commons.cli.HelpFormatter; 33 | import org.apache.commons.cli.Options; 34 | import org.apache.commons.cli.ParseException; 35 | import org.apache.commons.cli.PosixParser; 36 | 37 | import com.guye.baffle.exception.BaffleException; 38 | import com.guye.baffle.util.ConsoleHandler; 39 | 40 | public class Main { 41 | public static void main(String[] args) throws IOException, BaffleException { 42 | Options opt = new Options(); 43 | 44 | opt.addOption("c", "config", true, "config file path,keep or mapping"); 45 | 46 | opt.addOption("r", "repeat", true, "output of repeat file check result"); 47 | 48 | opt.addOption("o", "output", true, "output mapping writer file"); 49 | 50 | opt.addOption("w", "towebp", true, "change image file to webp , param is your apk mini sdk level"); 51 | 52 | opt.addOption("d", "delete", false, "delete same file in the apk"); 53 | 54 | opt.addOption("v", "verbose", false, "explain what is being done."); 55 | 56 | opt.addOption("h", "help", false, "print help for the command."); 57 | 58 | opt.getOption("c").setArgName("file list"); 59 | 60 | opt.getOption("o").setArgName("file path"); 61 | 62 | opt.getOption("r").setArgName("file path"); 63 | 64 | String formatstr = "baffle [-c/--config filepaths list ][-o/--output filepath][-r/--repeat filepath][-h/--help] ApkFile TargetApkFile"; 65 | 66 | HelpFormatter formatter = new HelpFormatter(); 67 | CommandLineParser parser = new PosixParser(); 68 | CommandLine cl = null; 69 | try { 70 | // 处理Options和参数 71 | cl = parser.parse(opt, args); 72 | 73 | } catch (ParseException e) { 74 | formatter.printHelp(formatstr, opt); // 如果发生异常,则打印出帮助信息 75 | return; 76 | } 77 | 78 | if (cl == null || cl.getArgs() == null || cl.getArgs().length == 0) { 79 | formatter.printHelp(formatstr, opt); 80 | return; 81 | } 82 | 83 | // 如果包含有-h或--help,则打印出帮助信息 84 | if (cl.hasOption("h")) { 85 | HelpFormatter hf = new HelpFormatter(); 86 | hf.printHelp(formatstr, "", opt, ""); 87 | return; 88 | } 89 | 90 | // 获取参数值,这里主要是DirectoryName 91 | String[] str = cl.getArgs(); 92 | if (str == null || str.length != 2) { 93 | HelpFormatter hf = new HelpFormatter(); 94 | hf.printHelp("not specify apk file or taget apk file", opt); 95 | return; 96 | } 97 | 98 | if (str[1].equals(str[0])) { 99 | HelpFormatter hf = new HelpFormatter(); 100 | hf.printHelp( 101 | "apk file can not rewrite , please specify new target file", 102 | opt); 103 | return; 104 | } 105 | File apkFile = new File(str[0]); 106 | if (!apkFile.exists()) { 107 | HelpFormatter hf = new HelpFormatter(); 108 | hf.printHelp("apk file not exists", opt); 109 | return; 110 | } 111 | 112 | File[] configs = null; 113 | if (cl.hasOption("c")) { 114 | String cfg = cl.getOptionValue("c"); 115 | String[] fs = cfg.split(","); 116 | int len = fs.length; 117 | configs = new File[fs.length]; 118 | for (int i = 0; i < len; i++) { 119 | configs[i] = new File(fs[i]); 120 | if (!configs[i].exists()) { 121 | HelpFormatter hf = new HelpFormatter(); 122 | hf.printHelp("config file " + fs[i] + " not exists", opt); 123 | return; 124 | } 125 | } 126 | } 127 | 128 | File mappingfile = null; 129 | if (cl.hasOption("o")) { 130 | String mfile = cl.getOptionValue("o"); 131 | mappingfile = new File(mfile); 132 | 133 | if (mappingfile.getParentFile() != null) { 134 | mappingfile.getParentFile().mkdirs(); 135 | } 136 | 137 | } 138 | 139 | File repeatfile = null; 140 | if (cl.hasOption("r")) { 141 | String mfile = cl.getOptionValue("r"); 142 | repeatfile = new File(mfile); 143 | 144 | if (repeatfile.getParentFile() != null) { 145 | repeatfile.getParentFile().mkdirs(); 146 | } 147 | 148 | } 149 | 150 | if(cl.hasOption('v')){ 151 | Logger.getLogger(Obfuscater.LOG_NAME).setLevel(Level.CONFIG); 152 | }else{ 153 | Logger.getLogger(Obfuscater.LOG_NAME).setLevel(Level.OFF); 154 | } 155 | 156 | Logger.getLogger(Obfuscater.LOG_NAME).addHandler(new ConsoleHandler()); 157 | 158 | Obfuscater obfuscater = new Obfuscater(configs, mappingfile, repeatfile , apkFile, 159 | str[1]); 160 | 161 | obfuscater.setWebpParam(cl.hasOption('w') , cl.getOptionValue('w')); 162 | obfuscater.setRemoveSameFile(cl.hasOption('d')); 163 | obfuscater.obfuscate(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/com/guye/baffle/obfuscate/NameFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.obfuscate; 24 | 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | public class NameFactory { 29 | 30 | private static char chara = 'a'; 31 | 32 | private Map mUsingName = new HashMap(16); 33 | 34 | public String createName(String type){ 35 | String name = mUsingName.get(type); 36 | if(name == null){ 37 | name = String.valueOf(chara); 38 | mUsingName.put(type, name); 39 | return name; 40 | }else{ 41 | char[] chars = name.toCharArray(); 42 | StringBuilder stringBuilder = new StringBuilder(chars.length); 43 | if(add(chars, chars.length -1)){ 44 | stringBuilder.append(chara); 45 | } 46 | stringBuilder.append(chars); 47 | mUsingName.put(type, stringBuilder.toString()); 48 | return stringBuilder.toString(); 49 | } 50 | } 51 | 52 | private boolean add(char[] c, int index){ 53 | if(c[index] < 'z'){ 54 | c[index]++; 55 | return false; 56 | }else{ 57 | c[index] = chara; 58 | if(index > 0){ 59 | return add(c , index-1); 60 | }else{ 61 | return true; 62 | } 63 | } 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/com/guye/baffle/obfuscate/ObfuscateHelper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.obfuscate; 24 | 25 | import java.util.Collections; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | import java.util.Map.Entry; 29 | import java.util.Set; 30 | import java.util.logging.Level; 31 | import java.util.logging.Logger; 32 | 33 | import com.guye.baffle.config.BaffleConfig; 34 | import com.guye.baffle.decoder.ArscData; 35 | import com.guye.baffle.decoder.StringBlock; 36 | import com.guye.baffle.exception.BaffleException; 37 | import com.guye.baffle.exception.BaffleRuntimeException; 38 | import com.guye.baffle.util.ZipInfo; 39 | 40 | public class ObfuscateHelper { 41 | private Map usedKey = new HashMap(); 42 | 43 | public static final String RES_PROFIX = "res/"; 44 | 45 | public static final String RES_KEY = "res"; 46 | private static final String RES_KEY_KEY = "key_"; 47 | 48 | private static final String DEFAULT_RES_KEY = "res"; 49 | 50 | private Logger log = Logger.getLogger(Obfuscater.LOG_NAME); 51 | 52 | private Map mTypeMaping = new HashMap(16); 53 | private Map mKeyMaping = new HashMap(100); 54 | private Map mWebpMapping; 55 | private Map mChangeEuqalMapping; 56 | private BaffleConfig baffleConfig; 57 | private NameFactory factory; 58 | 59 | ObfuscateHelper(BaffleConfig config) throws BaffleException { 60 | baffleConfig = config; 61 | Map map = baffleConfig.getAllMapping(); 62 | log.log(Level.CONFIG, "ObfuscateHelper:---map>>>" + map.size()); 63 | Set> set = map.entrySet(); 64 | for (Entry entry : set) { 65 | if (usedKey.containsKey(entry.getValue())) { 66 | throw new BaffleException("duplicate map config : " 67 | + entry.getKey() + "--->" + entry.getValue()); 68 | } 69 | usedKey.put(entry.getValue(), Boolean.TRUE); 70 | } 71 | factory = new NameFactory(); 72 | } 73 | 74 | public String createTypeName(String type) { 75 | if (baffleConfig.isKeepKey(type)) { 76 | return type; 77 | }else{ 78 | return factory.createName(RES_KEY); 79 | } 80 | } 81 | 82 | public String getResKey() { 83 | return DEFAULT_RES_KEY; 84 | } 85 | 86 | public String createKeyName(String key) { 87 | String newKey = baffleConfig.getMapping(key); 88 | if (newKey == null) { 89 | if (baffleConfig.isKeepKey(key)) { 90 | if (usedKey.containsKey(key)) { 91 | throw new BaffleRuntimeException("error keep key config :" 92 | + key); 93 | } else { 94 | usedKey.put(key, Boolean.TRUE); 95 | return key; 96 | } 97 | } else { 98 | newKey = factory.createName(RES_KEY_KEY); 99 | while (usedKey.containsKey(newKey)) { 100 | newKey = factory.createName(RES_KEY_KEY); 101 | } 102 | usedKey.put(newKey, Boolean.TRUE); 103 | return newKey; 104 | } 105 | } else { 106 | return newKey; 107 | } 108 | } 109 | 110 | public void obfuscate(ArscData orgData) { 111 | int strPoolCount = orgData.getmTableStrings().getCount(); 112 | String orgString; 113 | String[] names; 114 | String[] newNames; 115 | for (int i = 0; i < strPoolCount; i++) { 116 | orgString = orgData.getmTableStrings().getString(i); 117 | 118 | if (orgString.startsWith(RES_PROFIX)) { 119 | log.log(Level.CONFIG,"orgResString:---->>" + orgString); 120 | names = orgString.split("/"); 121 | if (names == null || names.length != 3) { 122 | throw new RuntimeException(); // TODO 123 | } 124 | newNames = new String[3]; 125 | newNames[0] = getResKey(); 126 | String typeName = mTypeMaping.get(names[1]); 127 | if (typeName == null) { 128 | newNames[1] = createTypeName(names[1]); 129 | mTypeMaping.put(names[1], newNames[1]); 130 | } else { 131 | newNames[1] = typeName; 132 | } 133 | int index = names[2].indexOf('.'); 134 | if (index > 0) { 135 | names[2] = names[2].substring(0, index); 136 | } 137 | newNames[2] = mKeyMaping.get(names[2]); 138 | if (newNames[2] == null) { 139 | newNames[2] = createKeyName(names[2]); 140 | } 141 | mKeyMaping.put(names[2], newNames[2]); 142 | } 143 | } 144 | StringBlock keyStrings = orgData.getmSpecNames(); 145 | int keyCount = keyStrings.getCount(); 146 | String keyName; 147 | String newKey; 148 | for (int i = 0; i < keyCount; i++) { 149 | keyName = keyStrings.getString(i); 150 | if (!mKeyMaping.containsKey(keyName)) { 151 | newKey = createKeyName(keyName); 152 | mKeyMaping.put(keyName, newKey); 153 | log.log(Level.CONFIG,keyStrings.getString(i) + " ---> " + newKey); 154 | } else { 155 | log.log(Level.CONFIG,keyStrings.getString(i) + " ---> " 156 | + mKeyMaping.get(keyName)); 157 | } 158 | } 159 | } 160 | 161 | String getNewKeyString(String orgString) { 162 | return mKeyMaping.get(orgString); 163 | } 164 | 165 | String getNewTableString(String orgString) { 166 | if (orgString.startsWith(RES_PROFIX)) { 167 | String webpOrgString = mWebpMapping.get(orgString); 168 | if(webpOrgString != null){ 169 | orgString = webpOrgString; 170 | } 171 | String changeFile = mChangeEuqalMapping==null?null:mChangeEuqalMapping.get(orgString); 172 | if(changeFile != null){ 173 | orgString = changeFile; 174 | } 175 | String[] names = orgString.split("/"); 176 | if (names == null || names.length != 3) { 177 | throw new RuntimeException(); // TODO 178 | } 179 | String[] newNames = new String[3]; 180 | newNames[0] = getResKey(); 181 | newNames[1] = mTypeMaping.get(names[1]); 182 | if (newNames[1] == null) { 183 | throw new RuntimeException(); // TODO 184 | } 185 | int index = names[2].indexOf('.'); 186 | String postfix = ""; 187 | if (index > 0) { 188 | postfix = names[2].substring(index, names[2].length()); 189 | names[2] = names[2].substring(0, index); 190 | } 191 | newNames[2] = mKeyMaping.get(names[2]); 192 | if (newNames[2] == null) { 193 | throw new RuntimeException(); // TODO 194 | } 195 | String newString = new StringBuilder().append(newNames[0]) 196 | .append('/').append(newNames[1]).append('/') 197 | .append(newNames[2]).append(postfix).toString(); 198 | return newString; 199 | } else { 200 | return orgString; 201 | } 202 | } 203 | 204 | public String getNewKey(ZipInfo zipInfo) { 205 | if (zipInfo.type == null) { 206 | return zipInfo.getKey(RES_KEY); 207 | } else { 208 | zipInfo.type = mTypeMaping.get(zipInfo.type); 209 | zipInfo.name = mKeyMaping.get(zipInfo.name); 210 | return zipInfo.getKey(getResKey()); 211 | } 212 | } 213 | 214 | public ObfuscateData getObfuscateData() { 215 | ObfuscateData data = new ObfuscateData(); 216 | data.resName = getResKey(); 217 | data.typeMaping = Collections.unmodifiableMap(mTypeMaping); 218 | data.keyMaping = Collections.unmodifiableMap(mKeyMaping); 219 | return data; 220 | } 221 | 222 | public static class ObfuscateData { 223 | public String resName; 224 | public Map typeMaping; 225 | public Map keyMaping; 226 | } 227 | 228 | public void setWebpMapping(Map map) { 229 | this.mWebpMapping = map; 230 | 231 | } 232 | 233 | public void setChangeEqualMapping(Map changeEqualFile) { 234 | mChangeEuqalMapping = changeEqualFile; 235 | 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/com/guye/baffle/obfuscate/Obfuscater.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.obfuscate; 24 | 25 | import java.io.ByteArrayOutputStream; 26 | import java.io.File; 27 | import java.io.FileNotFoundException; 28 | import java.io.IOException; 29 | import java.io.PrintStream; 30 | import java.nio.charset.Charset; 31 | import java.util.ArrayList; 32 | import java.util.Collections; 33 | import java.util.Comparator; 34 | import java.util.HashMap; 35 | import java.util.List; 36 | import java.util.Map; 37 | import java.util.Map.Entry; 38 | import java.util.Set; 39 | import java.util.logging.Level; 40 | import java.util.logging.Logger; 41 | import java.util.zip.CRC32; 42 | import java.util.zip.ZipEntry; 43 | 44 | import com.guye.baffle.config.BaffleConfig; 45 | import com.guye.baffle.config.ConfigReader; 46 | import com.guye.baffle.config.MappingWriter; 47 | import com.guye.baffle.decoder.ArscData; 48 | import com.guye.baffle.decoder.StringBlock; 49 | import com.guye.baffle.exception.BaffleException; 50 | import com.guye.baffle.util.ApkFileUtils; 51 | import com.guye.baffle.util.LEDataOutputStream; 52 | import com.guye.baffle.util.OS; 53 | import com.guye.baffle.util.ZipInfo; 54 | 55 | public class Obfuscater { 56 | 57 | public static final String LOG_NAME = "BAFFLE"; 58 | private static final Charset UTF_8_CHARSET = Charset.forName("UTF-8"); 59 | 60 | private List mZipinfos; 61 | private ObfuscateHelper mObfuscateHelper; 62 | private ArscData mArscData; 63 | 64 | private BaffleConfig mBaffleConfig; 65 | 66 | private MappingWriter mMappingWrite; 67 | 68 | private File[] mConfigFiles; 69 | 70 | private File mApkFile; 71 | 72 | private String mTarget; 73 | 74 | private File mMappingFile; 75 | 76 | private File mRepeatFile; 77 | 78 | private Logger log = Logger.getLogger(LOG_NAME); 79 | 80 | private Map mWebpMapping = new HashMap<>(1000); 81 | private boolean toWebp; 82 | private int minLevelInt; 83 | private boolean hasOption; 84 | private boolean removeSameFile; 85 | 86 | public Obfuscater(File[] configs, File mappingFile, File repeatFile, File apkFile, String target) { 87 | mConfigFiles = configs; 88 | mApkFile = apkFile; 89 | mTarget = target; 90 | mRepeatFile = repeatFile; 91 | mMappingFile = mappingFile; 92 | } 93 | 94 | private StringBlock createStrings(StringBlock orgTableStrings, boolean isTableString) { 95 | try { 96 | ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(); 97 | LEDataOutputStream dataOutputStream = new LEDataOutputStream(arrayOutputStream); 98 | int count = orgTableStrings.getCount(); 99 | int curOffset = 0; 100 | int[] offset = new int[count]; 101 | String newStr; 102 | byte[] strData; 103 | byte[] l = new byte[2]; 104 | byte[] l1 = new byte[2]; 105 | int offsetLen = 1; 106 | int offsetDataLen = 1; 107 | for (int i = 0; i < count; i++) { 108 | if (isTableString) { 109 | newStr = mObfuscateHelper.getNewTableString(orgTableStrings.getString(i)); 110 | } else { 111 | newStr = mObfuscateHelper.getNewKeyString(orgTableStrings.getString(i)); 112 | } 113 | strData = newStr.getBytes(UTF_8_CHARSET); 114 | offset[i] = curOffset; 115 | if (newStr.length() < 128) { 116 | offsetLen = 1; 117 | l[0] = (byte) (0x7f & (newStr.length())); 118 | } else { 119 | offsetLen = 2; 120 | short len = (short) (newStr.length()); 121 | l[0] = (byte) ((byte) ((len & 0xff00) >> 8) | 0x80); 122 | l[1] = (byte) (len & 0x00ff); 123 | } 124 | 125 | if (strData.length < 128) { 126 | l1[0] = (byte) (0x7f & (strData.length)); 127 | offsetDataLen = 1; 128 | } else { 129 | offsetDataLen = 2; 130 | short len = (short) (strData.length); 131 | l1[0] = (byte) ((byte) ((len & 0xff00) >> 8) | 0x80); 132 | l1[1] = (byte) (len & 0x00ff); 133 | } 134 | dataOutputStream.write(l, 0, offsetLen); 135 | dataOutputStream.write(l1, 0, offsetDataLen); 136 | dataOutputStream.write(strData); 137 | dataOutputStream.write(0); 138 | curOffset += (offsetLen + offsetDataLen + strData.length + 1); 139 | } 140 | 141 | strData = arrayOutputStream.toByteArray(); 142 | dataOutputStream.close(); 143 | arrayOutputStream.close(); 144 | 145 | return new StringBlock(offset, strData, orgTableStrings.getStyleOffset(), orgTableStrings.getStyle(), true); 146 | } catch (IOException e) {// not a disk IO option 147 | e.printStackTrace(); 148 | } 149 | return null; 150 | } 151 | 152 | public void obfuscate() throws IOException, BaffleException { 153 | 154 | String tempDir = System.getProperty("java.io.tmpdir") + File.separator + "old" + File.separator; 155 | log.log(Level.CONFIG, "tempDir:::" + tempDir); 156 | File temp = new File(tempDir); 157 | OS.rmdir(temp); 158 | temp.mkdirs(); 159 | 160 | // read keep and mapping config 161 | mBaffleConfig = new ConfigReader().read(mConfigFiles); 162 | 163 | mObfuscateHelper = new ObfuscateHelper(mBaffleConfig); 164 | 165 | // unzip apk or ap_ file 166 | List zipinfos = ApkFileUtils.unZipApk(mApkFile, tempDir, toWebp ,mWebpMapping,minLevelInt); 167 | 168 | if(removeSameFile){ 169 | removeEqualFile(temp, zipinfos); 170 | } 171 | 172 | 173 | // decode arsc file 174 | mArscData = ArscData.decode(new File(tempDir + "resources.arsc")); 175 | 176 | // do obfuscate 177 | mObfuscateHelper.obfuscate(mArscData); 178 | 179 | mObfuscateHelper.setWebpMapping(mWebpMapping); 180 | 181 | // write mapping file 182 | if (mMappingFile != null) 183 | 184 | { 185 | mMappingWrite = new MappingWriter(mObfuscateHelper.getObfuscateData().keyMaping); 186 | mMappingWrite.WriteToFile(mMappingFile); 187 | } else { 188 | log.log(Level.CONFIG, "not specific mapping file"); 189 | } 190 | 191 | StringBlock tableBlock = createStrings(mArscData.getmTableStrings(), true); 192 | StringBlock keyBlock = createStrings(mArscData.getmSpecNames(), false); 193 | File arscFile = new File(tempDir + "resources.n.arsc"); 194 | CRC32 arscCrc = mArscData.createObfuscateFile(tableBlock, keyBlock, arscFile); 195 | 196 | mZipinfos = zipinfos; 197 | 198 | ZipInfo arscInfo = new ZipInfo("resources.arsc", ZipEntry.STORED, arscFile.length(), arscCrc.getValue(), ""); 199 | 200 | try { 201 | new ApkBuilder(mObfuscateHelper, mZipinfos, arscInfo).reBuildapk(mTarget, tempDir); 202 | } catch (IOException e) { 203 | OS.rmfile(mTarget); 204 | throw e; 205 | } 206 | 207 | OS.rmdir(temp); 208 | } 209 | 210 | private void removeEqualFile( File temp, List zipinfos ) throws FileNotFoundException { 211 | Map changeEqualFile = new HashMap<>(100); 212 | 213 | PrintStream printStream = null; 214 | if (mRepeatFile != null) { 215 | printStream = new PrintStream(mRepeatFile); 216 | } 217 | List sortedZipinfo = new ArrayList(zipinfos.size()); 218 | sortedZipinfo.addAll(zipinfos); 219 | 220 | Collections.sort(sortedZipinfo, new Comparator() { 221 | 222 | @Override 223 | public int compare(ZipInfo o1, ZipInfo o2) { 224 | return o1.getDigest().compareTo(o2.getDigest()); 225 | } 226 | }); 227 | 228 | int size = zipinfos.size(); 229 | int index = 0; 230 | ZipInfo info = null; 231 | info = sortedZipinfo.get(index); 232 | Map> map = new HashMap>(); 233 | while (index < size - 1) { 234 | if (info.getDigest().equals(sortedZipinfo.get(index + 1).getDigest())) { 235 | List infos = map.get(info.getDigest()); 236 | if (infos == null) { 237 | infos = new ArrayList(); 238 | map.put(info.getDigest(), infos); 239 | infos.add(info); 240 | } 241 | infos.add(sortedZipinfo.get(index + 1)); 242 | index += 1; 243 | if (index >= size) { 244 | break; 245 | } 246 | info = sortedZipinfo.get(index); 247 | } else { 248 | index += 1; 249 | if (index >= size) { 250 | break; 251 | } 252 | info = sortedZipinfo.get(index); 253 | } 254 | } 255 | Set>> entries = map.entrySet(); 256 | for (Entry> entry : entries) { 257 | if (mRepeatFile != null) { 258 | printStream.println("md5:" + entry.getKey()); 259 | } 260 | String firstFile = null; 261 | for (ZipInfo z : entry.getValue()) { 262 | if(!z.getOrginName().startsWith("res/")){ 263 | continue; 264 | } 265 | if (firstFile == null) { 266 | firstFile = z.getOrginName(); 267 | }else{ 268 | File dFile = new File(temp,z.getOrginName()); 269 | dFile.delete(); 270 | System.out.println(z.getOrginName()); 271 | zipinfos.remove(z); 272 | } 273 | 274 | changeEqualFile.put(z.getOrginName(), firstFile); 275 | 276 | if (mRepeatFile != null) { 277 | printStream.println("\t" + z.getOrginName()); 278 | } 279 | } 280 | if (mRepeatFile != null) { 281 | printStream.println("----------"); 282 | } 283 | } 284 | if (mRepeatFile != null) { 285 | printStream.close(); 286 | } 287 | 288 | mObfuscateHelper.setChangeEqualMapping(changeEqualFile); 289 | } 290 | 291 | public void setWebpParam( boolean webp, String minLevel ) { 292 | this.toWebp = webp; 293 | if(toWebp){ 294 | try{ 295 | minLevelInt = Integer.parseInt(minLevel); 296 | }catch(Exception e){ 297 | throw new RuntimeException("can not parse webp mini sdk level"); 298 | } 299 | } 300 | 301 | } 302 | 303 | public void setRemoveSameFile( boolean removeSameFile ) { 304 | this.removeSameFile = removeSameFile; 305 | 306 | 307 | } 308 | 309 | } 310 | -------------------------------------------------------------------------------- /src/com/guye/baffle/util/AntLogHandle.java: -------------------------------------------------------------------------------- 1 | package com.guye.baffle.util; 2 | 3 | import java.util.logging.Handler; 4 | import java.util.logging.LogRecord; 5 | 6 | import org.apache.tools.ant.Task; 7 | import org.apache.tools.ant.types.LogLevel; 8 | 9 | public class AntLogHandle extends Handler { 10 | 11 | private Task task; 12 | 13 | public AntLogHandle(Task task) { 14 | this.task = task; 15 | } 16 | 17 | @Override 18 | public void publish( LogRecord record ) { 19 | task.log(record.getMessage(), LogLevel.INFO.getLevel()); 20 | 21 | } 22 | 23 | @Override 24 | public void flush() { 25 | } 26 | 27 | @Override 28 | public void close() throws SecurityException { 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/com/guye/baffle/util/ApkFileUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.util; 24 | 25 | import java.io.BufferedOutputStream; 26 | import java.io.File; 27 | import java.io.FileOutputStream; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.security.DigestOutputStream; 31 | import java.security.MessageDigest; 32 | import java.security.NoSuchAlgorithmException; 33 | import java.util.ArrayList; 34 | import java.util.Enumeration; 35 | import java.util.List; 36 | import java.util.Map; 37 | import java.util.zip.ZipEntry; 38 | import java.util.zip.ZipFile; 39 | 40 | import com.guye.baffle.decoder.MakeCsc; 41 | import com.guye.baffle.webp.WebpIO; 42 | 43 | public class ApkFileUtils { 44 | public static List unZipApk( File apkFile, String tempDir, boolean toWebp , Map webpMapping ,int minilevel) throws IOException { 45 | ZipFile in = new ZipFile(apkFile); 46 | File out = new File(tempDir); 47 | File outFile; 48 | BufferedOutputStream bos; 49 | InputStream input; 50 | int length = -1; 51 | byte[] buffer = new byte[8192]; 52 | Enumeration entries = in.entries(); 53 | List zipInfos = new ArrayList(100); 54 | ZipInfo info; 55 | while (entries.hasMoreElements()) { 56 | ZipEntry entry = entries.nextElement(); 57 | 58 | if (entry.isDirectory() || entry.getName().startsWith("META-INF")) { 59 | continue; 60 | } 61 | outFile = new File(out, entry.getName()); 62 | if (!outFile.getParentFile().exists()) { 63 | outFile.getParentFile().mkdirs(); 64 | } 65 | bos = new BufferedOutputStream(new FileOutputStream(outFile)); 66 | MessageDigest md5 = null; 67 | try { 68 | md5 = MessageDigest.getInstance("MD5"); 69 | } catch (NoSuchAlgorithmException e) { 70 | e.printStackTrace(); 71 | // do nothing 72 | } 73 | DigestOutputStream digestOutputStream = new DigestOutputStream(bos, md5); 74 | input = in.getInputStream(entry); 75 | while (true) { 76 | length = input.read(buffer); 77 | if (length == -1) 78 | break; 79 | digestOutputStream.write(buffer, 0, length); 80 | } 81 | digestOutputStream.close(); 82 | input.close(); 83 | if (!entry.isDirectory()) { 84 | if(toWebp && (entry.getName().startsWith("res/")) && ((entry.getName().endsWith(".png") && !entry.getName().endsWith(".9.png")) || entry.getName().endsWith(".jpg") || entry.getName().endsWith(".jpeg"))){ 85 | File newOutfile = new File(outFile.getParentFile() , getName(outFile.getName())); 86 | boolean hasChange = WebpIO.toWebp(outFile, newOutfile , minilevel); 87 | int index = entry.getName().indexOf('.'); 88 | if(hasChange){ 89 | long crc = 0; 90 | try { 91 | crc = MakeCsc.getFileCRCCode(newOutfile); 92 | } catch (Exception e) { 93 | throw new RuntimeException(e); 94 | } 95 | info = new ZipInfo(entry.getName().substring(0, index)+".webp", entry.getMethod(), newOutfile.length(), 96 | crc, toHex(md5.digest())); 97 | 98 | webpMapping.put(entry.getName(), entry.getName().substring(0, index)+".webp"); 99 | }else{ 100 | info = new ZipInfo(entry.getName(), entry.getMethod(), entry.getSize(), 101 | entry.getCrc(), toHex(md5.digest())); 102 | } 103 | }else{ 104 | info = new ZipInfo(entry.getName(), entry.getMethod(), entry.getSize(), 105 | entry.getCrc(), toHex(md5.digest())); 106 | } 107 | 108 | zipInfos.add(info); 109 | } 110 | } 111 | in.close(); 112 | return zipInfos; 113 | } 114 | 115 | private static String getName( String name ) { 116 | name = name.substring(0,name.indexOf('.')); 117 | return name+".webp"; 118 | } 119 | 120 | /** 121 | * md5 摘要转16进制 122 | * 123 | * @param digest 124 | * @return 125 | */ 126 | private static String toHex( byte[] digest ) { 127 | StringBuilder sb = new StringBuilder(); 128 | int len = digest.length; 129 | 130 | String out = null; 131 | for (int i = 0; i < len; i++) { 132 | // out = Integer.toHexString(0xFF & digest[i] + 0xABCDEF); //加任意 133 | // salt 134 | out = Integer.toHexString(0xFF & digest[i]);// 原始方法 135 | if (out.length() == 1) { 136 | sb.append("0");// 如果为1位 前面补个0 137 | } 138 | sb.append(out); 139 | } 140 | return sb.toString(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/com/guye/baffle/util/ConsoleHandler.java: -------------------------------------------------------------------------------- 1 | package com.guye.baffle.util; 2 | 3 | import java.util.logging.Handler; 4 | import java.util.logging.LogRecord; 5 | 6 | public class ConsoleHandler extends Handler{ 7 | 8 | @Override 9 | public void publish( LogRecord record ) { 10 | System.out.println(record.getLevel() + ":" + record.getMessage()); 11 | } 12 | 13 | @Override 14 | public void flush() { 15 | 16 | } 17 | 18 | @Override 19 | public void close() throws SecurityException { 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/com/guye/baffle/util/DataInputDelegate.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.util; 24 | 25 | import java.io.DataInput; 26 | import java.io.IOException; 27 | 28 | /** 29 | * @author Ryszard Wiśniewski 30 | */ 31 | abstract public class DataInputDelegate implements DataInput { 32 | protected final DataInput mDelegate; 33 | 34 | public DataInputDelegate(DataInput delegate) { 35 | this.mDelegate = delegate; 36 | } 37 | 38 | public int skipBytes(int n) throws IOException { 39 | return mDelegate.skipBytes(n); 40 | } 41 | 42 | public int readUnsignedShort() throws IOException { 43 | return mDelegate.readUnsignedShort(); 44 | } 45 | 46 | public int readUnsignedByte() throws IOException { 47 | return mDelegate.readUnsignedByte(); 48 | } 49 | 50 | public String readUTF() throws IOException { 51 | return mDelegate.readUTF(); 52 | } 53 | 54 | public short readShort() throws IOException { 55 | return mDelegate.readShort(); 56 | } 57 | 58 | public long readLong() throws IOException { 59 | return mDelegate.readLong(); 60 | } 61 | 62 | public String readLine() throws IOException { 63 | return mDelegate.readLine(); 64 | } 65 | 66 | public int readInt() throws IOException { 67 | return mDelegate.readInt(); 68 | } 69 | 70 | public void readFully(byte[] b, int off, int len) throws IOException { 71 | mDelegate.readFully(b, off, len); 72 | } 73 | 74 | public void readFully(byte[] b) throws IOException { 75 | mDelegate.readFully(b); 76 | } 77 | 78 | public float readFloat() throws IOException { 79 | return mDelegate.readFloat(); 80 | } 81 | 82 | public double readDouble() throws IOException { 83 | return mDelegate.readDouble(); 84 | } 85 | 86 | public char readChar() throws IOException { 87 | return mDelegate.readChar(); 88 | } 89 | 90 | public byte readByte() throws IOException { 91 | return mDelegate.readByte(); 92 | } 93 | 94 | public boolean readBoolean() throws IOException { 95 | return mDelegate.readBoolean(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/com/guye/baffle/util/Duo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 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.guye.baffle.util; 18 | 19 | /** 20 | * @author Ryszard Wiśniewski 21 | */ 22 | public class Duo { 23 | public final T1 m1; 24 | public final T2 m2; 25 | 26 | public Duo(T1 t1, T2 t2) { 27 | this.m1 = t1; 28 | this.m2 = t2; 29 | } 30 | 31 | @Override 32 | public boolean equals(Object obj) { 33 | if (obj == null) { 34 | return false; 35 | } 36 | if (getClass() != obj.getClass()) { 37 | return false; 38 | } 39 | final Duo other = (Duo) obj; 40 | if (this.m1 != other.m1 && (this.m1 == null || !this.m1.equals(other.m1))) { 41 | return false; 42 | } 43 | if (this.m2 != other.m2 && (this.m2 == null || !this.m2.equals(other.m2))) { 44 | return false; 45 | } 46 | return true; 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | int hash = 3; 52 | hash = 71 * hash + (this.m1 != null ? this.m1.hashCode() : 0); 53 | hash = 71 * hash + (this.m2 != null ? this.m2.hashCode() : 0); 54 | return hash; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/com/guye/baffle/util/ExtDataInput.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 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.guye.baffle.util; 18 | 19 | import java.io.*; 20 | 21 | /** 22 | * @author Ryszard Wiśniewski 23 | */ 24 | public class ExtDataInput extends DataInputDelegate { 25 | public ExtDataInput(InputStream in) { 26 | this((DataInput) new DataInputStream(in)); 27 | } 28 | 29 | public ExtDataInput(DataInput delegate) { 30 | super(delegate); 31 | } 32 | 33 | public int[] readIntArray(int length) throws IOException { 34 | int[] array = new int[length]; 35 | for(int i = 0; i < length; i++) { 36 | array[i] = readInt(); 37 | } 38 | return array; 39 | } 40 | 41 | public void skipInt() throws IOException { 42 | skipBytes(4); 43 | } 44 | 45 | public void skipCheckInt(int expected) throws IOException { 46 | int got = readInt(); 47 | if (got != expected) { 48 | throw new IOException(String.format( 49 | "Expected: 0x%08x, got: 0x%08x", expected, got)); 50 | } 51 | } 52 | 53 | public void skipCheckShort(short expected) throws IOException { 54 | short got = readShort(); 55 | if (got != expected) { 56 | throw new IOException(String.format( 57 | "Expected: 0x%08x, got: 0x%08x", expected, got)); 58 | } 59 | } 60 | 61 | public void skipCheckByte(byte expected) throws IOException { 62 | byte got = readByte(); 63 | if (got != expected) { 64 | throw new IOException(String.format( 65 | "Expected: 0x%08x, got: 0x%08x", expected, got)); 66 | } 67 | } 68 | 69 | public String readNulEndedString(int length, boolean fixed) 70 | throws IOException { 71 | StringBuilder string = new StringBuilder(16); 72 | while(length-- != 0) { 73 | short ch = readShort(); 74 | if (ch == 0) { 75 | break; 76 | } 77 | string.append((char) ch); 78 | } 79 | if (fixed) { 80 | skipBytes(length * 2); 81 | } 82 | 83 | return string.toString(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/com/guye/baffle/util/LEDataInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * @(#)LEDataInputStream.java 3 | * 4 | * Summary: Little-Endian version of DataInputStream. 5 | * 6 | * Copyright: (c) 1998-2010 Roedy Green, Canadian Mind Products, http://mindprod.com 7 | * 8 | * Licence: This software may be copied and used freely for any purpose but military. 9 | * http://mindprod.com/contact/nonmil.html 10 | * 11 | * Requires: JDK 1.1+ 12 | * 13 | * Created with: IntelliJ IDEA IDE. 14 | * 15 | * Version History: 16 | * 1.8 2007-05-24 17 | */ 18 | package com.guye.baffle.util; 19 | 20 | import java.io.DataInput; 21 | import java.io.DataInputStream; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | 25 | /** 26 | * Little-Endian version of DataInputStream. 27 | *

28 | * Very similar to DataInputStream except it reads little-endian instead of 29 | * big-endian binary data. We can't extend DataInputStream directly since it has 30 | * only final methods, though DataInputStream itself is not final. This forces 31 | * us implement LEDataInputStream with a DataInputStream object, and use wrapper 32 | * methods. 33 | * 34 | * @author Roedy Green, Canadian Mind Products 35 | * @version 1.8 2007-05-24 36 | * @since 1998 37 | */ 38 | public final class LEDataInputStream implements DataInput { 39 | // ------------------------------ CONSTANTS ------------------------------ 40 | 41 | /** 42 | * undisplayed copyright notice. 43 | * 44 | * @noinspection UnusedDeclaration 45 | */ 46 | private static final String EMBEDDED_COPYRIGHT = "copyright (c) 1999-2010 Roedy Green, Canadian Mind Products, http://mindprod.com"; 47 | 48 | // ------------------------------ FIELDS ------------------------------ 49 | 50 | /** 51 | * to get at the big-Endian methods of a basic DataInputStream 52 | * 53 | * @noinspection WeakerAccess 54 | */ 55 | protected final DataInputStream dis; 56 | 57 | /** 58 | * to get at the a basic readBytes method. 59 | * 60 | * @noinspection WeakerAccess 61 | */ 62 | protected final InputStream is; 63 | 64 | /** 65 | * work array for buffering input. 66 | * 67 | * @noinspection WeakerAccess 68 | */ 69 | protected final byte[] work; 70 | 71 | // -------------------------- PUBLIC STATIC METHODS 72 | // -------------------------- 73 | 74 | /** 75 | * Note. This is a STATIC method! 76 | * 77 | * @param in 78 | * stream to read UTF chars from (endian irrelevant) 79 | * 80 | * @return string from stream 81 | * @throws IOException 82 | * if read fails. 83 | */ 84 | public static String readUTF(DataInput in) throws IOException { 85 | return DataInputStream.readUTF(in); 86 | } 87 | 88 | // -------------------------- PUBLIC INSTANCE METHODS 89 | // -------------------------- 90 | 91 | /** 92 | * constructor. 93 | * 94 | * @param in 95 | * binary inputstream of little-endian data. 96 | */ 97 | public LEDataInputStream(InputStream in) { 98 | this.is = in; 99 | this.dis = new DataInputStream(in); 100 | work = new byte[8]; 101 | } 102 | 103 | /** 104 | * close. 105 | * 106 | * @throws IOException 107 | * if close fails. 108 | */ 109 | public final void close() throws IOException { 110 | dis.close(); 111 | } 112 | 113 | /** 114 | * Read bytes. Watch out, read may return fewer bytes than requested. 115 | * 116 | * @param ba 117 | * where the bytes go. 118 | * @param off 119 | * offset in buffer, not offset in file. 120 | * @param len 121 | * count of bytes to read. 122 | * 123 | * @return how many bytes read. 124 | * @throws IOException 125 | * if read fails. 126 | */ 127 | public final int read(byte ba[], int off, int len) throws IOException { 128 | // For efficiency, we avoid one layer of wrapper 129 | return is.read(ba, off, len); 130 | } 131 | 132 | /** 133 | * read only a one-byte boolean. 134 | * 135 | * @return true or false. 136 | * @throws IOException 137 | * if read fails. 138 | * @see java.io.DataInput#readBoolean() 139 | */ 140 | @Override 141 | public final boolean readBoolean() throws IOException { 142 | return dis.readBoolean(); 143 | } 144 | 145 | /** 146 | * read byte. 147 | * 148 | * @return the byte read. 149 | * @throws IOException 150 | * if read fails. 151 | * @see java.io.DataInput#readByte() 152 | */ 153 | @Override 154 | public final byte readByte() throws IOException { 155 | return dis.readByte(); 156 | } 157 | 158 | /** 159 | * Read on char. like DataInputStream.readChar except little endian. 160 | * 161 | * @return little endian 16-bit unicode char from the stream. 162 | * @throws IOException 163 | * if read fails. 164 | */ 165 | @Override 166 | public final char readChar() throws IOException { 167 | dis.readFully(work, 0, 2); 168 | return (char) ((work[1] & 0xff) << 8 | (work[0] & 0xff)); 169 | } 170 | 171 | /** 172 | * Read a double. like DataInputStream.readDouble except little endian. 173 | * 174 | * @return little endian IEEE double from the datastream. 175 | * @throws IOException 176 | */ 177 | @Override 178 | public final double readDouble() throws IOException { 179 | return Double.longBitsToDouble(readLong()); 180 | } 181 | 182 | /** 183 | * Read one float. Like DataInputStream.readFloat except little endian. 184 | * 185 | * @return little endian IEEE float from the datastream. 186 | * @throws IOException 187 | * if read fails. 188 | */ 189 | @Override 190 | public final float readFloat() throws IOException { 191 | return Float.intBitsToFloat(readInt()); 192 | } 193 | 194 | /** 195 | * Read bytes until the array is filled. 196 | * 197 | * @see java.io.DataInput#readFully(byte[]) 198 | */ 199 | @Override 200 | public final void readFully(byte ba[]) throws IOException { 201 | dis.readFully(ba, 0, ba.length); 202 | } 203 | 204 | /** 205 | * Read bytes until the count is satisfied. 206 | * 207 | * @throws IOException 208 | * if read fails. 209 | * @see java.io.DataInput#readFully(byte[],int,int) 210 | */ 211 | @Override 212 | public final void readFully(byte ba[], int off, int len) throws IOException { 213 | dis.readFully(ba, off, len); 214 | } 215 | 216 | /** 217 | * Read an int, 32-bits. Like DataInputStream.readInt except little endian. 218 | * 219 | * @return little-endian binary int from the datastream 220 | * @throws IOException 221 | * if read fails. 222 | */ 223 | @Override 224 | public final int readInt() throws IOException { 225 | dis.readFully(work, 0, 4); 226 | return (work[3]) << 24 | (work[2] & 0xff) << 16 | (work[1] & 0xff) << 8 227 | | (work[0] & 0xff); 228 | } 229 | 230 | /** 231 | * Read a line. 232 | * 233 | * @return a rough approximation of the 8-bit stream as a 16-bit unicode 234 | * string 235 | * @throws IOException 236 | * @noinspection deprecation 237 | * @deprecated This method does not properly convert bytes to characters. 238 | * Use a Reader instead with a little-endian encoding. 239 | */ 240 | @Deprecated 241 | @Override 242 | public final String readLine() throws IOException { 243 | return dis.readLine(); 244 | } 245 | 246 | /** 247 | * read a long, 64-bits. Like DataInputStream.readLong except little endian. 248 | * 249 | * @return little-endian binary long from the datastream. 250 | * @throws IOException 251 | */ 252 | @Override 253 | public final long readLong() throws IOException { 254 | dis.readFully(work, 0, 8); 255 | return (long) (work[7]) << 56 | 256 | /* long cast needed or shift done modulo 32 */ 257 | (long) (work[6] & 0xff) << 48 | (long) (work[5] & 0xff) << 40 258 | | (long) (work[4] & 0xff) << 32 | (long) (work[3] & 0xff) << 24 259 | | (long) (work[2] & 0xff) << 16 | (long) (work[1] & 0xff) << 8 260 | | work[0] & 0xff; 261 | } 262 | 263 | /** 264 | * Read short, 16-bits. Like DataInputStream.readShort except little endian. 265 | * 266 | * @return little endian binary short from stream. 267 | * @throws IOException 268 | * if read fails. 269 | */ 270 | @Override 271 | public final short readShort() throws IOException { 272 | dis.readFully(work, 0, 2); 273 | return (short) ((work[1] & 0xff) << 8 | (work[0] & 0xff)); 274 | } 275 | 276 | /** 277 | * Read UTF counted string. 278 | * 279 | * @return String read. 280 | */ 281 | @Override 282 | public final String readUTF() throws IOException { 283 | return dis.readUTF(); 284 | } 285 | 286 | /** 287 | * Read an unsigned byte. Note: returns an int, even though says Byte 288 | * (non-Javadoc) 289 | * 290 | * @throws IOException 291 | * if read fails. 292 | * @see java.io.DataInput#readUnsignedByte() 293 | */ 294 | @Override 295 | public final int readUnsignedByte() throws IOException { 296 | return dis.readUnsignedByte(); 297 | } 298 | 299 | /** 300 | * Read an unsigned short, 16 bits. Like DataInputStream.readUnsignedShort 301 | * except little endian. Note, returns int even though it reads a short. 302 | * 303 | * @return little-endian int from the stream. 304 | * @throws IOException 305 | * if read fails. 306 | */ 307 | @Override 308 | public final int readUnsignedShort() throws IOException { 309 | dis.readFully(work, 0, 2); 310 | return ((work[1] & 0xff) << 8 | (work[0] & 0xff)); 311 | } 312 | 313 | /** 314 | * Skip over bytes in the stream. See the general contract of the 315 | * skipBytes method of DataInput. 316 | *

317 | * Bytes for this operation are read from the contained input stream. 318 | * 319 | * @param n 320 | * the number of bytes to be skipped. 321 | * 322 | * @return the actual number of bytes skipped. 323 | * @throws IOException 324 | * if an I/O error occurs. 325 | */ 326 | @Override 327 | public final int skipBytes(int n) throws IOException { 328 | return dis.skipBytes(n); 329 | } 330 | } -------------------------------------------------------------------------------- /src/com/guye/baffle/util/LEDataOutputStream.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.util; 24 | 25 | import java.io.IOException; 26 | import java.io.OutputStream; 27 | 28 | public class LEDataOutputStream { 29 | 30 | OutputStream stream; 31 | 32 | public LEDataOutputStream(OutputStream out) { 33 | stream = out; 34 | } 35 | 36 | public void writeIntArray(int[] ints) throws IOException { 37 | for (int i = 0; i < ints.length; i++) { 38 | writeInt(ints[i]); 39 | } 40 | } 41 | 42 | public void write(int b) throws IOException { 43 | stream.write(b); 44 | } 45 | 46 | public void write(byte[] b) throws IOException { 47 | stream.write(b); 48 | } 49 | 50 | public void write(byte[] b, int off, int len) throws IOException { 51 | stream.write(b, off, len); 52 | } 53 | 54 | public void writeShort(int v) throws IOException { 55 | stream.write((v >>> 0) & 0xFF); 56 | stream.write((v >>> 8) & 0xFF); 57 | } 58 | 59 | public void writeInt(int v) throws IOException { 60 | stream.write((v >>> 0) & 0xFF); 61 | stream.write((v >>> 8) & 0xFF); 62 | stream.write((v >>> 16) & 0xFF); 63 | stream.write((v >>> 24) & 0xFF); 64 | } 65 | 66 | public void close() throws IOException { 67 | stream.close(); 68 | } 69 | 70 | public void flush() throws IOException { 71 | stream.flush(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/com/guye/baffle/util/OS.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 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.guye.baffle.util; 18 | 19 | import java.io.BufferedReader; 20 | import java.io.BufferedWriter; 21 | import java.io.File; 22 | import java.io.FileInputStream; 23 | import java.io.FileOutputStream; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.io.InputStreamReader; 27 | import java.io.OutputStream; 28 | import java.io.OutputStreamWriter; 29 | import java.util.Arrays; 30 | 31 | import org.apache.commons.io.IOUtils; 32 | 33 | import com.guye.baffle.exception.BaffleException; 34 | 35 | /** 36 | * @author Ryszard Wiśniewski 37 | */ 38 | public class OS { 39 | 40 | public static void rmdir(File dir) throws BaffleException { 41 | if (!dir.exists()) { 42 | return; 43 | } 44 | File[] files = dir.listFiles(); 45 | for (int i = 0; i < files.length; i++) { 46 | File file = files[i]; 47 | if (file.isDirectory()) { 48 | rmdir(file); 49 | } else { 50 | file.delete(); 51 | } 52 | } 53 | dir.delete(); 54 | } 55 | 56 | public static void rmfile(String file) throws BaffleException { 57 | File del = new File(file); 58 | del.delete(); 59 | } 60 | 61 | public static void rmdir(String dir) throws BaffleException { 62 | rmdir(new File(dir)); 63 | } 64 | 65 | public static void cpdir(File src, File dest) throws BaffleException { 66 | dest.mkdirs(); 67 | File[] files = src.listFiles(); 68 | for (int i = 0; i < files.length; i++) { 69 | File file = files[i]; 70 | File destFile = new File(dest.getPath() + File.separatorChar 71 | + file.getName()); 72 | if (file.isDirectory()) { 73 | cpdir(file, destFile); 74 | continue; 75 | } 76 | try { 77 | InputStream in = new FileInputStream(file); 78 | OutputStream out = new FileOutputStream(destFile); 79 | IOUtils.copy(in, out); 80 | in.close(); 81 | out.close(); 82 | } catch (IOException ex) { 83 | throw new BaffleException("Could not copy file: " + file, ex); 84 | } 85 | } 86 | } 87 | 88 | public static void cpdir(String src, String dest) throws BaffleException { 89 | cpdir(new File(src), new File(dest)); 90 | } 91 | 92 | public static void exec(String[] cmd) throws BaffleException { 93 | Process ps = null; 94 | try { 95 | ps = Runtime.getRuntime().exec(cmd); 96 | 97 | new StreamForwarder(ps.getInputStream(), System.err).start(); 98 | new StreamForwarder(ps.getErrorStream(), System.err).start(); 99 | if (ps.waitFor() != 0) { 100 | throw new BaffleException("could not exec command: " 101 | + Arrays.toString(cmd)); 102 | } 103 | } catch (IOException ex) { 104 | throw new BaffleException("could not exec command: " 105 | + Arrays.toString(cmd), ex); 106 | } catch (InterruptedException ex) { 107 | throw new BaffleException("could not exec command: " 108 | + Arrays.toString(cmd), ex); 109 | } 110 | } 111 | 112 | public static File createTempDirectory() throws BaffleException { 113 | try { 114 | File tmp = File.createTempFile("BRUT", null); 115 | if (!tmp.delete()) { 116 | throw new BaffleException("Could not delete tmp file: " 117 | + tmp.getAbsolutePath()); 118 | } 119 | if (!tmp.mkdir()) { 120 | throw new BaffleException("Could not create tmp dir: " 121 | + tmp.getAbsolutePath()); 122 | } 123 | return tmp; 124 | } catch (IOException ex) { 125 | throw new BaffleException("Could not create tmp dir", ex); 126 | } 127 | } 128 | 129 | static class StreamForwarder extends Thread { 130 | 131 | public StreamForwarder(InputStream in, OutputStream out) { 132 | mIn = in; 133 | mOut = out; 134 | } 135 | 136 | @Override 137 | public void run() { 138 | try { 139 | BufferedReader in = new BufferedReader(new InputStreamReader( 140 | mIn)); 141 | BufferedWriter out = new BufferedWriter(new OutputStreamWriter( 142 | mOut)); 143 | String line; 144 | while ((line = in.readLine()) != null) { 145 | out.write(line); 146 | out.newLine(); 147 | } 148 | out.flush(); 149 | } catch (IOException ex) { 150 | ex.printStackTrace(); 151 | } 152 | } 153 | 154 | private final InputStream mIn; 155 | private final OutputStream mOut; 156 | } 157 | public static void main( String[] args ) { 158 | byte[] buff = new byte[]{0x3f, (byte) 0xf3 ,(byte) 0xbb ,0x53 ,(byte) 0x93 ,0x25, 0x0b, 0x52}; 159 | 160 | long addr = buff[0] & 0xFF; 161 | addr = (addr << 8) | (buff[1] & 0xff) ; 162 | addr = (addr << 8) | (buff[2] & 0xff) ; 163 | addr = (addr << 8) | (buff[3] & 0xff) ; 164 | addr = (addr << 8) | (buff[4] & 0xff) ; 165 | addr = (addr << 8) | (buff[5] & 0xff) ; 166 | addr = (addr << 8) | (buff[6] & 0xff) ; 167 | addr = (addr << 8) | (buff[7] & 0xff) ; 168 | 169 | System.out.printf("%x" , addr); 170 | System.out.println(); 171 | System.out.println(Double.longBitsToDouble(addr)); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/com/guye/baffle/util/ZipInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Baffle Project 3 | * The MIT License (MIT) Copyright (Baffle) 2015 guye 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | * and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies 12 | * or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | * @author guye 21 | * 22 | **/ 23 | package com.guye.baffle.util; 24 | 25 | import java.util.logging.Level; 26 | import java.util.logging.Logger; 27 | 28 | import com.guye.baffle.obfuscate.ObfuscateHelper; 29 | import com.guye.baffle.obfuscate.Obfuscater; 30 | 31 | public class ZipInfo { 32 | public String type; 33 | public String name; 34 | private String orginName; 35 | public long size; 36 | public int zipMethod; 37 | public long crc; 38 | private String postfix; 39 | private String digest; 40 | 41 | public ZipInfo(String name, int method, long l, long crc,String digest) { 42 | zipMethod = method; 43 | this.size = l; 44 | this.crc = crc; 45 | this.setDigest(digest); 46 | setOrginName(name); 47 | if (name.startsWith(ObfuscateHelper.RES_PROFIX)) { 48 | String[] names = name.split("/"); 49 | if (names == null || names.length != 3) { 50 | Logger.getLogger(Obfuscater.LOG_NAME).log(Level.CONFIG, 51 | "ZipInfo:names->" + name); 52 | throw new RuntimeException(); // TODO 53 | } 54 | 55 | int index = names[2].indexOf('.'); 56 | String postfix = ""; 57 | if (index > 0) { 58 | postfix = names[2].substring(index, names[2].length()); 59 | names[2] = names[2].substring(0, index); 60 | } 61 | 62 | this.type = names[1]; 63 | this.name = names[2]; 64 | this.postfix = postfix; 65 | } else { 66 | this.name = name; 67 | } 68 | } 69 | 70 | public String getKey(String resKey) { 71 | if (type != null) { 72 | StringBuilder builder = new StringBuilder(resKey).append("/") 73 | .append(type).append("/").append(name).append(postfix); 74 | return builder.toString(); 75 | } else { 76 | return name; 77 | } 78 | } 79 | 80 | public String getDigest() { 81 | return digest; 82 | } 83 | 84 | public void setDigest( String digest ) { 85 | this.digest = digest; 86 | } 87 | 88 | public String getOrginName() { 89 | return orginName; 90 | } 91 | 92 | public void setOrginName( String orginName ) { 93 | this.orginName = orginName; 94 | } 95 | 96 | @Override 97 | public int hashCode() { 98 | final int prime = 31; 99 | int result = 1; 100 | result = prime * result + ((orginName == null) ? 0 : orginName.hashCode()); 101 | return result; 102 | } 103 | 104 | @Override 105 | public boolean equals(Object obj) { 106 | if (this == obj) 107 | return true; 108 | if (obj == null) 109 | return false; 110 | if (getClass() != obj.getClass()) 111 | return false; 112 | ZipInfo other = (ZipInfo) obj; 113 | if (orginName == null) { 114 | if (other.orginName != null) 115 | return false; 116 | } else if (!orginName.equals(other.orginName)) 117 | return false; 118 | return true; 119 | } 120 | 121 | 122 | } -------------------------------------------------------------------------------- /src/com/guye/baffle/webp/WebpIO.java: -------------------------------------------------------------------------------- 1 | package com.guye.baffle.webp; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.File; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.util.Iterator; 8 | 9 | import javax.imageio.IIOImage; 10 | import javax.imageio.ImageIO; 11 | import javax.imageio.ImageTypeSpecifier; 12 | import javax.imageio.ImageWriter; 13 | import javax.imageio.stream.FileImageOutputStream; 14 | 15 | import com.luciad.imageio.webp.WebPWriteParam; 16 | 17 | public class WebpIO { 18 | public static boolean toWebp(File in , File out, int minilevel) { 19 | if(minilevel < 14){ 20 | return false; 21 | } 22 | long orgSize = in.length(); 23 | BufferedImage image; 24 | try{ 25 | image = ImageIO.read(in); 26 | }catch(Exception e){ 27 | e.printStackTrace(); 28 | return false; 29 | } 30 | 31 | if(image == null){ 32 | return false; 33 | } 34 | 35 | if(minilevel < 17){ 36 | if(image.isAlphaPremultiplied()){ 37 | return false; 38 | } 39 | } 40 | ImageWriter w = null; 41 | ImageTypeSpecifier type = 42 | ImageTypeSpecifier.createFromRenderedImage(image); 43 | Iterator iter = ImageIO.getImageWriters(type, "webp"); 44 | 45 | if (iter.hasNext()) { 46 | w = iter.next(); 47 | } else { 48 | w= null; 49 | } 50 | if(w == null){ 51 | return false; 52 | } 53 | // Obtain a WebP ImageWriter instance 54 | ImageWriter writer = ImageIO.getImageWritersByMIMEType("image/webp").next(); 55 | 56 | // Configure encoding parameters 57 | WebPWriteParam writeParam = new WebPWriteParam(writer.getLocale()); 58 | writeParam.setCompressionMode(WebPWriteParam.LOSSY_COMPRESSION); 59 | 60 | 61 | 62 | // Configure the output on the ImageWriter 63 | try { 64 | writer.setOutput(new FileImageOutputStream(out)); 65 | // Encode 66 | writer.write(null, new IIOImage(image, null, null), writeParam); 67 | } catch (FileNotFoundException e) { 68 | 69 | e.printStackTrace(); 70 | return false; 71 | } catch (IOException e) { 72 | 73 | e.printStackTrace(); 74 | return false; 75 | } 76 | 77 | 78 | 79 | 80 | long newSize = out.length(); 81 | if(orgSize > newSize){ 82 | in.delete(); 83 | return true; 84 | }else{ 85 | out.delete(); 86 | return false; 87 | } 88 | 89 | 90 | } 91 | 92 | private static String getWebpName(String name) { 93 | name = name.substring(0,name.indexOf('.')); 94 | return name+".webp"; 95 | } 96 | } 97 | --------------------------------------------------------------------------------