parallel AS2 export (similar to AS3)

This commit is contained in:
2015-07-11 11:41:20 +02:00
parent 737f20edd4
commit e9b74200ad
7 changed files with 209 additions and 133 deletions
@@ -28,8 +28,6 @@ public class RetryTask {
private final AbortRetryIgnoreHandler handler;
public Object result;
public RetryTask(RunnableIOEx r, AbortRetryIgnoreHandler handler) {
this.r = r;
this.handler = handler;
@@ -1346,7 +1346,7 @@ public final class SWF implements SWFContainerItem, Timelined {
if (isAS3()) {
ret.addAll(new AS3ScriptExporter().exportActionScript3(this, handler, outdir, exportSettings, parallel, evl));
} else {
ret.addAll(new AS2ScriptExporter().exportAS2ScriptsTimeout(handler, outdir, getASMs(true), exportSettings, evl));
ret.addAll(new AS2ScriptExporter().exportAS2Scripts(handler, outdir, getASMs(true), exportSettings, parallel, evl));
}
return ret;
}
@@ -19,18 +19,12 @@ package com.jpexs.decompiler.flash.exporters.script;
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
import com.jpexs.decompiler.flash.EventListener;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings;
import com.jpexs.decompiler.flash.helpers.FileTextWriter;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.graph.TranslateException;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Path;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -38,9 +32,11 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -53,35 +49,18 @@ public class AS2ScriptExporter {
private static final Logger logger = Logger.getLogger(AS2ScriptExporter.class.getName());
public List<File> exportActionScript2(SWF swf, AbortRetryIgnoreHandler handler, String outdir, ScriptExportSettings exportSettings, boolean parallel, EventListener evl) throws IOException {
return exportAS2ScriptsTimeout(handler, outdir, swf.getASMs(true), exportSettings, evl);
return exportAS2Scripts(handler, outdir, swf.getASMs(true), exportSettings, parallel, evl);
}
public List<File> exportAS2ScriptsTimeout(final AbortRetryIgnoreHandler handler, final String outdir, final Map<String, ASMSource> asms, final ScriptExportSettings exportSettings, final EventListener evl) throws IOException {
try {
List<File> result = CancellableWorker.call(new Callable<List<File>>() {
@Override
public List<File> call() throws Exception {
return exportAS2Scripts(handler, outdir, asms, exportSettings, evl);
}
}, Configuration.exportTimeout.get(), TimeUnit.SECONDS);
return result;
} catch (TimeoutException ex) {
logger.log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached", ex);
} catch (ExecutionException | InterruptedException ex) {
logger.log(Level.SEVERE, "Error during AS2 export", ex);
}
return new ArrayList<>();
}
private List<File> exportAS2Scripts(AbortRetryIgnoreHandler handler, String outdir, Map<String, ASMSource> asms, ScriptExportSettings exportSettings, EventListener evl) throws IOException, InterruptedException {
public List<File> exportAS2Scripts(AbortRetryIgnoreHandler handler, String outdir, Map<String, ASMSource> asms, ScriptExportSettings exportSettings, boolean parallel, EventListener evl) throws IOException {
List<File> ret = new ArrayList<>();
if (!outdir.endsWith(File.separator)) {
outdir += File.separator;
}
Map<String, List<String>> existingNamesMap = new HashMap<>();
AtomicInteger cnt = new AtomicInteger(1);
int cnt = 1;
List<ExportScriptTask> tasks = new ArrayList<>();
for (String key : asms.keySet()) {
ASMSource asm = asms.get(key);
String currentOutDir = outdir + key + File.separator;
@@ -102,80 +81,59 @@ public class AS2ScriptExporter {
}
existingNames.add(name);
File f = exportAS2Script(handler, currentOutDir, asm, exportSettings, evl, cnt, asms.size(), name);
if (f != null) {
ret.add(f);
tasks.add(new ExportScriptTask(handler, cnt++, asms.size(), name, asm, currentOutDir, exportSettings, evl));
}
if (!parallel || tasks.size() < 2) {
try {
CancellableWorker.call(new Callable<Void>() {
@Override
public Void call() throws Exception {
for (ExportScriptTask task : tasks) {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
ret.add(task.call());
}
return null;
}
}, Configuration.exportTimeout.get(), TimeUnit.SECONDS);
} catch (TimeoutException ex) {
logger.log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached", ex);
} catch (ExecutionException | InterruptedException ex) {
logger.log(Level.SEVERE, "Error during AS2 export", ex);
}
} else {
ExecutorService executor = Executors.newFixedThreadPool(Configuration.getParallelThreadCount());
List<Future<File>> futureResults = new ArrayList<>();
for (ExportScriptTask task : tasks) {
Future<File> future = executor.submit(task);
futureResults.add(future);
}
try {
executor.shutdown();
if (!executor.awaitTermination(Configuration.exportTimeout.get(), TimeUnit.SECONDS)) {
logger.log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached");
}
} catch (InterruptedException ex) {
} finally {
executor.shutdownNow();
}
for (int f = 0; f < futureResults.size(); f++) {
try {
if (futureResults.get(f).isDone()) {
ret.add(futureResults.get(f).get());
}
} catch (InterruptedException ex) {
} catch (ExecutionException ex) {
logger.log(Level.SEVERE, "Error during ABC export", ex);
}
}
}
return ret;
}
private File exportAS2Script(AbortRetryIgnoreHandler handler, String outdir, ASMSource asm, ScriptExportSettings exportSettings, EventListener evl, AtomicInteger index, int count, String name) throws IOException, InterruptedException {
boolean retry;
do {
retry = false;
try {
int currentIndex = index.getAndIncrement();
if (!exportSettings.singleFile) {
Path.createDirectorySafe(new File(outdir));
}
String f = Path.combine(outdir, name) + exportSettings.getFileExtension();
if (evl != null) {
evl.handleExportingEvent("script", currentIndex, count, f);
}
long startTime = System.currentTimeMillis();
File file = new File(f);
try (FileTextWriter writer = exportSettings.singleFile ? null : new FileTextWriter(Configuration.getCodeFormatting(), new FileOutputStream(f))) {
FileTextWriter writer2 = exportSettings.singleFile ? exportSettings.singleFileWriter : writer;
ScriptExportMode exportMode = exportSettings.mode;
if (exportMode == ScriptExportMode.HEX) {
asm.getActionSourcePrefix(writer2);
asm.getActionBytesAsHex(writer2);
asm.getActionSourceSuffix(writer2);
} else if (exportMode != ScriptExportMode.AS) {
asm.getActionSourcePrefix(writer2);
asm.getASMSource(exportMode, writer2, null);
asm.getActionSourceSuffix(writer2);
} else {
List<Action> as = asm.getActions();
Action.setActionsAddresses(as, 0);
Action.actionsToSource(asm, as, asm.toString()/*FIXME?*/, writer2);
}
}
long stopTime = System.currentTimeMillis();
if (evl != null) {
long time = stopTime - startTime;
evl.handleExportedEvent("script", currentIndex, count, f + ", " + Helper.formatTimeSec(time));
}
return file;
} catch (InterruptedException ex) {
throw ex;
} catch (IOException | OutOfMemoryError | TranslateException | StackOverflowError ex) {
logger.log(Level.SEVERE, "Decompilation error in script: " + name, ex);
if (handler != null) {
int action = handler.getNewInstance().handle(ex);
switch (action) {
case AbortRetryIgnoreHandler.ABORT:
throw ex;
case AbortRetryIgnoreHandler.RETRY:
retry = true;
break;
case AbortRetryIgnoreHandler.IGNORE:
retry = false;
break;
}
}
}
} while (retry);
return null;
}
}
@@ -34,7 +34,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -46,27 +45,30 @@ public class AS3ScriptExporter {
private static final Logger logger = Logger.getLogger(AS3ScriptExporter.class.getName());
public List<File> exportActionScript3(SWF swf, final AbortRetryIgnoreHandler handler, final String outdir, final ScriptExportSettings exportSettings, final boolean parallel, final EventListener evl) {
final AtomicInteger cnt = new AtomicInteger(1);
public List<File> exportActionScript3(SWF swf, AbortRetryIgnoreHandler handler, String outdir, ScriptExportSettings exportSettings, boolean parallel, EventListener evl) {
final List<File> ret = new ArrayList<>();
final List<ScriptPack> packs = swf.getAS3Packs();
if (!parallel || packs.size() < 2) {
int cnt = 1;
List<ExportPackTask> tasks = new ArrayList<>();
for (ScriptPack item : packs) {
if (!item.isSimple && Configuration.ignoreCLikePackages.get()) {
continue;
}
tasks.add(new ExportPackTask(handler, cnt++, packs.size(), item.getClassPath(), item, outdir, exportSettings, parallel, evl));
}
if (!parallel || tasks.size() < 2) {
try {
CancellableWorker.call(new Callable<Void>() {
@Override
public Void call() throws Exception {
for (ScriptPack item : packs) {
for (ExportPackTask task : tasks) {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
if (!item.isSimple && Configuration.ignoreCLikePackages.get()) {
continue;
}
ExportPackTask task = new ExportPackTask(handler, cnt, packs.size(), item.getClassPath(), item, outdir, exportSettings, parallel, evl);
ret.add(task.call());
}
return null;
@@ -74,17 +76,14 @@ public class AS3ScriptExporter {
}, Configuration.exportTimeout.get(), TimeUnit.SECONDS);
} catch (TimeoutException ex) {
logger.log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached", ex);
} catch (Exception ex) {
} catch (ExecutionException | InterruptedException ex) {
logger.log(Level.SEVERE, "Error during ABC export", ex);
}
} else {
ExecutorService executor = Executors.newFixedThreadPool(Configuration.getParallelThreadCount());
List<Future<File>> futureResults = new ArrayList<>();
for (ScriptPack item : packs) {
if (!item.isSimple && Configuration.ignoreCLikePackages.get()) {
continue;
}
Future<File> future = executor.submit(new ExportPackTask(handler, cnt, packs.size(), item.getClassPath(), item, outdir, exportSettings, parallel, evl));
for (ExportPackTask task : tasks) {
Future<File> future = executor.submit(task);
futureResults.add(future);
}
@@ -20,7 +20,6 @@ import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
import com.jpexs.decompiler.flash.EventListener;
import com.jpexs.decompiler.flash.RetryTask;
import com.jpexs.decompiler.flash.RunnableIOExResult;
import com.jpexs.decompiler.flash.abc.ABC;
import com.jpexs.decompiler.flash.abc.ClassPath;
import com.jpexs.decompiler.flash.abc.ScriptPack;
import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings;
@@ -28,7 +27,6 @@ import com.jpexs.helpers.Helper;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
@@ -44,7 +42,7 @@ public class ExportPackTask implements Callable<File> {
ClassPath path;
AtomicInteger index;
int index;
int count;
@@ -58,7 +56,7 @@ public class ExportPackTask implements Callable<File> {
EventListener eventListener;
public ExportPackTask(AbortRetryIgnoreHandler handler, AtomicInteger index, int count, ClassPath path, ScriptPack pack, String directory, ScriptExportSettings exportSettings, boolean parallel, EventListener evl) {
public ExportPackTask(AbortRetryIgnoreHandler handler, int index, int count, ClassPath path, ScriptPack pack, String directory, ScriptExportSettings exportSettings, boolean parallel, EventListener evl) {
this.pack = pack;
this.directory = directory;
this.exportSettings = exportSettings;
@@ -71,7 +69,7 @@ public class ExportPackTask implements Callable<File> {
}
@Override
public File call() throws Exception {
public File call() throws IOException, InterruptedException {
RunnableIOExResult<File> rio = new RunnableIOExResult<File>() {
@Override
public void run() throws IOException, InterruptedException {
@@ -80,19 +78,18 @@ public class ExportPackTask implements Callable<File> {
stopTime = System.currentTimeMillis();
}
};
int currentIndex = index.getAndIncrement();
if (eventListener != null) {
synchronized (ABC.class) {
eventListener.handleExportingEvent("script", currentIndex, count, path);
}
eventListener.handleExportingEvent("script", index, count, path);
}
new RetryTask(rio, handler).run();
if (eventListener != null) {
synchronized (ABC.class) {
long time = stopTime - startTime;
eventListener.handleExportedEvent("script", currentIndex, count, path + ", " + Helper.formatTimeSec(time));
}
long time = stopTime - startTime;
eventListener.handleExportedEvent("script", index, count, path + ", " + Helper.formatTimeSec(time));
}
return rio.result;
}
}
@@ -0,0 +1,124 @@
/*
* Copyright (C) 2010-2015 JPEXS, 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.exporters.script;
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
import com.jpexs.decompiler.flash.EventListener;
import com.jpexs.decompiler.flash.RunnableIOExResult;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings;
import com.jpexs.decompiler.flash.helpers.FileTextWriter;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Path;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.logging.Logger;
/**
*
* @author JPEXS
*/
public class ExportScriptTask implements Callable<File> {
private static final Logger logger = Logger.getLogger(ExportScriptTask.class.getName());
ASMSource asm;
String directory;
ScriptExportSettings exportSettings;
String name;
int index;
int count;
AbortRetryIgnoreHandler handler;
long startTime;
long stopTime;
EventListener eventListener;
public ExportScriptTask(AbortRetryIgnoreHandler handler, int index, int count, String name, ASMSource asm, String directory, ScriptExportSettings exportSettings, EventListener evl) {
this.asm = asm;
this.directory = directory;
this.exportSettings = exportSettings;
this.name = name;
this.index = index;
this.count = count;
this.handler = handler;
this.eventListener = evl;
}
@Override
public File call() throws IOException, InterruptedException {
String f = Path.combine(directory, name) + exportSettings.getFileExtension();
RunnableIOExResult<File> rio = new RunnableIOExResult<File>() {
@Override
public void run() throws IOException, InterruptedException {
startTime = System.currentTimeMillis();
if (!exportSettings.singleFile) {
Path.createDirectorySafe(new File(directory));
}
File file = new File(f);
try (FileTextWriter writer = exportSettings.singleFile ? null : new FileTextWriter(Configuration.getCodeFormatting(), new FileOutputStream(f))) {
FileTextWriter writer2 = exportSettings.singleFile ? exportSettings.singleFileWriter : writer;
ScriptExportMode exportMode = exportSettings.mode;
if (exportMode == ScriptExportMode.HEX) {
asm.getActionSourcePrefix(writer2);
asm.getActionBytesAsHex(writer2);
asm.getActionSourceSuffix(writer2);
} else if (exportMode != ScriptExportMode.AS) {
asm.getActionSourcePrefix(writer2);
asm.getASMSource(exportMode, writer2, null);
asm.getActionSourceSuffix(writer2);
} else {
List<Action> as = asm.getActions();
Action.setActionsAddresses(as, 0);
Action.actionsToSource(asm, as, asm.toString()/*FIXME?*/, writer2);
}
}
this.result = file;
stopTime = System.currentTimeMillis();
}
};
if (eventListener != null) {
eventListener.handleExportingEvent("script", index, count, f);
}
if (eventListener != null) {
long time = stopTime - startTime;
eventListener.handleExportedEvent("script", index, count, f + ", " + Helper.formatTimeSec(time));
}
return rio.result;
}
}