/* * Copyright (C) 2010-2016 JPEXS, Miron Sadziak, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.jpexs.decompiler.flash.helpers; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.abc.ABC; import com.jpexs.decompiler.flash.abc.types.MethodBody; import com.jpexs.decompiler.flash.abc.types.traits.Trait; import com.jpexs.decompiler.flash.action.ActionList; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.helpers.Helper; import com.jpexs.helpers.Path; import com.jpexs.helpers.plugin.CharSequenceJavaFileObject; import com.jpexs.helpers.plugin.ClassFileManager; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.tools.JavaCompiler; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; /** * * @author JPEXS */ public class SWFDecompilerPlugin { private static final Logger logger = Logger.getLogger(SWFDecompilerPlugin.class.getName()); private static final List<SWFDecompilerListener> listeners = new ArrayList<>(); public static String[] customParameters = new String[0]; public static File getPluginsDir() { File pluginPath = null; try { String pluginPathConfig = Configuration.pluginPath.get(); if (pluginPathConfig != null && !pluginPathConfig.isEmpty()) { pluginPath = new File(pluginPathConfig); } if (pluginPath == null || !pluginPath.exists()) { File f = new File(SWFDecompilerPlugin.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()); File dir = f.getAbsoluteFile().getParentFile().getParentFile(); pluginPath = new File(Path.combine(dir.getPath(), "plugins")).getCanonicalFile(); } } catch (IOException | URISyntaxException ex) { logger.log(Level.SEVERE, null, ex); } return pluginPath; } public static void loadPlugins() { File pluginPath = getPluginsDir(); if (pluginPath != null && pluginPath.exists()) { System.out.println("Loading plugins from " + pluginPath.getPath()); File[] files = pluginPath.listFiles(); if (files != null) { for (File file : files) { System.out.println("Loading plugin: " + file.getPath()); loadPlugin(file.getPath()); } } } } public static void loadPlugin(String path) { if (".class".equals(Path.getExtension(path))) { loadPluginCompiled(path); } else { loadPluginSource(path); } } private static void loadPluginCompiled(String path) { File pluginFile = new File(path); File file = pluginFile.getParentFile(); try { // Convert File to a URL URL url = file.toURI().toURL(); URL[] urls = new URL[]{url}; // Create a new class loader with the directory ClassLoader cl = new URLClassLoader(urls); String pluginName = Path.getFileNameWithoutExtension(pluginFile); Class<?> cls = cl.loadClass(pluginName); if (SWFDecompilerListener.class.isAssignableFrom(cls)) { SWFDecompilerListener listener = (SWFDecompilerListener) cls.newInstance(); listeners.add(listener); } System.out.println("Plugin loaded: " + pluginName); } catch (MalformedURLException | ClassNotFoundException | InstantiationException | IllegalAccessException ex) { logger.log(Level.SEVERE, null, ex); } } private static void loadPluginSource(String path) { // Here we specify the source code of the class to be compiled String src = Helper.readTextFile(path); // Full name of the class that will be compiled. // If class should be in some package, // fullName should contain it too // (ex. "testpackage.DynaClass") int idx = src.indexOf("public class "); String fullName = src.substring(idx + 13); fullName = fullName.substring(0, fullName.indexOf(' ')).trim(); // We get an instance of JavaCompiler. Then // we create a file manager // (our custom implementation of it) JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); if (compiler == null) { logger.log(Level.SEVERE, "Compiler is null"); return; } JavaFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null)); // Dynamic compiling requires specifying // a list of "files" to compile. In our case // this is a list containing one "file" which is in our case // our own implementation (see details below) List<JavaFileObject> jfiles = new ArrayList<>(); jfiles.add(new CharSequenceJavaFileObject(fullName, src)); // We specify a task to the compiler. Compiler should use our file // manager and our list of "files". // Then we run the compilation with call() compiler.getTask(null, fileManager, null, null, null, jfiles).call(); // Creating an instance of our compiled class and try { listeners.add((SWFDecompilerListener) fileManager.getClassLoader(null).loadClass(fullName).newInstance()); System.out.println("Plugin loaded: " + fullName); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) { logger.log(Level.SEVERE, null, ex); } } public static byte[] fireProxyFileCatched(byte[] data) { byte[] result = null; for (SWFDecompilerListener listener : listeners) { try { byte[] newResult = listener.proxyFileCatched(data); if (newResult != null) { result = newResult; data = newResult; } } catch (ThreadDeath ex) { throw ex; } catch (Throwable e) { logger.log(Level.SEVERE, "Failed to call plugin method proxyFileCatched.", e); } } return result; } public static boolean fireSwfParsed(SWF swf) { for (SWFDecompilerListener listener : listeners) { try { listener.swfParsed(swf); } catch (ThreadDeath ex) { throw ex; } catch (Throwable e) { logger.log(Level.SEVERE, "Failed to call plugin method swfParsed.", e); } } return !listeners.isEmpty(); } public static boolean fireActionListParsed(ActionList actions, SWF swf) throws InterruptedException { for (SWFDecompilerListener listener : listeners) { try { listener.actionListParsed(actions, swf); } catch (ThreadDeath | InterruptedException ex) { throw ex; } catch (Throwable e) { logger.log(Level.SEVERE, "Failed to call plugin method actionListParsed.", e); } } return !listeners.isEmpty(); } public static boolean fireActionTreeCreated(List<GraphTargetItem> tree, SWF swf) throws InterruptedException { for (SWFDecompilerListener listener : listeners) { try { listener.actionTreeCreated(tree, swf); } catch (ThreadDeath | InterruptedException ex) { throw ex; } catch (Throwable e) { logger.log(Level.SEVERE, "Failed to call plugin method actionTreeCreated.", e); } } return !listeners.isEmpty(); } public static boolean fireAbcParsed(ABC abc, SWF swf) { for (SWFDecompilerListener listener : listeners) { try { listener.abcParsed(abc, swf); } catch (ThreadDeath ex) { throw ex; } catch (Throwable e) { logger.log(Level.SEVERE, "Failed to call plugin method abcParsed.", e); } } return !listeners.isEmpty(); } public static boolean fireMethodBodyParsed(ABC abc, MethodBody body, SWF swf) { for (SWFDecompilerListener listener : listeners) { try { listener.methodBodyParsed(abc, body, swf); } catch (ThreadDeath ex) { throw ex; } catch (Throwable e) { logger.log(Level.SEVERE, "Failed to call plugin method methodBodyParsed.", e); } } return !listeners.isEmpty(); } public static boolean fireAvm2CodeRemoveTraps(String path, int classIndex, boolean isStatic, int scriptIndex, ABC abc, Trait trait, int methodInfo, MethodBody body) throws InterruptedException { for (SWFDecompilerListener listener : listeners) { try { listener.avm2CodeRemoveTraps(path, classIndex, isStatic, scriptIndex, abc, trait, methodInfo, body); } catch (ThreadDeath | InterruptedException ex) { throw ex; } catch (Throwable e) { logger.log(Level.SEVERE, "Failed to call plugin method abcParsed.", e); } } return !listeners.isEmpty(); } }