package com.njlabs.showjava.processor;
import android.util.Log;
import com.crashlytics.android.Crashlytics;
import com.googlecode.dex2jar.Method;
import com.googlecode.dex2jar.ir.IrMethod;
import com.googlecode.dex2jar.reader.DexFileReader;
import com.googlecode.dex2jar.v3.Dex2jar;
import com.googlecode.dex2jar.v3.DexExceptionHandler;
import com.njlabs.showjava.utils.StringUtils;
import com.njlabs.showjava.utils.logging.Ln;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.immutable.ImmutableDexFile;
import org.objectweb.asm.tree.MethodNode;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Niranjan on 29-05-2015.
*/
@SuppressWarnings({"ResultOfMethodCallIgnored", "ConstantConditions"})
public class JarExtractor extends ProcessServiceHelper {
private final ArrayList<String> ignoredLibs;
public JarExtractor(ProcessService processService) {
this.processService = processService;
this.UIHandler = processService.UIHandler;
this.packageFilePath = processService.packageFilePath;
this.packageName = processService.packageName;
this.exceptionHandler = processService.exceptionHandler;
this.sourceOutputDir = processService.sourceOutputDir;
this.javaSourceOutputDir = processService.javaSourceOutputDir;
ignoredLibs = new ArrayList<>();
//////
printStream = new PrintStream(new ProgressStream());
System.setErr(printStream);
System.setOut(printStream);
//////
}
public void extract() {
ThreadGroup group = new ThreadGroup("DEX TO JAR EXTRACTION");
broadcastStatus("optimise_dex_start");
Runnable runProcess = new Runnable() {
@Override
public void run() {
loadIgnoredLibs();
apkToDex();
if(!processService.decompilerToUse.equals("jadx")){
dexToJar();
}
startJavaExtractor();
}
};
Thread extractionThread = new Thread(group, runProcess, "DEX TO JAR EXTRACTION", processService.STACK_SIZE);
extractionThread.setPriority(Thread.MAX_PRIORITY);
extractionThread.setUncaughtExceptionHandler(exceptionHandler);
extractionThread.start();
}
private void apkToDex() {
DexFile dexFile = null;
try {
dexFile = DexFileFactory.loadDexFile(packageFilePath, 19);
} catch (Exception e) {
broadcastStatus("exit");
UIHandler.post(new ToastRunnable("The app you selected cannot be decompiled. Please select another app."));
}
List<ClassDef> classes = new ArrayList<>();
broadcastStatus("optimising", "");
for (ClassDef classDef : dexFile.getClasses()) {
if (!isIgnored(classDef.getType())) {
final String CurrentClass = classDef.getType();
broadcastStatus("optimising_class", CurrentClass.replaceAll("Processing ", ""));
classes.add(classDef);
}
}
broadcastStatus("optimise_dex_finish");
File PerAppWorkingDirectory = new File(processService.sourceOutputDir);
PerAppWorkingDirectory.mkdirs();
Log.d("DEBUGGER", "Prepare Writing");
broadcastStatus("merging_classes");
dexFile = new ImmutableDexFile(classes);
try {
Log.d("DEBUGGER", "Start Writing");
DexFileFactory.writeDexFile(PerAppWorkingDirectory + "/optimised_classes.dex", dexFile);
Log.d("DEBUGGER", "Writing done!");
} catch (Exception e) {
broadcastStatus("exit");
UIHandler.post(new ToastRunnable("The app you selected cannot be decompiled. Please select another app."));
}
}
private void dexToJar() {
Log.i("STATUS", "Jar Extraction Started");
broadcastStatus("dex2jar");
// DEX 2 JAR CONFIGS
boolean reuseReg = false; // reuse register while generate java .class file
boolean topologicalSort1 = false; // same with --topological-sort/-ts
boolean topologicalSort = false; // sort block by topological, that will generate more readable code
boolean verbose = true; // show progress
boolean debugInfo = false; // translate debug info
boolean printIR = false; // print ir to System.out
boolean optimizeSynchronized = true; // Optimise-synchronised
File PerAppWorkingDirectory = new File(sourceOutputDir);
File file = new File(PerAppWorkingDirectory + "/" + packageName + ".jar");
File dexFile = new File(PerAppWorkingDirectory + "/optimised_classes.dex");
if (dexFile.exists() && dexFile.isFile()) {
DexExceptionHandlerMod dexExceptionHandlerMod = new DexExceptionHandlerMod();
try {
DexFileReader reader = new DexFileReader(dexFile);
Dex2jar dex2jar = Dex2jar.from(reader).reUseReg(reuseReg).topoLogicalSort(topologicalSort || topologicalSort1).skipDebug(!debugInfo)
.optimizeSynchronized(optimizeSynchronized).printIR(printIR).verbose(verbose);
dex2jar.setExceptionHandler(dexExceptionHandlerMod);
dex2jar.to(file);
} catch (Exception e) {
broadcastStatus("exit_process_on_error");
}
Log.i("STATUS", "Clearing cache");
File ClassDex = new File(PerAppWorkingDirectory + "/optimised_classes.dex");
ClassDex.delete();
}
}
private void startJavaExtractor() {
JavaExtractor javaExtractor = new JavaExtractor(processService);
javaExtractor.extract();
}
private void loadIgnoredLibs() {
String ignoredList = (processService.IGNORE_LIBS ? "ignored.list":"ignored_basic.list");
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(processService.getAssets().open(ignoredList)));
String mLine = reader.readLine().trim();
while (mLine != null) {
mLine = mLine.trim();
if (mLine.length() != 0) {
ignoredLibs.add(StringUtils.toClassName(mLine));
}
mLine = reader.readLine();
}
} catch (IOException e) {
Crashlytics.logException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
Crashlytics.logException(e);
}
}
}
}
private boolean isIgnored(String className) {
for (String ignoredClass : ignoredLibs) {
if (className.startsWith(ignoredClass)) {
return true;
}
}
return false;
}
private class DexExceptionHandlerMod implements DexExceptionHandler {
@Override
public void handleFileException(Exception e) {
Ln.d("Dex2Jar Exception " + e);
}
@Override
public void handleMethodTranslateException(Method method, IrMethod irMethod, MethodNode methodNode, Exception e) {
Ln.d("Dex2Jar Exception " + e);
}
}
}