mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-07-15 20:38:06 +00:00
sample AS2 deobfuscator plugin
This commit is contained in:
166
examples/AS2DeobfuscatorSample.java
Normal file
166
examples/AS2DeobfuscatorSample.java
Normal file
@@ -0,0 +1,166 @@
|
||||
import com.jpexs.decompiler.flash.action.Action;
|
||||
import com.jpexs.decompiler.flash.action.ActionList;
|
||||
import com.jpexs.decompiler.flash.action.ActionLocalData;
|
||||
import com.jpexs.decompiler.flash.action.swf4.ActionAdd;
|
||||
import com.jpexs.decompiler.flash.action.swf4.ActionEquals;
|
||||
import com.jpexs.decompiler.flash.action.swf4.ActionGetVariable;
|
||||
import com.jpexs.decompiler.flash.action.swf4.ActionIf;
|
||||
import com.jpexs.decompiler.flash.action.swf4.ActionJump;
|
||||
import com.jpexs.decompiler.flash.action.swf4.ActionNot;
|
||||
import com.jpexs.decompiler.flash.action.swf4.ActionPush;
|
||||
import com.jpexs.decompiler.flash.action.swf4.ActionSetVariable;
|
||||
import com.jpexs.decompiler.flash.action.swf4.ActionSubtract;
|
||||
import com.jpexs.decompiler.flash.action.swf5.ActionAdd2;
|
||||
import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction;
|
||||
import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal;
|
||||
import com.jpexs.decompiler.flash.action.swf5.ActionReturn;
|
||||
import com.jpexs.decompiler.flash.ecma.EcmaScript;
|
||||
import com.jpexs.decompiler.flash.helpers.SWFDecompilerListener;
|
||||
import com.jpexs.decompiler.graph.Graph;
|
||||
import com.jpexs.decompiler.graph.GraphTargetItem;
|
||||
import com.jpexs.decompiler.graph.TranslateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EmptyStackException;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class AS2DeobfuscatorSample implements SWFDecompilerListener {
|
||||
|
||||
@Override
|
||||
public void actionListParsed(ActionList actions) {
|
||||
combinePushs(actions);
|
||||
if (removeFakeFunction(actions)) {
|
||||
while (removeObfuscationIfs(actions));
|
||||
}
|
||||
}
|
||||
|
||||
private void combinePushs(ActionList actions) {
|
||||
for (int i = 0; i < actions.size() - 1; i++) {
|
||||
Action action = actions.get(i);
|
||||
Action action2 = actions.get(i + 1);
|
||||
if (action instanceof ActionPush && action2 instanceof ActionPush) {
|
||||
ActionPush push = (ActionPush) action;
|
||||
ActionPush push2 = (ActionPush) action2;
|
||||
push.values.addAll(push2.values);
|
||||
actions.remove(i + 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean removeObfuscationIfs(ActionList actions) {
|
||||
if (actions.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
int idx = i;
|
||||
List<GraphTargetItem> output = new ArrayList<GraphTargetItem>();
|
||||
ActionLocalData localData = new ActionLocalData();
|
||||
Stack<GraphTargetItem> stack = new Stack<>();
|
||||
|
||||
try {
|
||||
int lastOkIdx = -1;
|
||||
while (true) {
|
||||
Action action = actions.get(idx);
|
||||
|
||||
action.translate(localData, stack, output, Graph.SOP_USE_STATIC, "");
|
||||
|
||||
if (!(action instanceof ActionPush ||
|
||||
action instanceof ActionAdd ||
|
||||
action instanceof ActionAdd2 ||
|
||||
action instanceof ActionSubtract ||
|
||||
action instanceof ActionDefineLocal ||
|
||||
action instanceof ActionJump ||
|
||||
action instanceof ActionGetVariable ||
|
||||
action instanceof ActionSetVariable ||
|
||||
action instanceof ActionEquals ||
|
||||
action instanceof ActionNot ||
|
||||
action instanceof ActionIf)) {
|
||||
break;
|
||||
}
|
||||
|
||||
idx++;
|
||||
|
||||
if (action instanceof ActionJump) {
|
||||
ActionJump jump = (ActionJump) action;
|
||||
long address = jump.getAddress() + jump.getTotalActionLength() + jump.getJumpOffset();
|
||||
idx = actions.indexOf(actions.getByAddress(address));
|
||||
}
|
||||
|
||||
if (action instanceof ActionIf) {
|
||||
ActionIf aif = (ActionIf) action;
|
||||
if (EcmaScript.toBoolean(stack.peek().getResult())) {
|
||||
long address = aif.getAddress() + aif.getTotalActionLength() + aif.getJumpOffset();
|
||||
idx = actions.indexOf(actions.getByAddress(address));
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
if (localData.variables.size() == 1 && stack.empty()) {
|
||||
lastOkIdx = idx;
|
||||
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
if (lastOkIdx != -1) {
|
||||
int a = 1;
|
||||
}
|
||||
} catch (EmptyStackException | TranslateException | InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean removeFakeFunction(ActionList actions) {
|
||||
/*
|
||||
DefineFunction "fakeName" 0 {
|
||||
Push 1777
|
||||
Return
|
||||
}
|
||||
*/
|
||||
|
||||
for (int i = 0; i < actions.size() - 2; i++) {
|
||||
Action action = actions.get(i);
|
||||
if (action instanceof ActionDefineFunction) {
|
||||
Action action2 = actions.get(i + 1);
|
||||
Action action3 = actions.get(i + 2);
|
||||
if (action2 instanceof ActionPush && action3 instanceof ActionReturn) {
|
||||
ActionDefineFunction def = (ActionDefineFunction) action;
|
||||
ActionPush push = (ActionPush) action2;
|
||||
if (def.paramNames.isEmpty() && push.values.size() == 1) {
|
||||
Object pushValueObj = push.values.get(0);
|
||||
if (pushValueObj instanceof Long) {
|
||||
String functionName = def.functionName;
|
||||
long pushValue = (Long) pushValueObj;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
actions.removeAction(i);
|
||||
}
|
||||
|
||||
for (int j = 0; j < actions.size() - 1; j++) {
|
||||
action = actions.get(j);
|
||||
if (action instanceof ActionPush) {
|
||||
push = (ActionPush) action;
|
||||
int pushValuesCount = push.values.size();
|
||||
if (pushValuesCount >= 2
|
||||
&& push.values.get(pushValuesCount - 1).equals(functionName)) {
|
||||
push.values.remove(pushValuesCount - 1);
|
||||
push.values.remove(pushValuesCount - 2);
|
||||
push.values.add(pushValue);
|
||||
actions.removeAction(j + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
40
src/com/jpexs/decompiler/flash/action/ActionList.java
Normal file
40
src/com/jpexs/decompiler/flash/action/ActionList.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 JPEXS
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.action;
|
||||
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class ActionList extends ArrayList<Action> {
|
||||
|
||||
public void removeAction(int index) {
|
||||
ActionListReader.removeAction(this, index, SWF.DEFAULT_VERSION, true);
|
||||
}
|
||||
|
||||
public Action getByAddress(long address) {
|
||||
for (Action action : this) {
|
||||
if (action.getAddress() == address) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,8 @@ import com.jpexs.decompiler.flash.configuration.Configuration;
|
||||
import com.jpexs.decompiler.flash.ecma.EcmaScript;
|
||||
import com.jpexs.decompiler.flash.ecma.Null;
|
||||
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
|
||||
import com.jpexs.decompiler.flash.gui.View;
|
||||
import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin;
|
||||
import com.jpexs.decompiler.graph.Graph;
|
||||
import com.jpexs.decompiler.graph.GraphSourceItem;
|
||||
import com.jpexs.decompiler.graph.GraphSourceItemContainer;
|
||||
@@ -90,13 +92,15 @@ public class ActionListReader {
|
||||
*/
|
||||
public static List<Action> readActionListTimeout(final List<DisassemblyListener> listeners, final SWFInputStream sis, final int version, final int ip, final int endIp, final String path) throws IOException, InterruptedException, TimeoutException {
|
||||
try {
|
||||
return CancellableWorker.call(new Callable<List<Action>>() {
|
||||
ActionList actions = CancellableWorker.call(new Callable<ActionList>() {
|
||||
|
||||
@Override
|
||||
public List<Action> call() throws IOException, InterruptedException {
|
||||
public ActionList call() throws IOException, InterruptedException {
|
||||
return readActionList(listeners, sis, version, ip, endIp, path);
|
||||
}
|
||||
}, Configuration.decompilationTimeoutSingleMethod.get(), TimeUnit.SECONDS);
|
||||
|
||||
return actions;
|
||||
} catch (ExecutionException ex) {
|
||||
Throwable cause = ex.getCause();
|
||||
if (cause instanceof InterruptedException) {
|
||||
@@ -124,7 +128,7 @@ public class ActionListReader {
|
||||
* @throws IOException
|
||||
* @throws java.lang.InterruptedException
|
||||
*/
|
||||
private static List<Action> readActionList(List<DisassemblyListener> listeners, SWFInputStream sis, int version, int ip, int endIp, String path) throws IOException, InterruptedException {
|
||||
private static ActionList readActionList(List<DisassemblyListener> listeners, SWFInputStream sis, int version, int ip, int endIp, String path) throws IOException, InterruptedException {
|
||||
ConstantPool cpool = new ConstantPool();
|
||||
|
||||
// Map of the actions. Use TreeMap to sort the keys in ascending order
|
||||
@@ -134,7 +138,7 @@ public class ActionListReader {
|
||||
sis, actionMap, nextOffsets,
|
||||
ip, 0, endIp, path, false, new ArrayList<Long>());
|
||||
|
||||
List<Action> actions = new ArrayList<>();
|
||||
ActionList actions = new ActionList();
|
||||
if (actionMap.isEmpty()) {
|
||||
return actions;
|
||||
}
|
||||
@@ -188,11 +192,25 @@ public class ActionListReader {
|
||||
endAddress -= aEnd.getTotalActionLength();
|
||||
}
|
||||
|
||||
updateJumps(actions, jumps, containerLastActions, endAddress, version);
|
||||
updateJumps(actions, jumps, containerLastActions, endAddress);
|
||||
updateActionStores(actions, jumps);
|
||||
updateContainerSizes(actions, containerLastActions);
|
||||
updateActionLengths(actions, version);
|
||||
|
||||
if (SWFDecompilerPlugin.listener != null) {
|
||||
try {
|
||||
SWFDecompilerPlugin.listener.actionListParsed(actions);
|
||||
|
||||
updateAddresses(actions, 0, version);
|
||||
updateJumps(actions, jumps, containerLastActions, endAddress);
|
||||
updateActionStores(actions, jumps);
|
||||
updateContainerSizes(actions, containerLastActions);
|
||||
updateActionLengths(actions, version);
|
||||
} catch (Throwable e) {
|
||||
View.showMessageDialog(null, "Failed to call plugin method actionListParsed. Exception: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (Configuration.autoDeobfuscate.get()) {
|
||||
try {
|
||||
actions = deobfuscateActionList(listeners, actions, version, 0, path);
|
||||
@@ -233,7 +251,7 @@ public class ActionListReader {
|
||||
* @throws IOException
|
||||
* @throws java.lang.InterruptedException
|
||||
*/
|
||||
private static List<Action> deobfuscateActionList(List<DisassemblyListener> listeners, List<Action> actions, int version, int ip, String path) throws IOException, InterruptedException {
|
||||
private static ActionList deobfuscateActionList(List<DisassemblyListener> listeners, ActionList actions, int version, int ip, String path) throws IOException, InterruptedException {
|
||||
if (actions.isEmpty()) {
|
||||
return actions;
|
||||
}
|
||||
@@ -255,12 +273,6 @@ public class ActionListReader {
|
||||
actionMap.set((int) a.getAddress(), a);
|
||||
}
|
||||
|
||||
ConstantPool cpool = new ConstantPool();
|
||||
|
||||
Stack<GraphTargetItem> stack = new Stack<>();
|
||||
|
||||
ActionLocalData localData = new ActionLocalData();
|
||||
|
||||
int maxRecursionLevel = 0;
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
Action a = actions.get(i);
|
||||
@@ -274,7 +286,16 @@ public class ActionListReader {
|
||||
}
|
||||
}
|
||||
|
||||
deobfustaceActionListAtPosRecursive(listeners, new ArrayList<GraphTargetItem>(), new HashMap<Long, List<GraphSourceItemContainer>>(), localData, stack, cpool, actionMap, ip, retdups, ip, endIp, path, new HashMap<Integer, Integer>(), false, new HashMap<Integer, HashMap<String, GraphTargetItem>>(), version, 0, maxRecursionLevel);
|
||||
deobfustaceActionListAtPosRecursive(listeners,
|
||||
new ArrayList<GraphTargetItem>(),
|
||||
new HashMap<Long, List<GraphSourceItemContainer>>(),
|
||||
new ActionLocalData(),
|
||||
new Stack<GraphTargetItem>(),
|
||||
new ConstantPool(),
|
||||
actionMap, ip, retdups, ip, endIp, path,
|
||||
new HashMap<Integer, Integer>(), false,
|
||||
new HashMap<Integer, HashMap<String, GraphTargetItem>>(),
|
||||
version, 0, maxRecursionLevel);
|
||||
|
||||
List<Action> ret = new ArrayList<>();
|
||||
Action last = null;
|
||||
@@ -285,7 +306,7 @@ public class ActionListReader {
|
||||
last = a;
|
||||
}
|
||||
ret = Action.removeNops(0, ret, version, path);
|
||||
List<Action> reta = new ArrayList<>();
|
||||
ActionList reta = new ActionList();
|
||||
for (Object o : ret) {
|
||||
if (o instanceof Action) {
|
||||
reta.add((Action) o);
|
||||
@@ -462,7 +483,7 @@ public class ActionListReader {
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateJumps(List<Action> actions, Map<Action, Action> jumps, Map<Action, List<Action>> containerLastActions, long endAddress, int version) {
|
||||
private static void updateJumps(List<Action> actions, Map<Action, Action> jumps, Map<Action, List<Action>> containerLastActions, long endAddress) {
|
||||
if (actions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -524,7 +545,7 @@ public class ActionListReader {
|
||||
* @param removeWhenLast
|
||||
* @return
|
||||
*/
|
||||
private static boolean removeAction(List<Action> actions, int index, int version, boolean removeWhenLast) {
|
||||
public static boolean removeAction(List<Action> actions, int index, int version, boolean removeWhenLast) {
|
||||
|
||||
if (index < 0 || actions.size() <= index) {
|
||||
return false;
|
||||
@@ -576,7 +597,7 @@ public class ActionListReader {
|
||||
actions.remove(index);
|
||||
|
||||
updateAddresses(actions, startIp, version);
|
||||
updateJumps(actions, jumps, containerLastActions, endAddress, version);
|
||||
updateJumps(actions, jumps, containerLastActions, endAddress);
|
||||
updateActionStores(actions, jumps);
|
||||
updateContainerSizes(actions, containerLastActions);
|
||||
updateActionLengths(actions, version);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.jpexs.decompiler.flash.action.parser.pcode;
|
||||
|
||||
import com.jpexs.decompiler.flash.action.Action;
|
||||
import com.jpexs.decompiler.flash.action.ActionList;
|
||||
import com.jpexs.decompiler.flash.action.flashlite.ActionFSCommand2;
|
||||
import com.jpexs.decompiler.flash.action.flashlite.ActionStrictMode;
|
||||
import com.jpexs.decompiler.flash.action.parser.ParseException;
|
||||
@@ -134,8 +135,8 @@ import java.util.logging.Logger;
|
||||
|
||||
public class ASMParser {
|
||||
|
||||
public static List<Action> parse(boolean ignoreNops, List<Label> labels, long address, FlasmLexer lexer, List<String> constantPool, int version) throws IOException, ParseException {
|
||||
List<Action> list = new ArrayList<>();
|
||||
public static ActionList parse(boolean ignoreNops, List<Label> labels, long address, FlasmLexer lexer, List<String> constantPool, int version) throws IOException, ParseException {
|
||||
ActionList list = new ActionList();
|
||||
Stack<GraphSourceItemContainer> containers = new Stack<>();
|
||||
|
||||
ActionConstantPool cpool = new ActionConstantPool(constantPool);
|
||||
@@ -446,7 +447,7 @@ public class ASMParser {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Action> parse(long address, boolean ignoreNops, String source, int version, boolean throwOnError) throws IOException, ParseException {
|
||||
public static ActionList parse(long address, boolean ignoreNops, String source, int version, boolean throwOnError) throws IOException, ParseException {
|
||||
FlasmLexer lexer = new FlasmLexer(new StringReader(source));
|
||||
List<Action> list = parseAllActions(lexer, version);
|
||||
|
||||
@@ -459,7 +460,7 @@ public class ASMParser {
|
||||
|
||||
lexer = new FlasmLexer(new StringReader(source));
|
||||
List<Label> labels = new ArrayList<>();
|
||||
List<Action> ret = parse(ignoreNops, labels, address, lexer, constantPool, version);
|
||||
ActionList ret = parse(ignoreNops, labels, address, lexer, constantPool, version);
|
||||
//Action.setActionsAddresses(ret, address, version);
|
||||
for (Action link : ret) {
|
||||
if (!(link instanceof ActionIf || link instanceof ActionJump)) {
|
||||
|
||||
@@ -326,6 +326,8 @@ public class Configuration {
|
||||
@ConfigurationDefaultInt(128)
|
||||
public static final ConfigurationItem<Integer> lzmaFastBytes = null;
|
||||
|
||||
public static final ConfigurationItem<String> pluginPath = null;
|
||||
|
||||
private enum OSId {
|
||||
|
||||
WINDOWS, OSX, UNIX
|
||||
|
||||
@@ -327,7 +327,7 @@ public class AdvancedSettingsDialog extends AppDialog implements ActionListener
|
||||
// Reflection exceptions. This should never happen
|
||||
throw new Error(ex.getMessage());
|
||||
}
|
||||
if (item.get() != null && !item.get().equals(value)) {
|
||||
if (item.get() == null || !item.get().equals(value)) {
|
||||
item.set(value);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.jpexs.decompiler.flash.configuration.Configuration;
|
||||
import com.jpexs.decompiler.flash.console.CommandLineArgumentParser;
|
||||
import com.jpexs.decompiler.flash.console.ContextMenuTools;
|
||||
import com.jpexs.decompiler.flash.gui.proxy.ProxyFrame;
|
||||
import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin;
|
||||
import com.jpexs.decompiler.flash.tags.base.FontTag;
|
||||
import com.jpexs.decompiler.flash.treeitems.SWFList;
|
||||
import com.jpexs.helpers.Cache;
|
||||
@@ -877,6 +878,16 @@ public class Main {
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
String pluginPath = Configuration.pluginPath.get();
|
||||
if (pluginPath != null) {
|
||||
try {
|
||||
SWFDecompilerPlugin.loadPlugin(pluginPath);
|
||||
} catch (Throwable e) {
|
||||
View.showMessageDialog(null, "Failed to load plugin: " + pluginPath);
|
||||
}
|
||||
}
|
||||
|
||||
AppStrings.setResourceClass(MainFrame.class);
|
||||
initLogging(Configuration.debugMode.get());
|
||||
initLang();
|
||||
|
||||
@@ -259,3 +259,7 @@ config.description.textExportExportFontFace = Embed font files in SVG using font
|
||||
|
||||
config.name.lzmaFastBytes = LZMA fast bytes (valid values: 5-255)
|
||||
config.description.lzmaFastBytes = Fast bytes parameter of the LZMA encoder
|
||||
|
||||
#temporary setting, do not translate it
|
||||
config.name.pluginPath = Plugin Path
|
||||
config.description.pluginPath = -
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 JPEXS
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.helpers;
|
||||
|
||||
import com.jpexs.decompiler.flash.action.ActionList;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class EmptySWFDecompilerListener implements SWFDecompilerListener {
|
||||
|
||||
@Override
|
||||
public void actionListParsed(ActionList actions) {
|
||||
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 JPEXS
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.helpers;
|
||||
|
||||
import com.jpexs.decompiler.flash.action.ActionList;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public interface SWFDecompilerListener {
|
||||
|
||||
void actionListParsed(ActionList actions);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 JPEXS, Miron Sadziak
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.helpers;
|
||||
|
||||
import com.jpexs.helpers.Helper;
|
||||
import com.jpexs.helpers.plugin.CharSequenceJavaFileObject;
|
||||
import com.jpexs.helpers.plugin.ClassFileManager;
|
||||
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 {
|
||||
|
||||
public static SWFDecompilerListener listener;
|
||||
|
||||
public static void loadPlugin(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();
|
||||
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 {
|
||||
listener = (SWFDecompilerListener) fileManager.getClassLoader(null).loadClass(fullName).newInstance();
|
||||
listener = new EmptySWFDecompilerListener();
|
||||
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
|
||||
Logger.getLogger(SWFDecompilerPlugin.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/com/jpexs/helpers/plugin/CharSequenceJavaFileObject.java
Normal file
59
src/com/jpexs/helpers/plugin/CharSequenceJavaFileObject.java
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 JPEXS, Miron Sadziak
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.jpexs.helpers.plugin;
|
||||
|
||||
import java.net.URI;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class CharSequenceJavaFileObject extends SimpleJavaFileObject {
|
||||
|
||||
/**
|
||||
* CharSequence representing the source code to be compiled
|
||||
*/
|
||||
private final CharSequence content;
|
||||
|
||||
/**
|
||||
* This constructor will store the source code in the internal "content"
|
||||
* variable and register it as a source code, using a URI containing the
|
||||
* class full name
|
||||
*
|
||||
* @param className name of the public class in the source code
|
||||
* @param content source code to compile
|
||||
*/
|
||||
public CharSequenceJavaFileObject(String className, CharSequence content) {
|
||||
super(URI.create("string:///" + className.replace('.', '/')
|
||||
+ Kind.SOURCE.extension), Kind.SOURCE);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Answers the CharSequence to be compiled. It will give the source code
|
||||
* stored in variable "content"
|
||||
*
|
||||
* @param ignoreEncodingErrors
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
90
src/com/jpexs/helpers/plugin/ClassFileManager.java
Normal file
90
src/com/jpexs/helpers/plugin/ClassFileManager.java
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 JPEXS, Miron Sadziak
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.jpexs.helpers.plugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureClassLoader;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.ForwardingJavaFileManager;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class ClassFileManager extends
|
||||
ForwardingJavaFileManager<JavaFileManager> {
|
||||
|
||||
/**
|
||||
* Instance of JavaClassObject that will store the compiled bytecode of our
|
||||
* class
|
||||
*/
|
||||
private JavaClassObject jclassObject;
|
||||
|
||||
/**
|
||||
* Will initialize the manager with the specified standard java file manager
|
||||
*
|
||||
* @param standardManager
|
||||
*/
|
||||
public ClassFileManager(StandardJavaFileManager standardManager) {
|
||||
super(standardManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be used by us to get the class loader for our compiled class. It
|
||||
* creates an anonymous class extending the SecureClassLoader which uses the
|
||||
* byte code created by the compiler and stored in the JavaClassObject, and
|
||||
* returns the Class for it
|
||||
*
|
||||
* @param location
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public ClassLoader getClassLoader(Location location) {
|
||||
return new SecureClassLoader() {
|
||||
@Override
|
||||
protected Class<?> findClass(String name)
|
||||
throws ClassNotFoundException {
|
||||
byte[] b = jclassObject.getBytes();
|
||||
return super.defineClass(name, jclassObject
|
||||
.getBytes(), 0, b.length);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the compiler an instance of the JavaClassObject so that the
|
||||
* compiler can write the byte code into it.
|
||||
*
|
||||
* @param location
|
||||
* @param className
|
||||
* @param kind
|
||||
* @param sibling
|
||||
* @return
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(Location location,
|
||||
String className, Kind kind, FileObject sibling)
|
||||
throws IOException {
|
||||
jclassObject = new JavaClassObject(className, kind);
|
||||
return jclassObject;
|
||||
}
|
||||
}
|
||||
73
src/com/jpexs/helpers/plugin/JavaClassObject.java
Normal file
73
src/com/jpexs/helpers/plugin/JavaClassObject.java
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 JPEXS, Miron Sadziak
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.jpexs.helpers.plugin;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class JavaClassObject extends SimpleJavaFileObject {
|
||||
|
||||
/**
|
||||
* Byte code created by the compiler will be stored in this
|
||||
* ByteArrayOutputStream so that we can later get the byte array out of it
|
||||
* and put it in the memory as an instance of our class.
|
||||
*/
|
||||
protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
/**
|
||||
* Registers the compiled class object under URI containing the class full
|
||||
* name
|
||||
*
|
||||
* @param name Full name of the compiled class
|
||||
* @param kind Kind of the data. It will be CLASS in our case
|
||||
*/
|
||||
public JavaClassObject(String name, Kind kind) {
|
||||
super(URI.create("string:///" + name.replace('.', '/')
|
||||
+ kind.extension), kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be used by our file manager to get the byte code that can be put
|
||||
* into memory to instantiate our class
|
||||
*
|
||||
* @return compiled byte code
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will provide the compiler with an output stream that leads to our byte
|
||||
* array. This way the compiler will write everything into the byte array
|
||||
* that we will instantiate later
|
||||
*
|
||||
* @return
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
return bos;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 JPEXS
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash;
|
||||
|
||||
import com.jpexs.decompiler.flash.action.Action;
|
||||
import com.jpexs.decompiler.flash.action.ActionList;
|
||||
import com.jpexs.decompiler.flash.action.parser.ParseException;
|
||||
import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser;
|
||||
import com.jpexs.decompiler.flash.configuration.Configuration;
|
||||
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
|
||||
import com.jpexs.decompiler.flash.gui.Main;
|
||||
import com.jpexs.decompiler.flash.helpers.CodeFormatting;
|
||||
import com.jpexs.decompiler.flash.helpers.HilightedTextWriter;
|
||||
import com.jpexs.decompiler.flash.tags.DoActionTag;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.testng.Assert;
|
||||
import static org.testng.Assert.fail;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class ActionScript2ModificationTest extends ActionStript2TestBase {
|
||||
|
||||
@BeforeClass
|
||||
public void init() throws IOException, InterruptedException {
|
||||
Main.initLogging(false);
|
||||
Configuration.autoDeobfuscate.set(false);
|
||||
swf = new SWF(new BufferedInputStream(new FileInputStream("testdata/as2/as2.swf")), false);
|
||||
}
|
||||
|
||||
private String normalizeLabels(String actions) {
|
||||
int labelCnt = 1;
|
||||
while (true) {
|
||||
Pattern pattern = Pattern.compile("^([a-z][0-9a-z]+):", Pattern.MULTILINE);
|
||||
Matcher matcher = pattern.matcher(actions);
|
||||
if (matcher.find()) {
|
||||
String str = matcher.group(1);
|
||||
actions = actions.replaceAll(str, "label_" + labelCnt++);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveAction() {
|
||||
String actionsString =
|
||||
"ConstantPool\n" +
|
||||
"Push 1\n" +
|
||||
"Jump l1\n" +
|
||||
"l1:Push 2";
|
||||
String expectedResult =
|
||||
"ConstantPool\n" +
|
||||
"Push 1 2\n";
|
||||
try {
|
||||
ActionList actions = ASMParser.parse(0, true, actionsString, swf.version, false);
|
||||
actions.removeAction(2);
|
||||
|
||||
DoActionTag doa = getFirstActionTag();
|
||||
doa.setActionBytes(Action.actionsToBytes(actions, true, swf.version));
|
||||
HilightedTextWriter writer = new HilightedTextWriter(new CodeFormatting(), false);
|
||||
doa.getASMSource(ScriptExportMode.PCODE, writer, doa.getActions());
|
||||
String actualResult = normalizeLabels(writer.toString());
|
||||
|
||||
actualResult = actualResult.replaceAll("[ \r\n]", "");
|
||||
expectedResult = expectedResult.replaceAll("[ \r\n]", "");
|
||||
|
||||
Assert.assertEquals(actualResult, expectedResult);
|
||||
} catch (IOException | ParseException | InterruptedException ex) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user