com.rover12421.shaka.apktool.lib.AndrolibAj.java Source code

Java tutorial

Introduction

Here is the source code for com.rover12421.shaka.apktool.lib.AndrolibAj.java

Source

/**
 *  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;
    }
}