/*
* Copyright 2013 ThinkFree
*
* 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 org.ruboto;
import android.util.Log;
import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipFile;
/**
* Collection of dirty codes. don't tell android team this mess.
* @author Alan Goo
*/
public class FrameworkHack {
private static final String TAG = "FrameworkHack";
public static boolean debug = false;
private FrameworkHack() {
// do not create an instance
}
/**
* dalvik do not have security manager
*/
private static void forceSet(Object obj, Field f, Object val) throws IllegalAccessException {
f.setAccessible(true);
f.set(obj, val);
}
private static Object forceGetFirst(Object obj, Field fArray) throws IllegalAccessException {
fArray.setAccessible(true);
Object[] vArray = (Object[]) fArray.get(obj);
return vArray[0];
}
private static String joinPaths(String[] paths) {
if (paths == null) {
return "";
}
StringBuilder buf = new StringBuilder();
for (int i = 0; i < paths.length; i++) {
buf.append(paths[i]);
buf.append(':');
}
return buf.toString();
}
// https://android.googlesource.com/platform/dalvik/+/android-1.6_r1/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
public static void appendDexListImplUnderICS(String[] jarPathsToAppend, PathClassLoader pcl, File optDir)
throws Exception {
int oldSize = 1; // gonna assume the original path had single entry for simplicity
Class pclClass = pcl.getClass();
Field fPath = pclClass.getDeclaredField("path");
fPath.setAccessible(true);
String orgPath = fPath.get(pcl).toString();
String pathToAdd = joinPaths(jarPathsToAppend);
String path = orgPath + ':' + pathToAdd;
forceSet(pcl, fPath, path);
boolean wantDex = System.getProperty("android.vm.dexfile", "").equals("true");
File[] files = new File[oldSize + jarPathsToAppend.length];
ZipFile[] zips = new ZipFile[oldSize + jarPathsToAppend.length];
DexFile[] dexs = new DexFile[oldSize + jarPathsToAppend.length];
Field fmPaths = pclClass.getDeclaredField("mPaths");
String[] newMPaths = new String[oldSize + jarPathsToAppend.length];
// set originals
newMPaths[0] = (String) forceGetFirst(pcl, fmPaths);
forceSet(pcl, fmPaths, newMPaths);
Field fmFiles = pclClass.getDeclaredField("mFiles");
files[0] = (File) forceGetFirst(pcl, fmFiles);
Field fmZips = pclClass.getDeclaredField("mZips");
zips[0] = (ZipFile) forceGetFirst(pcl, fmZips);
Field fmDexs = pclClass.getDeclaredField("mDexs");
dexs[0] = (DexFile) forceGetFirst(pcl, fmDexs);
for (int i = 0; i < jarPathsToAppend.length; i++) {
newMPaths[oldSize + i] = jarPathsToAppend[i];
File pathFile = new File(jarPathsToAppend[i]);
files[oldSize + i] = pathFile;
zips[oldSize + i] = new ZipFile(pathFile);
if (wantDex) {
String outDexName = pathFile.getName() + ".dex";
File outFile = new File(optDir, outDexName);
dexs[oldSize + i] = DexFile.loadDex(pathFile.getAbsolutePath(), outFile.getAbsolutePath(), 0);
}
}
forceSet(pcl, fmFiles, files);
forceSet(pcl, fmZips, zips);
forceSet(pcl, fmDexs, dexs);
}
// https://android.googlesource.com/platform/libcore/+/master/libdvm/src/main/java/dalvik/system/BaseDexClassLoader.java
// https://android.googlesource.com/platform/libcore/+/master/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public static void appendDexListImplICS(ArrayList<File> jarFiles, PathClassLoader pcl, File optDir,
boolean kitkatPlus, boolean marshmallowPlus) throws Exception {
if(debug) {
Log.d(TAG, "appendDexListImplICS(" + jarFiles);
}
// to save original values
Class bdclClass = Class.forName("dalvik.system.BaseDexClassLoader");
// ICS+ - pathList
Field fPathList = bdclClass.getDeclaredField("pathList");
fPathList.setAccessible(true);
Object dplObj = fPathList.get(pcl);
// to call DexPathList.makeDexElements() for additional jar(apk)s
Class dplClass = dplObj.getClass();
Field fDexElements = dplClass.getDeclaredField("dexElements");
fDexElements.setAccessible(true);
Object objOrgDexElements = fDexElements.get(dplObj);
int orgDexCount = Array.getLength(objOrgDexElements);
if(debug) {
Log.d(TAG, "orgDexCount : " + orgDexCount);
debugDexElements(objOrgDexElements);
}
Class clazzElement = Class.forName("dalvik.system.DexPathList$Element");
// create new merged array
int jarCount = jarFiles.size();
Object newDexElemArray = Array.newInstance(clazzElement, orgDexCount + jarCount);
System.arraycopy(objOrgDexElements, 0, newDexElemArray, 0, orgDexCount);
final Method mMakeDexElements;
if (marshmallowPlus) {
mMakeDexElements =
dplClass.getDeclaredMethod("makePathElements", List.class, File.class, List.class);
} else if (kitkatPlus) {
mMakeDexElements =
dplClass.getDeclaredMethod("makeDexElements", ArrayList.class, File.class, ArrayList.class);
} else {
mMakeDexElements = dplClass.getDeclaredMethod("makeDexElements", ArrayList.class, File.class);
}
mMakeDexElements.setAccessible(true);
Object elemsToAdd;
if (kitkatPlus) {
elemsToAdd = mMakeDexElements.invoke(null, jarFiles, optDir, new ArrayList());
} else {
elemsToAdd = mMakeDexElements.invoke(null, jarFiles, optDir);
}
for (int i = 0; i < jarCount; i++) {
int pos = orgDexCount + i;
Object elemToAdd = Array.get(elemsToAdd, i);
Array.set(newDexElemArray, pos, elemToAdd);
}
if(debug) {
Log.d(TAG, "appendDexListImplICS() " + Arrays.deepToString((Object[]) newDexElemArray));
}
forceSet(dplObj, fDexElements, newDexElemArray);
}
private static void debugDexElements(Object dexElements) throws Exception {
Object[] objArray = (Object[]) dexElements;
Class clazzElement = Class.forName("dalvik.system.DexPathList$Element");
Field fFile = clazzElement.getDeclaredField("file");
fFile.setAccessible(true);
for (int i = 0; i < objArray.length; i++) {
File f = (File) fFile.get(objArray[i]);
Log.d(TAG, "[" + i + "] " + f);
}
}
}