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.meta.MetaInfo; 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.apktool.Main; import brut.directory.Directory; import brut.directory.DirectoryException; import brut.directory.FileDirectory; import brut.util.OS; import com.rover12421.shaka.lib.AndroidZip; import com.rover12421.shaka.lib.LogHelper; import com.rover12421.shaka.lib.ShakaIO; import com.rover12421.shaka.lib.ShakaProperties; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.archivers.zip.ZipFile; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import java.io.*; import java.nio.file.*; import java.util.Collection; import java.util.Enumeration; import java.util.Map; import java.util.Set; import java.util.zip.CRC32; /** * Created by rover12421 on 8/9/14. */ @Aspect public class AndrolibAj { public static MetaInfo metaInfo = new MetaInfo(); public static final String UNK_DIRNAME = "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, MetaInfo meta) { Androlib thiz = (Androlib) joinPoint.getThis(); Map<String, String> files = meta.unknownFiles; if (files == null) { ResUnknownFiles mResUnknownFiles = thiz.getResUnknownFiles(); files = mResUnknownFiles.getUnknownFiles(); meta.unknownFiles = files; } if (AndrolibResourcesAj.doNotCompress != null) { for (String file : AndrolibResourcesAj.doNotCompress) { String pFile = file.replace("\\", "/"); pFile = getDecodeFileMapName(pFile); files.put(pFile, String.valueOf(ZipArchiveEntry.STORED)); } } try { File unknownFile = new File(appDir, UNK_DIRNAME); if (!unknownFile.exists()) { return; } try { Directory directory = new FileDirectory(unknownFile); Set<String> addFiles = directory.getFiles(true); for (String file : addFiles) { //.??.?,?? file = file.replace("\\", "/"); 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, bakdeb, api)") public void decodeSourcesSmali_around(ProceedingJoinPoint joinPoint, File apkFile, File outDir, String filename, 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, bakdeb, api); //? if (mapDirName != null) { metaInfo.addDexMap(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() && metaInfo.getDexMap(name) == null && name.startsWith("smali_")) { String key = name.substring("smali_".length()) + ".dex"; metaInfo.addDexMap(key, name); } } } if (metaInfo.dexMaps == null) return; for (String dex : metaInfo.dexMaps.keySet()) { try { Androlib androlib = (Androlib) joinPoint.getThis(); File dexFile = new File(appDir, APK_DIRNAME + "/" + dex); dexFile.getParentFile().mkdirs(); androlib.buildSourcesSmali(appDir, metaInfo.getDexMap(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, MetaInfo meta) throws Throwable { if (meta.unknownFiles != null) { LogHelper.info("Copying unknown files/dir..."); Map<String, String> files = meta.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"); } ZipFile inputFile = new ZipFile(tempFile); try (ZipArchiveOutputStream actualOutput = new ZipArchiveOutputStream(outFile)) { copyExistingFiles(inputFile, actualOutput, files); copyUnknownFiles(appDir, actualOutput, files); } catch (IOException ex) { throw new AndrolibException(ex); } finally { inputFile.close(); // Remove our temporary file. tempFile.delete(); } } } private void copyExistingFiles(ZipFile inputFile, ZipArchiveOutputStream outputFile, Map<String, String> excludeFiles) throws IOException { // First, copy the contents from the existing outFile: Enumeration<? extends ZipArchiveEntry> entries = inputFile.getEntries(); while (entries.hasMoreElements()) { ZipArchiveEntry oldEntry = entries.nextElement(); ZipArchiveEntry entry = new ZipArchiveEntry(oldEntry); if (excludeFiles.get(entry.getName()) != null) { //??. continue; } // We can't reuse the compressed size because it depends on compression sizes. entry.setCompressedSize(-1); outputFile.putArchiveEntry(entry); // No need to create directory entries in the final apk if (!entry.isDirectory()) { ShakaIO.copy(inputFile, outputFile, oldEntry); } outputFile.closeArchiveEntry(); } } private void copyUnknownFiles(File appDir, ZipArchiveOutputStream outputFile, Map<String, String> files) throws IOException { File unknownFileDir = new File(appDir, UNK_DIRNAME); File buildDir = new File(appDir, "build/apk"); // loop through unknown files for (Map.Entry<String, String> unknownFileInfo : files.entrySet()) { String file = unknownFileInfo.getKey(); file = getDecodeFileMapName(file); File inputFile = new File(unknownFileDir, file); if (inputFile.isDirectory()) { continue; } if (!inputFile.exists()) { inputFile = new File(buildDir, file); if (!inputFile.exists()) { inputFile = new File(appDir, file); // if (inputFile.exists()) { // LogHelper.info("Find unkown file in app dir : " + file); // } // } else { // LogHelper.info("Find unkown file in build dir : " + file); } } if (!inputFile.exists()) { LogHelper.info("Not found unkown file : " + file); continue; } ZipArchiveEntry newEntry = new ZipArchiveEntry(unknownFileInfo.getKey()); int method = Integer.valueOf(unknownFileInfo.getValue()); LogHelper.fine( String.format("Copying unknown file %s with method %d", unknownFileInfo.getKey(), method)); if (method == ZipArchiveEntry.STORED) { newEntry.setMethod(ZipArchiveEntry.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(ZipArchiveEntry.DEFLATED); } outputFile.putArchiveEntry(newEntry); ShakaIO.copy(inputFile, outputFile); outputFile.closeArchiveEntry(); } } 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 { Androlib thiz = (Androlib) joinPoint.getThis(); File unknownOut = new File(outDir, UNK_DIRNAME); ResUnknownFiles mResUnknownFiles = thiz.getResUnknownFiles(); 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|DSA)", "").isEmpty()) { continue; } /** * dex */ if (metaInfo.getDexMap(file) != null) { 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, MetaInfo meta) { meta.dexMaps = metaInfo.dexMaps; meta.decodeFileMaps = metaInfo.decodeFileMaps; meta.shakaVer = ShakaProperties.getVersion(); } // @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); // } // } @Around("execution(* brut.androlib.Androlib.readMetaFile(..))") public MetaInfo readMetaFile(ProceedingJoinPoint joinPoint) throws Throwable { metaInfo = (MetaInfo) joinPoint.proceed(joinPoint.getArgs()); return metaInfo; } public String getDecodeFileMapName(String name) { String mapName = metaInfo.getDecodeFileMap(name); if (mapName == null) { mapName = name; } return mapName; } /** * ,??? * @param apkFile * @param uncompressedFiles * @throws AndrolibException */ @Around("execution(* brut.androlib.Androlib.recordUncompressedFiles(..))" + "&& args(apkFile, uncompressedFiles)") public void recordUncompressedFiles(ExtFile apkFile, Collection<String> uncompressedFiles) throws AndrolibException { try { Directory unk = apkFile.getDirectory(); Set<String> files = unk.getFiles(true); for (String file : files) { if (unk.getCompressionLevel(file) == ZipArchiveEntry.STORED //? && AndroidZip.needCompress(file) // Android ? ) { //???? uncompressedFiles.add(file); } } } catch (DirectoryException ex) { throw new AndrolibException(ex); } } @After("execution(* brut.androlib.Androlib.decodeResourcesFull(..))" + "&& args(apkFile, outDir, resTable)") public void decodeResourcesFull(JoinPoint joinPoint, ExtFile apkFile, File outDir, ResTable resTable) { if (ResResSpecAj.RenameResResSpec) { //??????,??? AndroidManifest.xml, ?? `decodeManifestWithResources`????. Androlib thiz = (Androlib) joinPoint.getThis(); try { thiz.decodeManifestWithResources(apkFile, outDir, resTable); } catch (AndrolibException e) { e.printStackTrace(); } } } }