package net.fourbytes.shadow.utils.backend; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; import dalvik.system.PathClassLoader; import net.fourbytes.shadow.Shadow; import net.fourbytes.shadow.mod.ChainClassLoader; import net.fourbytes.shadow.mod.IMod; import net.fourbytes.shadow.mod.ModFile; import net.fourbytes.shadow.mod.ModManager; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.List; /** * AndroidModLoader is an implementation of ModLoader for * the Android backend. */ public class AndroidModLoader extends ModLoader { public AndroidModLoader() { } public static String MOD_META_KEY = "shadowmodule"; @Override public void init(String root) { FileHandle fhbl = Shadow.getDir(null).child("blacklist.txt"); Array<String> blacklist = new Array<String>(); if (!fhbl.exists()) { try { fhbl.file().createNewFile(); } catch (IOException e) { e.printStackTrace(); } } else { BufferedReader reader = new BufferedReader(fhbl.reader()); try { String line; do { line = reader.readLine(); if (line != null) { blacklist.add(line); } } while (line != null); } catch (IOException e1) { e1.printStackTrace(); } finally { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } init(null, blacklist); } @Override public void init(String root, Array<String> blacklist) { PackageManager pm = ((AndroidApplication)Gdx.app).getPackageManager(); if (pm == null) { return; } //TODO Find out whether META_DATA can be replaced with simple 0 List<PackageInfo> rawpkgs = pm.getInstalledPackages(PackageManager.GET_META_DATA); for (PackageInfo pkg : rawpkgs) { String packageName = pkg.packageName; if (blacklist != null && blacklist.contains(packageName, false)) { ModManager.filesIgnored.add(new ModFile(packageName)); continue; } load(packageName); } } @Override public IMod load(ModFile mf) { return load(mf.pkg); } @Override public IMod load(String path) { PackageManager pm = ((AndroidApplication)Gdx.app).getPackageManager(); if (pm == null) { return null; } IMod mod = null; ModFile mf = new ModFile(path); try { PackageInfo pkg = pm.getPackageInfo(path, PackageManager.GET_META_DATA); ApplicationInfo app = pkg.applicationInfo; Bundle metaData = app.metaData; if (app.enabled && metaData != null && metaData.containsKey(MOD_META_KEY)) { System.out.println(path+" is a great module candidate..."); String clazzLoadName = metaData.getString(MOD_META_KEY); String pathAPK = app.publicSourceDir; String pathLIB = app.nativeLibraryDir; System.out.println("APK: \""+pathAPK+"\""); System.out.println("LIB: \""+pathLIB+"\""); //Get new APK class loader using own class loader wrapped in ChainClassLoader as parent. //History: //Own CL didn't work, own parent CL did, but with different class instances (not crashing JVM) //Custom worked partially, at some point failed as own did //Actually, JVM crashed with own CL due to conflicting class instances //Fixed by changing the dependency structure in IntelliJ and using the own CL again. //Getting and creating needed class loaders ClassLoader ownCL = Shadow.class.getClassLoader(); ChainClassLoader chainCL = new ChainClassLoader(ownCL); PathClassLoader cl = new PathClassLoader(pathAPK, pathLIB, getClass().getClassLoader()); chainCL.blacklist = new String[] {"net.fourbytes.shadow.mod.Config"}; if (ModManager.mods.size < 0) { IMod pmod = ModManager.mods.items[ModManager.mods.size - 1]; ClassLoader parentCL = pmod.getClass().getClassLoader().getParent(); System.out.println(parentCL); if (parentCL instanceof ChainClassLoader) { ChainClassLoader parentChainCL = (ChainClassLoader) parentCL; parentChainCL.chainChild = chainCL; chainCL.chainParent = parentChainCL; } } Class<?> clazzNT = cl.loadClass(clazzLoadName); if (IMod.class.isAssignableFrom(clazzNT)) { Class<? extends IMod> clazz = clazzNT.asSubclass(IMod.class); System.out.println("So far so good... let's load the class."); //Creating mod mod = clazz.getConstructor().newInstance(); System.out.println(mod.getClass().getClassLoader().getParent()); //Adding mod to manager ModManager.mods.add(mod); mf.mod = mod; ModManager.filesLoaded.add(mf); ModManager.mapModFile.put(mod, mf); System.out.println("Such ClassLoader! Very Android! WOW!"); } else { System.out.println("... the cake is a lie... or from another dimension. Either of that."); } } /* if (metaData != null) { System.out.println("app: "+path); for (String key : metaData.keySet()) { System.out.println("key: "+key); System.out.println("raw: "+(metaData.get(key).toString())); } } */ } catch (Exception e) { e.printStackTrace(); if (ModManager.filesFailed.contains(mf, false)) { ModManager.filesFailed.add(mf); } } loadFailed(); return mod; } @Override public void blacklist(ModFile modfile, boolean blacklist) { FileHandle fhbl = Shadow.getDir(null).child("blacklist.txt"); boolean blacklisted = false; Array<String> lines = new Array<String>(String.class); BufferedReader br = new BufferedReader(fhbl.reader()); try { String line; while ((line = br.readLine()) != null) { if (line.trim().equals(modfile.pkg)) { blacklisted = true; } else { lines.add(line); } } } catch (IOException e) { e.printStackTrace(); } finally { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (!blacklist && blacklisted) { BufferedWriter bw = new BufferedWriter(fhbl.writer(false)); try { for (int i = 0; i < lines.size; i++) { String line = lines.items[i].trim(); if (line.length() > 0 && !line.equals(modfile.pkg)) { bw.write(line + "\n"); } } bw.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } else if (blacklist && !blacklisted) { try { fhbl.writeBytes((modfile.pkg + "\n").getBytes("UTF-8"), true); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } @Override public void delete(ModFile modfile) { if (!modfile.canDelete) { throw new RuntimeException("Can not uninstall "+modfile.pkg+" as ModFile.canDelete == false"); } Intent intent = new Intent(Intent.ACTION_DELETE, Uri.parse("package:"+modfile.pkg)); ((AndroidApplication)Gdx.app).startActivity(intent); } }