Java tutorial
/** * Copyright 2015 Rover12421 <rover12421@163.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Copyright 2015 Rover12421 <rover12421@163.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.rover12421.shaka.apktool.lib; import brut.androlib.Androlib; import brut.androlib.AndrolibException; import brut.androlib.res.data.ResTable; import brut.androlib.res.data.ResUnknownFiles; import brut.androlib.res.util.ExtFile; import brut.androlib.src.SmaliDecoder; import brut.directory.Directory; import brut.directory.DirectoryException; import brut.directory.FileDirectory; import brut.util.BrutIO; import brut.util.OS; import com.rover12421.shaka.lib.ShakaProperties; import com.rover12421.shaka.lib.AndroidZip; import com.rover12421.shaka.lib.LogHelper; import com.rover12421.shaka.lib.reflect.Reflect; import org.apache.commons.io.IOUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import java.io.*; import java.nio.file.*; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.zip.*; /** * Created by rover12421 on 8/9/14. */ @Aspect public class AndrolibAj { public String getUNK_DIRNAME() { // return (String) ReflectUtil.getFieldValue(Androlib.class, "UNK_DIRNAME"); return "unknown"; } private final static String SMALI_DIRNAME = "smali"; private final static String APK_DIRNAME = "build/apk"; /** * ?,?,???"unknown" */ @Before("execution(void brut.androlib.Androlib.buildUnknownFiles(..))" + "&& args(appDir, outFile, meta)") public void buildUnknownFiles_before(JoinPoint joinPoint, File appDir, File outFile, Map<String, Object> meta) { try { String UNK_DIRNAME = getUNK_DIRNAME(); File unknownFile = new File(appDir, UNK_DIRNAME); if (!unknownFile.exists()) { return; } try { Directory directory = new FileDirectory(unknownFile); Set<String> addFiles = directory.getFiles(true); Map<String, String> files = (Map<String, String>) meta.get("unknownFiles"); if (files == null) { ResUnknownFiles mResUnknownFiles = Reflect.on(joinPoint.getThis()).get("mResUnknownFiles"); files = mResUnknownFiles.getUnknownFiles(); meta.put("unknownFiles", files); } for (String file : addFiles) { //.??.?,?? if (!files.containsKey(file)) { files.put(file, AndroidZip.getZipMethod(new File(appDir, file).getAbsolutePath()) + ""); } } } catch (DirectoryException e) { e.printStackTrace(); } } catch (Throwable e) { e.printStackTrace(); } } /** * public void build(ExtFile appDir, File outFile) */ @Before("execution(void brut.androlib.Androlib.build(brut.androlib.res.util.ExtFile, java.io.File))") public void build_before() { LogHelper.info("Using ShakaApktool " + ShakaProperties.getVersion()); } /** * <B>com.carrot.carrotfantasy.apk</B> Exception in thread "main" org.jf.util.ExceptionWithContext: The assets/cfg.dex file in com.carrot.carrotfantasy.apk is too small to be a valid dex file at org.jf.dexlib2.DexFileFactory.loadDexFile(DexFileFactory.java:77) at org.jf.dexlib2.DexFileFactory.loadDexFile(DexFileFactory.java:59) at brut.androlib.src.SmaliDecoder.decode(SmaliDecoder.java:94) at brut.androlib.src.SmaliDecoder.decode(SmaliDecoder.java:46) at brut.androlib.Androlib.decodeSourcesSmali(Androlib.java:83) at brut.androlib.ApkDecoder.decode(ApkDecoder.java:146) at brut.apktool.Main.cmdDecode(Main.java:170) at brut.apktool.Main.main(Main.java:86) */ @Around("execution(* brut.androlib.Androlib.decodeSourcesSmali(..))" + "&& args(apkFile, outDir, filename, debug, debugLinePrefix, bakdeb, api)") public void decodeSourcesSmali_around(ProceedingJoinPoint joinPoint, File apkFile, File outDir, String filename, boolean debug, String debugLinePrefix, boolean bakdeb, int api) throws Throwable { File smaliDir = null; String mapDirName = null; try { if (filename.equalsIgnoreCase("classes.dex")) { smaliDir = new File(outDir, SMALI_DIRNAME); } else { mapDirName = SMALI_DIRNAME + "_" + filename.replaceAll("\\\\|/", "_"); //.dex? mapDirName = mapDirName.substring(0, mapDirName.length() - 4); smaliDir = new File(outDir, mapDirName); } OS.rmdir(smaliDir); smaliDir.mkdirs(); LogHelper.info("Baksmaling " + filename + "..."); SmaliDecoder.decode(apkFile, smaliDir, filename, debug, debugLinePrefix, bakdeb, api); //? if (mapDirName != null) { DexMaps.put(filename, mapDirName); } } catch (Exception ex) { //?????classes.dex,?? if (!"classes.dex".equals(filename)) { LogHelper.warning("decodeSourcesSmali " + filename + " error!"); if (smaliDir != null) { OS.rmdir(smaliDir); } } else { throw ex; } } } @Around("execution(* brut.androlib.Androlib.buildNonDefaultSources(..))" + "&& args(appDir)") public void buildNonDefaultSources(ProceedingJoinPoint joinPoint, ExtFile appDir) { /** * `apktool.yml` * ?5.0, `gradle test` ? * `apktool.yml` */ File[] files = appDir.getAbsoluteFile().listFiles(); if (files != null) { for (File file : files) { String name = file.getName(); if (file.isDirectory() && !DexMaps.containsKey(name) && name.startsWith("smali_")) { String key = name.substring("smali_".length()) + ".dex"; DexMaps.put(key, name); } } } for (String dex : DexMaps.keySet()) { try { Androlib androlib = (Androlib) joinPoint.getThis(); File dexFile = new File(appDir, APK_DIRNAME + "/" + dex); dexFile.getParentFile().mkdirs(); androlib.buildSourcesSmali(appDir, DexMaps.get(dex), dex); } catch (Exception e) { e.printStackTrace(); } } } @Around("execution(* brut.androlib.Androlib.decodeSourcesRaw(..))" + "&& args(apkFile, outDir, filename)") public void decodeSourcesRaw(ProceedingJoinPoint joinPoint, ExtFile apkFile, File outDir, String filename) throws Throwable { /** * ??dex,?classes.dex?copy,unkownfiles??? */ if (filename.equals("classes.dex")) { joinPoint.proceed(joinPoint.getArgs()); } } @Around("execution(* brut.androlib.Androlib.buildUnknownFiles(..))" + "&& args(appDir, outFile, meta)") public void buildUnknownFiles_around(File appDir, File outFile, Map<String, Object> meta) throws Throwable { if (meta.containsKey("unknownFiles")) { LogHelper.info("Copying unknown files/dir..."); Map<String, String> files = (Map<String, String>) meta.get("unknownFiles"); File tempFile = File.createTempFile("buildUnknownFiles", "tmp", outFile.getParentFile()); tempFile.delete(); boolean renamed = outFile.renameTo(tempFile); if (!renamed) { throw new AndrolibException("Unable to rename temporary file"); } try (ZipFile inputFile = new ZipFile(tempFile); FileOutputStream fos = new FileOutputStream(outFile); ZipOutputStream actualOutput = new ZipOutputStream(fos)) { copyExistingFiles(inputFile, actualOutput, files); copyUnknownFiles(appDir, actualOutput, files); } catch (IOException ex) { throw new AndrolibException(ex); } finally { // Remove our temporary file. tempFile.delete(); } } } private void copyExistingFiles(ZipFile inputFile, ZipOutputStream outputFile, Map<String, String> excludeFiles) throws IOException { // First, copy the contents from the existing outFile: Enumeration<? extends ZipEntry> entries = inputFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = new ZipEntry(entries.nextElement()); if (excludeFiles.get(entry.getName()) != null) { //??. continue; } // We can't reuse the compressed size because it depends on compression sizes. entry.setCompressedSize(-1); outputFile.putNextEntry(entry); // No need to create directory entries in the final apk if (!entry.isDirectory()) { BrutIO.copy(inputFile, outputFile, entry); } outputFile.closeEntry(); } } private void copyUnknownFiles(File appDir, ZipOutputStream outputFile, Map<String, String> files) throws IOException { String UNK_DIRNAME = getUNK_DIRNAME(); File unknownFileDir = new File(appDir, UNK_DIRNAME); // loop through unknown files for (Map.Entry<String, String> unknownFileInfo : files.entrySet()) { File inputFile = new File(unknownFileDir, unknownFileInfo.getKey()); if (inputFile.isDirectory()) { continue; } ZipEntry newEntry = new ZipEntry(unknownFileInfo.getKey()); int method = Integer.valueOf(unknownFileInfo.getValue()); LogHelper.fine( String.format("Copying unknown file %s with method %d", unknownFileInfo.getKey(), method)); if (method == ZipEntry.STORED) { newEntry.setMethod(ZipEntry.STORED); newEntry.setSize(inputFile.length()); newEntry.setCompressedSize(-1); BufferedInputStream unknownFile = new BufferedInputStream(new FileInputStream(inputFile)); CRC32 crc = calculateCrc(unknownFile); newEntry.setCrc(crc.getValue()); // LogHelper.getLogger().fine("\tsize: " + newEntry.getSize()); } else { newEntry.setMethod(ZipEntry.DEFLATED); } outputFile.putNextEntry(newEntry); BrutIO.copy(inputFile, outputFile); outputFile.closeEntry(); } } public static CRC32 calculateCrc(InputStream input) throws IOException { CRC32 crc = new CRC32(); int bytesRead; byte[] buffer = new byte[4096]; while ((bytesRead = input.read(buffer)) != -1) { crc.update(buffer, 0, bytesRead); } return crc; } /** * @param joinPoint * @param apkFile * @param outDir * @param resTable */ @After("execution(* brut.androlib.Androlib.decodeUnknownFiles(..))" + "&& args(apkFile, outDir, resTable)") public void decodeUnknownFiles_after(JoinPoint joinPoint, ExtFile apkFile, File outDir, ResTable resTable) { try { File unknownOut = new File(outDir, getUNK_DIRNAME()); ResUnknownFiles mResUnknownFiles = Reflect.on(joinPoint.getThis()).get("mResUnknownFiles"); Directory unk = apkFile.getDirectory(); // loop all items in container recursively, ignoring any that are pre-defined by aapt Set<String> files = unk.getFiles(true); for (String file : files) { if (file.equals("classes.dex") || file.equals("resources.arsc")) { continue; } /** * ?? */ if (file.replaceFirst("META-INF[/\\\\]+[^/\\\\]+\\.(SF|RSA)", "").isEmpty()) { continue; } /** * dex */ if (DexMaps.containsKey(file)) { continue; } String decodeMapFileName = getDecodeFileMapName(file); File resFile = new File(outDir, decodeMapFileName); if (resFile.exists()) { //??, mResUnknownFiles.getUnknownFiles().remove(file); File needDeleteFile = new File(unknownOut, file); if (needDeleteFile.exists()) { //?,? needDeleteFile.delete(); } } else { File unFile = new File(unknownOut, file); if (unFile.exists()) { //? continue; } //?, // copy file out of archive into special "unknown" folder unk.copyToDir(unknownOut, file); // lets record the name of the file, and its compression type // so that we may re-include it the same way mResUnknownFiles.addUnknownFileInfo(file, String.valueOf(unk.getCompressionLevel(file))); } } if (unknownOut.exists()) { // Files.walkFileTree(Paths.get(unknownOut.getAbsolutePath()), new SimpleFileVisitor<Path>() { @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { try { Files.deleteIfExists(dir); } catch (Exception e) { // ignore exception } return FileVisitResult.CONTINUE; } }); } } catch (Exception e) { e.printStackTrace(); } } public static final String DecodeFileMapsMetaName = "DecodeFileMaps"; public static Map<String, String> DecodeFileMaps = new LinkedHashMap<>(); public static final String DexMapsMetaName = "DexMaps"; public static Map<String, String> DexMaps = new LinkedHashMap<>(); @Before("execution(* brut.androlib.Androlib.writeMetaFile(..))" + "&& args(mOutDir, meta)") public void writeMetaFile(File mOutDir, Map<String, Object> meta) { meta.put(DecodeFileMapsMetaName, DecodeFileMaps); meta.put(DexMapsMetaName, DexMaps); } @AfterReturning(pointcut = "execution(* brut.androlib.Androlib.readMetaFile(..))", returning = "meta") public void readMetaFile(Map<String, Object> meta) { if (meta.get(DecodeFileMapsMetaName) != null) { DecodeFileMaps = (Map<String, String>) meta.get(DecodeFileMapsMetaName); } if (meta.get(DexMapsMetaName) != null) { DexMaps = (Map<String, String>) meta.get(DexMapsMetaName); } } public String getDecodeFileMapName(String name) { String mapName = DecodeFileMaps.get(name); if (mapName == null) { mapName = name; } return mapName; } }