/*
* Copyright (C) 2010-2013 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 .
*/
package com.jpexs.decompiler.flash;
import SevenZip.Compression.LZMA.Encoder;
import com.jpexs.decompiler.flash.abc.ABC;
import com.jpexs.decompiler.flash.abc.ClassPath;
import com.jpexs.decompiler.flash.abc.RenameType;
import com.jpexs.decompiler.flash.abc.ScriptPack;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.ActionDeobfuscation;
import com.jpexs.decompiler.flash.action.ActionGraphSource;
import com.jpexs.decompiler.flash.action.model.ConstantPool;
import com.jpexs.decompiler.flash.action.model.DirectValueActionItem;
import com.jpexs.decompiler.flash.action.model.FunctionActionItem;
import com.jpexs.decompiler.flash.action.model.GetMemberActionItem;
import com.jpexs.decompiler.flash.action.model.GetVariableActionItem;
import com.jpexs.decompiler.flash.action.model.clauses.ClassActionItem;
import com.jpexs.decompiler.flash.action.model.clauses.InterfaceActionItem;
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.ActionPush;
import com.jpexs.decompiler.flash.action.swf4.ActionSetVariable;
import com.jpexs.decompiler.flash.action.swf4.ConstantIndex;
import com.jpexs.decompiler.flash.action.swf5.ActionCallFunction;
import com.jpexs.decompiler.flash.action.swf5.ActionCallMethod;
import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool;
import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction;
import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal;
import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal2;
import com.jpexs.decompiler.flash.action.swf5.ActionEquals2;
import com.jpexs.decompiler.flash.action.swf5.ActionGetMember;
import com.jpexs.decompiler.flash.action.swf5.ActionNewMethod;
import com.jpexs.decompiler.flash.action.swf5.ActionNewObject;
import com.jpexs.decompiler.flash.action.swf5.ActionSetMember;
import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.ecma.Null;
import com.jpexs.decompiler.flash.flv.AUDIODATA;
import com.jpexs.decompiler.flash.flv.FLVOutputStream;
import com.jpexs.decompiler.flash.flv.FLVTAG;
import com.jpexs.decompiler.flash.flv.VIDEODATA;
import com.jpexs.decompiler.flash.gui.SWFSourceInfo;
import com.jpexs.decompiler.flash.helpers.collections.MyEntry;
import com.jpexs.decompiler.flash.tags.ABCContainerTag;
import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag;
import com.jpexs.decompiler.flash.tags.DefineButton2Tag;
import com.jpexs.decompiler.flash.tags.DefineButtonTag;
import com.jpexs.decompiler.flash.tags.DefineSoundTag;
import com.jpexs.decompiler.flash.tags.DefineSpriteTag;
import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag;
import com.jpexs.decompiler.flash.tags.DoInitActionTag;
import com.jpexs.decompiler.flash.tags.ExportAssetsTag;
import com.jpexs.decompiler.flash.tags.FileAttributesTag;
import com.jpexs.decompiler.flash.tags.JPEGTablesTag;
import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag;
import com.jpexs.decompiler.flash.tags.ShowFrameTag;
import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag;
import com.jpexs.decompiler.flash.tags.SymbolClassTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.VideoFrameTag;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
import com.jpexs.decompiler.flash.tags.base.CharacterIdTag;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.Container;
import com.jpexs.decompiler.flash.tags.base.ContainerItem;
import com.jpexs.decompiler.flash.tags.base.DrawableTag;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
import com.jpexs.decompiler.flash.tags.base.RemoveTag;
import com.jpexs.decompiler.flash.tags.base.ShapeTag;
import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag;
import com.jpexs.decompiler.flash.tags.base.TextTag;
import com.jpexs.decompiler.flash.types.CXFORM;
import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
import com.jpexs.decompiler.flash.types.MATRIX;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.filters.BlendComposite;
import com.jpexs.decompiler.flash.types.filters.FILTER;
import com.jpexs.decompiler.flash.types.filters.Filtering;
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
import com.jpexs.decompiler.flash.types.sound.AdpcmDecoder;
import com.jpexs.decompiler.flash.xfl.XFLConverter;
import com.jpexs.decompiler.graph.ExportMode;
import com.jpexs.decompiler.graph.Graph;
import com.jpexs.decompiler.graph.GraphSourceItem;
import com.jpexs.decompiler.graph.GraphSourceItemContainer;
import com.jpexs.decompiler.graph.GraphTargetItem;
import com.jpexs.decompiler.graph.model.LocalData;
import com.jpexs.helpers.Cache;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.ProgressListener;
import com.jpexs.helpers.utf8.Utf8Helper;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
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;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import javax.imageio.ImageIO;
/**
* Class representing SWF file
*
* @author JPEXS
*/
public final class SWF {
/**
* Default version of SWF file format
*/
public static final int DEFAULT_VERSION = 10;
/**
* Tags inside of file
*/
public List tags = new ArrayList<>();
/**
* Rectangle for the display
*/
public RECT displayRect;
/**
* Movie frame rate
*/
public int frameRate;
/**
* Number of frames in movie
*/
public int frameCount;
/**
* Version of SWF
*/
public int version;
/**
* Size of the file
*/
public long fileSize;
/**
* Use compression
*/
public boolean compressed = false;
/**
* Use LZMA compression
*/
public boolean lzma = false;
/**
* Compressed size of the file (LZMA)
*/
public long compressedSize;
/**
* LZMA Properties
*/
public byte[] lzmaProperties;
public FileAttributesTag fileAttributes;
/**
* ScaleForm GFx
*/
public boolean gfx = false;
public SWFSourceInfo sourceInfo;
public String file;
public String fileTitle;
public HashMap characters;
public List abcList;
public JPEGTablesTag jtt;
/**
* Gets all tags with specified id
*
* @param tagId Identificator of tag type
* @return List of tags
*/
public List getTagData(int tagId) {
List ret = new ArrayList<>();
for (Tag tag : tags) {
if (tag.getId() == tagId) {
ret.add(tag);
}
}
return ret;
}
/**
* Saves this SWF into new file
*
* @param os OutputStream to save SWF in
* @throws IOException
*/
public void saveTo(OutputStream os) throws IOException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
SWFOutputStream sos = new SWFOutputStream(baos, version);
sos.writeRECT(displayRect);
sos.writeUI8(0);
sos.writeUI8(frameRate);
sos.writeUI16(frameCount);
sos.writeTags(tags);
sos.writeUI16(0);
sos.close();
if (compressed && lzma) {
os.write('Z');
} else if (compressed) {
os.write('C');
} else {
if (gfx) {
os.write('G');
} else {
os.write('F');
}
}
if (gfx) {
os.write('F');
os.write('X');
} else {
os.write('W');
os.write('S');
}
os.write(version);
byte[] data = baos.toByteArray();
sos = new SWFOutputStream(os, version);
sos.writeUI32(data.length + 8);
if (compressed) {
if (lzma) {
Encoder enc = new Encoder();
int val = lzmaProperties[0] & 0xFF;
int lc = val % 9;
int remainder = val / 9;
int lp = remainder % 5;
int pb = remainder / 5;
int dictionarySize = 0;
for (int i = 0; i < 4; i++) {
dictionarySize += ((int) (lzmaProperties[1 + i]) & 0xFF) << (i * 8);
}
enc.SetDictionarySize(dictionarySize);
enc.SetLcLpPb(lc, lp, pb);
baos = new ByteArrayOutputStream();
enc.SetEndMarkerMode(true);
enc.Code(new ByteArrayInputStream(data), baos, -1, -1, null);
data = baos.toByteArray();
byte[] udata = new byte[4];
udata[0] = (byte) (data.length & 0xFF);
udata[1] = (byte) ((data.length >> 8) & 0xFF);
udata[2] = (byte) ((data.length >> 16) & 0xFF);
udata[3] = (byte) ((data.length >> 24) & 0xFF);
os.write(udata);
os.write(lzmaProperties);
} else {
os = new DeflaterOutputStream(os);
}
}
os.write(data);
} finally {
if (os != null) {
os.close();
}
}
}
public SWF(InputStream is, boolean parallelRead) throws IOException, InterruptedException {
this(is, null, parallelRead);
}
/**
* Construct SWF from stream
*
* @param is Stream to read SWF from
* @param listener
* @param parallelRead Use parallel threads?
* @throws IOException
*/
public SWF(InputStream is, ProgressListener listener, boolean parallelRead) throws IOException, InterruptedException {
this(is, listener, parallelRead, false);
}
/**
* Construct SWF from stream
*
* @param is Stream to read SWF from
* @param listener
* @param parallelRead Use parallel threads?
* @param checkOnly Check only file validity
* @throws IOException
*/
public SWF(InputStream is, ProgressListener listener, boolean parallelRead, boolean checkOnly) throws IOException, InterruptedException {
byte[] hdr = new byte[3];
is.read(hdr);
String shdr = new String(hdr, Utf8Helper.charset);
if (!Arrays.asList(
"FWS", //Uncompressed Flash
"CWS", //ZLib compressed Flash
"ZWS", //LZMA compressed Flash
"GFX", //Uncompressed ScaleForm GFx
"CFX" //Compressed ScaleForm GFx
).contains(shdr)) {
throw new IOException("Invalid SWF file");
}
version = is.read();
SWFInputStream sis = new SWFInputStream(is, version, 4);
fileSize = sis.readUI32();
if(hdr[1] == 'F' && hdr[2] == 'X'){
gfx = true;
}
if (hdr[0] == 'C') {
sis = new SWFInputStream(new InflaterInputStream(is), version, 8);
compressed = true;
}
if (hdr[0] == 'Z') {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
sis.readUI32(); //outSize
int propertiesSize = 5;
lzmaProperties = new byte[propertiesSize];
if (sis.read(lzmaProperties, 0, propertiesSize) != propertiesSize) {
throw new IOException("LZMA:input .lzma file is too short");
}
long dictionarySize = 0;
for (int i = 0; i < 4; i++) {
dictionarySize += ((int) (lzmaProperties[1 + i]) & 0xFF) << (i * 8);
if (dictionarySize > Runtime.getRuntime().freeMemory()) {
throw new IOException("LZMA: Too large dictionary size");
}
}
SevenZip.Compression.LZMA.Decoder decoder = new SevenZip.Compression.LZMA.Decoder();
if (!decoder.SetDecoderProperties(lzmaProperties)) {
throw new IOException("LZMA:Incorrect stream properties");
}
if (!decoder.Code(sis, baos, fileSize - 8)) {
throw new IOException("LZMA:Error in data stream");
}
sis = new SWFInputStream(new ByteArrayInputStream(baos.toByteArray()), version, 8);
compressed = true;
lzma = true;
}
if (listener != null) {
sis.addPercentListener(listener);
}
sis.setPercentMax(fileSize);
displayRect = sis.readRECT();
// FIXED8 (16 bit fixed point) frameRate
sis.readUI8(); //tmpFirstByetOfFrameRate
frameRate = sis.readUI8();
frameCount = sis.readUI16();
if (checkOnly) {
//return;
}
tags = sis.readTagList(this, 0, parallelRead, true, !checkOnly);
if (!checkOnly) {
assignExportNamesToSymbols();
assignClassesToSymbols();
findFileAttributes();
}
}
/**
* Get title of the file
*
* @return file title
*/
public String getFileTitle() {
if (fileTitle != null) {
return fileTitle;
}
return file;
}
private void findFileAttributes() {
for (Tag t : tags) {
if (t instanceof FileAttributesTag) {
fileAttributes = (FileAttributesTag) t;
break;
}
}
}
private void assignExportNamesToSymbols() {
HashMap exportNames = new HashMap<>();
for (Tag t : tags) {
if (t instanceof ExportAssetsTag) {
ExportAssetsTag eat = (ExportAssetsTag) t;
for (int i = 0; i < eat.tags.size(); i++) {
if ((!exportNames.containsKey(eat.tags.get(i))) && (!exportNames.containsValue(eat.names.get(i)))) {
exportNames.put(eat.tags.get(i), eat.names.get(i));
}
}
}
}
for (Tag t : tags) {
if (t instanceof CharacterIdTag) {
CharacterIdTag ct = (CharacterIdTag) t;
if (exportNames.containsKey(ct.getCharacterId())) {
ct.setExportName(exportNames.get(ct.getCharacterId()));
}
}
}
}
public void assignClassesToSymbols() {
HashMap classes = new HashMap<>();
for (Tag t : tags) {
if (t instanceof SymbolClassTag) {
SymbolClassTag sct = (SymbolClassTag) t;
for (int i = 0; i < sct.tagIDs.length; i++) {
if ((!classes.containsKey(sct.tagIDs[i])) && (!classes.containsValue(sct.classNames[i]))) {
classes.put(sct.tagIDs[i], sct.classNames[i]);
}
}
}
}
for (Tag t : tags) {
if (t instanceof CharacterIdTag) {
CharacterIdTag ct = (CharacterIdTag) t;
if (classes.containsKey(ct.getCharacterId())) {
ct.setClassName(classes.get(ct.getCharacterId()));
}
}
}
}
/**
* Compress SWF file
*
* @param fis Input stream
* @param fos Output stream
* @return True on success
*/
public static boolean fws2cws(InputStream fis, OutputStream fos) {
try {
byte[] swfHead = new byte[8];
fis.read(swfHead);
if (swfHead[0] != 'F') {
fis.close();
return false;
}
swfHead[0] = 'C';
fos.write(swfHead);
fos = new DeflaterOutputStream(fos);
int i;
while ((i = fis.read()) != -1) {
fos.write(i);
}
fis.close();
fos.close();
} catch (IOException ex) {
return false;
}
return true;
}
public static boolean decompress(InputStream fis, OutputStream fos) {
try {
byte[] hdr = new byte[3];
fis.read(hdr);
String shdr = new String(hdr, Utf8Helper.charset);
switch (shdr) {
case "CWS":
{
int version = fis.read();
SWFInputStream sis = new SWFInputStream(fis, version, 4);
long fileSize = sis.readUI32();
SWFOutputStream sos = new SWFOutputStream(fos, version);
sos.write(Utf8Helper.getBytes("FWS"));
sos.writeUI8(version);
sos.writeUI32(fileSize);
InflaterInputStream iis = new InflaterInputStream(fis);
int i;
while ((i = iis.read()) != -1) {
fos.write(i);
}
fis.close();
fos.close();
break;
}
case "ZWS":
{
int version = fis.read();
SWFInputStream sis = new SWFInputStream(fis, version, 4);
long fileSize = sis.readUI32();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
sis.readUI32();
int propertiesSize = 5;
byte[] lzmaProperties = new byte[propertiesSize];
if (sis.read(lzmaProperties, 0, propertiesSize) != propertiesSize) {
throw new IOException("LZMA:input .lzma file is too short");
}
SevenZip.Compression.LZMA.Decoder decoder = new SevenZip.Compression.LZMA.Decoder();
if (!decoder.SetDecoderProperties(lzmaProperties)) {
throw new IOException("LZMA:Incorrect stream properties");
}
if (!decoder.Code(sis, baos, fileSize - 8)) {
throw new IOException("LZMA:Error in data stream");
}
try (SWFOutputStream sos = new SWFOutputStream(fos, version)) {
sos.write(Utf8Helper.getBytes("FWS"));
sos.write(version);
sos.writeUI32(fileSize);
sos.write(baos.toByteArray());
}
fis.close();
fos.close();
break;
}
default:
return false;
}
} catch (IOException ex) {
return false;
}
return true;
}
public static boolean renameInvalidIdentifiers(RenameType renameType, InputStream fis, OutputStream fos) {
try {
SWF swf = new SWF(fis, Configuration.parallelSpeedUp.get());
int cnt = swf.deobfuscateIdentifiers(renameType);
swf.assignClassesToSymbols();
System.out.println(cnt + " identifiers renamed.");
swf.saveTo(fos);
} catch (InterruptedException ex) {
return false;
} catch (IOException ex) {
return false;
}
return true;
}
public boolean exportAS3Class(String className, String outdir, ExportMode exportMode, boolean parallel) throws Exception {
List abcTags = new ArrayList<>();
for (Tag t : tags) {
if (t instanceof ABCContainerTag) {
ABCContainerTag cnt = (ABCContainerTag) t;
abcTags.add(cnt);
}
}
for (int i = 0; i < abcTags.size(); i++) {
ABC abc = abcTags.get(i).getABC();
ScriptPack scr = abc.findScriptPackByPath(className);
if (scr != null) {
String cnt = "";
if (abc.script_info.length > 1) {
cnt = "script " + (i + 1) + "/" + abc.script_info.length + " ";
}
String exStr = "Exporting " + "tag " + (i + 1) + "/" + abcTags.size() + " " + cnt + scr.getPath() + " ...";
informListeners("exporting", exStr);
scr.export(outdir, abcTags, exportMode, parallel);
exStr = "Exported " + "tag " + (i + 1) + "/" + abcTags.size() + " " + cnt + scr.getPath() + " ...";
informListeners("exported", exStr);
return true;
}
}
return false;
}
private List> uniqueAS3Packs(List> packs) {
List> ret = new ArrayList<>();
for (MyEntry item : packs) {
for (MyEntry itemOld : ret) {
if (item.key.equals(itemOld.key)) {
Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, "Duplicate pack path found (" + itemOld.key + ")!");
break;
}
}
ret.add(item);
}
return ret;
}
public List> getAS3Packs() {
List abcTags = new ArrayList<>();
for (Tag t : tags) {
if (t instanceof ABCContainerTag) {
abcTags.add((ABCContainerTag) t);
}
}
List> packs = new ArrayList<>();
for (int i = 0; i < abcTags.size(); i++) {
ABCContainerTag t = abcTags.get(i);
packs.addAll(t.getABC().getScriptPacks());
}
return uniqueAS3Packs(packs);
}
private class ExportPackTask implements Callable {
ScriptPack pack;
String directory;
List abcList;
ExportMode exportMode;
ClassPath path;
AtomicInteger index;
int count;
boolean parallel;
AbortRetryIgnoreHandler handler;
long startTime;
long stopTime;
public ExportPackTask(AbortRetryIgnoreHandler handler, AtomicInteger index, int count, ClassPath path, ScriptPack pack, String directory, List abcList, ExportMode exportMode, boolean parallel) {
this.pack = pack;
this.directory = directory;
this.abcList = abcList;
this.exportMode = exportMode;
this.path = path;
this.index = index;
this.count = count;
this.parallel = parallel;
this.handler = handler;
}
@Override
public File call() throws Exception {
RunnableIOExResult rio = new RunnableIOExResult() {
@Override
public void run() throws IOException {
startTime = System.currentTimeMillis();
this.result = pack.export(directory, abcList, exportMode, parallel);
stopTime = System.currentTimeMillis();
}
};
int currentIndex = index.getAndIncrement();
synchronized (ABC.class) {
long time = stopTime - startTime;
informListeners("exporting", "Exporting script " + currentIndex + "/" + count + " " + path);
}
new RetryTask(rio, handler).run();
synchronized (ABC.class) {
long time = stopTime - startTime;
informListeners("exported", "Exported script " + currentIndex + "/" + count + " " + path + ", " + Helper.formatTimeSec(time));
}
return rio.result;
}
}
public List exportActionScript2(AbortRetryIgnoreHandler handler, String outdir, ExportMode exportMode, boolean parallel, EventListener evl) throws IOException {
List ret = new ArrayList<>();
List list2 = new ArrayList<>();
list2.addAll(tags);
List list = createASTagList(list2, null);
TagNode.setExport(list, true);
if (!outdir.endsWith(File.separator)) {
outdir += File.separator;
}
outdir += "scripts" + File.separator;
ret.addAll(TagNode.exportNodeAS(tags, handler, list, outdir, exportMode, evl));
return ret;
}
public List exportActionScript3(final AbortRetryIgnoreHandler handler, final String outdir, final ExportMode exportMode, final boolean parallel) {
final AtomicInteger cnt = new AtomicInteger(1);
final List abcTags = new ArrayList<>();
for (Tag t : tags) {
if (t instanceof ABCContainerTag) {
abcTags.add((ABCContainerTag) t);
}
}
final List ret = new ArrayList<>();
final List> packs = getAS3Packs();
if (!parallel || packs.size() < 2) {
try {
CancellableWorker.call(new Callable() {
@Override
public Void call() throws Exception {
for (MyEntry item : packs) {
ExportPackTask task = new ExportPackTask(handler, cnt, packs.size(), item.key, item.value, outdir, abcTags, exportMode, parallel);
ret.add(task.call());
}
return null;
}
}, Configuration.exportTimeout.get(), TimeUnit.SECONDS);
} catch (TimeoutException ex) {
Logger.getLogger(ABC.class.getName()).log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached", ex);
} catch (Exception ex) {
Logger.getLogger(ABC.class.getName()).log(Level.SEVERE, "Error during ABC export", ex);
}
} else {
ExecutorService executor = Executors.newFixedThreadPool(Configuration.parallelThreadCount.get());
List> futureResults = new ArrayList<>();
for (MyEntry item : packs) {
Future future = executor.submit(new ExportPackTask(handler, cnt, packs.size(), item.key, item.value, outdir, abcTags, exportMode, parallel));
futureResults.add(future);
}
try {
executor.shutdown();
if (!executor.awaitTermination(Configuration.exportTimeout.get(), TimeUnit.SECONDS)) {
Logger.getLogger(ABC.class.getName()).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.getLogger(SWF.class.getName()).log(Level.SEVERE, "Error during ABC export", ex);
}
}
}
return ret;
}
public List exportActionScript(AbortRetryIgnoreHandler handler, String outdir, ExportMode exportMode, boolean parallel) throws Exception {
boolean asV3Found = false;
List ret = new ArrayList<>();
final EventListener evl = new EventListener() {
@Override
public void handleEvent(String event, Object data) {
if (event.equals("exporting") || event.equals("exported")) {
informListeners(event, data);
}
}
};
for (Tag t : tags) {
if (t instanceof ABCContainerTag) {
asV3Found = true;
}
}
if (asV3Found) {
ret.addAll(exportActionScript3(handler, outdir, exportMode, parallel));
} else {
ret.addAll(exportActionScript2(handler, outdir, exportMode, parallel, evl));
}
return ret;
}
public static List createASTagList(List extends ContainerItem> list, Object parent) {
List ret = new ArrayList<>();
int frame = 1;
List frames = new ArrayList<>();
List exportAssetsTags = new ArrayList<>();
for (ContainerItem t : list) {
if (t instanceof ExportAssetsTag) {
exportAssetsTags.add((ExportAssetsTag) t);
}
TagNode addNode = null;
if (t instanceof ShowFrameTag) {
TagNode tti = new TagNode(new FrameNode(t.getSwf(), frame, parent, false), t.getSwf());
for (int r = ret.size() - 1; r >= 0; r--) {
if (!(ret.get(r).tag instanceof DefineSpriteTag)) {
if (!(ret.get(r).tag instanceof DefineButtonTag)) {
if (!(ret.get(r).tag instanceof DefineButton2Tag)) {
if (!(ret.get(r).tag instanceof DoInitActionTag)) {
if (!(ret.get(r).tag instanceof PackageNode)) {
tti.subItems.add(ret.get(r));
ret.remove(r);
}
}
}
}
}
}
frame++;
frames.add(tti);
} else if (t instanceof ASMSource) {
TagNode tti = new TagNode(t, t.getSwf());
//ret.add(tti);
addNode = tti;
} else if (t instanceof Container) {
if (((Container) t).getItemCount() > 0) {
TagNode tti = new TagNode(t, t.getSwf());
List subItems = ((Container) t).getSubItems();
tti.subItems = createASTagList(subItems, t);
addNode = tti;
//ret.add(tti);
}
}
if (addNode != null) {
if (addNode.tag instanceof CharacterIdTag) {
CharacterIdTag cit = (CharacterIdTag) addNode.tag;
String path = cit.getExportName();
if (path == null) {
path = "";
}
String[] pathParts;
if (path.contains(".")) {
pathParts = path.split("\\.");
} else {
pathParts = new String[]{path};
}
List items = ret;
int pos = 0;
TagNode selNode = null;
do {
if (pos == pathParts.length - 1) {
break;
}
selNode = null;
for (TagNode node : items) {
if (node.tag instanceof PackageNode) {
PackageNode pkg = (PackageNode) node.tag;
if (pkg.packageName.equals(pathParts[pos])) {
selNode = node;
break;
}
}
}
int pkgCount = 0;
for (; pkgCount < items.size(); pkgCount++) {
if (items.get(pkgCount).tag instanceof PackageNode) {
PackageNode pkg = (PackageNode) items.get(pkgCount).tag;
if (pkg.packageName.compareTo(pathParts[pos]) > 0) {
break;
}
} else {
break;
}
}
if (selNode == null) {
items.add(pkgCount, selNode = new TagNode(new PackageNode(pathParts[pos]), t.getSwf()));
}
pos++;
if (selNode != null) {
items = selNode.subItems;
}
} while (selNode != null);
int clsCount = 0;
for (; clsCount < items.size(); clsCount++) {
if (items.get(clsCount).tag instanceof CharacterIdTag) {
CharacterIdTag ct = (CharacterIdTag) items.get(clsCount).tag;
String expName = ct.getExportName();
if (expName == null) {
expName = "";
}
if (expName.contains(".")) {
expName = expName.substring(expName.lastIndexOf('.') + 1);
}
if ((ct.getClass().getName() + "_" + expName).compareTo(addNode.tag.getClass().getName() + "_" + pathParts[pos]) > 0) {
break;
}
}
}
items.add(clsCount, addNode);
} else {
ret.add(addNode);
}
}
}
ret.addAll(frames);
for (int i = ret.size() - 1; i >= 0; i--) {
if (ret.get(i).tag instanceof DefineSpriteTag) {
((DefineSpriteTag) ret.get(i).tag).exportAssetsTags = exportAssetsTags;
}
if (ret.get(i).tag instanceof DefineButtonTag) {
((DefineButtonTag) ret.get(i).tag).exportAssetsTags = exportAssetsTags;
}
if (ret.get(i).tag instanceof DefineButton2Tag) {
((DefineButton2Tag) ret.get(i).tag).exportAssetsTags = exportAssetsTags;
}
/*if (ret.get(i).tag instanceof DoInitActionTag) {
//((DoInitActionTag) ret.get(i).tag).exportAssetsTags = exportAssetsTags;
}*/
if (ret.get(i).tag instanceof ASMSource) {
ASMSource ass = (ASMSource) ret.get(i).tag;
if (ass.containsSource()) {
continue;
}
}
if (ret.get(i).subItems.isEmpty()) {
ret.remove(i);
}
}
return ret;
}
private HashSet listeners = new HashSet<>();
public final void addEventListener(EventListener listener) {
listeners.add(listener);
for (Tag t : tags) {
if (t instanceof ABCContainerTag) {
(((ABCContainerTag) t).getABC()).addEventListener(listener);
}
}
}
public final void removeEventListener(EventListener listener) {
listeners.remove(listener);
for (Tag t : tags) {
if (t instanceof ABCContainerTag) {
(((ABCContainerTag) t).getABC()).removeEventListener(listener);
}
}
}
protected void informListeners(String event, Object data) {
for (EventListener listener : listeners) {
listener.handleEvent(event, data);
}
}
public static boolean hasErrorHeader(byte[] data) {
if (data.length > 4) {
if ((data[0] & 0xff) == 0xff) {
if ((data[1] & 0xff) == 0xd9) {
if ((data[2] & 0xff) == 0xff) {
if ((data[3] & 0xff) == 0xd8) {
return true;
}
}
}
}
}
return false;
}
public static void populateSoundStreamBlocks(List extends ContainerItem> tags, Tag head, List output) {
boolean found = false;
for (ContainerItem t : tags) {
if (t == head) {
found = true;
continue;
}
if (!found) {
continue;
}
if (t instanceof SoundStreamBlockTag) {
output.add((SoundStreamBlockTag) t);
}
if (t instanceof SoundStreamHeadTypeTag) {
break;
}
if (t instanceof Container) {
populateSoundStreamBlocks(((Container) t).getSubItems(), head, output);
}
}
}
public void populateVideoFrames(int streamId, List extends ContainerItem> tags, HashMap output) {
for (ContainerItem t : tags) {
if (t instanceof VideoFrameTag) {
output.put(((VideoFrameTag) t).frameNum, (VideoFrameTag) t);
}
if (t instanceof Container) {
populateVideoFrames(streamId, ((Container) t).getSubItems(), output);
}
}
}
public void exportMovies(AbortRetryIgnoreHandler handler, String outdir) throws IOException {
exportMovies(handler, outdir, tags);
}
public void exportSounds(AbortRetryIgnoreHandler handler, String outdir, boolean mp3, boolean wave) throws IOException {
exportSounds(handler, outdir, tags, mp3, wave);
}
public byte[] exportSound(Tag t) throws IOException {
boolean mp3 = true;
boolean wave = true;
try (ByteArrayOutputStream fos = new ByteArrayOutputStream()) {
if (t instanceof DefineSoundTag) {
DefineSoundTag st = (DefineSoundTag) t;
if ((st.soundFormat == DefineSoundTag.FORMAT_ADPCM) && wave) {
createWavFromAdpcm(fos, st.soundRate, st.soundSize, st.soundType, st.soundData);
} else if ((st.soundFormat == DefineSoundTag.FORMAT_MP3) && mp3) {
fos.write(st.soundData, 2, st.soundData.length - 2);
} else {
FLVOutputStream flv = new FLVOutputStream(fos);
flv.writeHeader(true, false);
flv.writeTag(new FLVTAG(0, new AUDIODATA(st.soundFormat, st.soundRate, st.soundSize, st.soundType, st.soundData)));
}
}
if (t instanceof SoundStreamHeadTypeTag) {
SoundStreamHeadTypeTag shead = (SoundStreamHeadTypeTag) t;
List blocks = new ArrayList<>();
populateSoundStreamBlocks(this.tags, t, blocks);
if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int b = 0; b < blocks.size(); b++) {
byte[] data = blocks.get(b).getData(SWF.DEFAULT_VERSION);
baos.write(data);
}
createWavFromAdpcm(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray());
} else if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_MP3) && mp3) {
for (int b = 0; b < blocks.size(); b++) {
byte[] data = blocks.get(b).getData(SWF.DEFAULT_VERSION);
fos.write(data, 4, data.length - 4);
}
} else {
FLVOutputStream flv = new FLVOutputStream(fos);
flv.writeHeader(true, false);
int ms = (int) (1000.0f / ((float) frameRate));
for (int b = 0; b < blocks.size(); b++) {
byte[] data = blocks.get(b).getData(SWF.DEFAULT_VERSION);
if (shead.getSoundFormat() == 2) { //MP3
data = Arrays.copyOfRange(data, 4, data.length);
}
flv.writeTag(new FLVTAG(ms * b, new AUDIODATA(shead.getSoundFormat(), shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), data)));
}
}
}
return fos.toByteArray();
}
}
private static void writeLE(OutputStream os, long val, int size) throws IOException {
for (int i = 0; i < size; i++) {
os.write((int) (val & 0xff));
val >>= 8;
}
}
private static void createWavFromAdpcm(OutputStream fos, int soundRate, int soundSize, int soundType, byte[] data) throws IOException {
byte[] pcmData = AdpcmDecoder.decode(data, soundType == 1 ? true : false);
ByteArrayOutputStream subChunk1Data = new ByteArrayOutputStream();
int audioFormat = 1; //PCM
writeLE(subChunk1Data, audioFormat, 2);
int numChannels = soundType == 1 ? 2 : 1;
writeLE(subChunk1Data, numChannels, 2);
int[] rateMap = {5512, 11025, 22050, 44100};
int sampleRate = rateMap[soundRate];
writeLE(subChunk1Data, sampleRate, 4);
int bitsPerSample = soundSize == 1 ? 16 : 8;
int byteRate = sampleRate * numChannels * bitsPerSample / 8;
writeLE(subChunk1Data, byteRate, 4);
int blockAlign = numChannels * bitsPerSample / 8;
writeLE(subChunk1Data, blockAlign, 2);
writeLE(subChunk1Data, bitsPerSample, 2);
ByteArrayOutputStream chunks = new ByteArrayOutputStream();
chunks.write(Utf8Helper.getBytes("fmt "));
byte[] subChunk1DataBytes = subChunk1Data.toByteArray();
writeLE(chunks, subChunk1DataBytes.length, 4);
chunks.write(subChunk1DataBytes);
chunks.write(Utf8Helper.getBytes("data"));
writeLE(chunks, pcmData.length, 4);
chunks.write(pcmData);
fos.write(Utf8Helper.getBytes("RIFF"));
byte[] chunkBytes = chunks.toByteArray();
writeLE(fos, 4 + chunkBytes.length, 4);
fos.write(Utf8Helper.getBytes("WAVE"));
fos.write(chunkBytes);
//size1=>16bit*/
}
public List exportSounds(AbortRetryIgnoreHandler handler, String outdir, List tags, boolean mp3, boolean wave) throws IOException {
List ret = new ArrayList<>();
if (tags.isEmpty()) {
return ret;
}
File foutdir = new File(outdir);
if (!foutdir.exists()) {
if (!foutdir.mkdirs()) {
if (!foutdir.exists()) {
throw new IOException("Cannot create directory " + outdir);
}
}
}
for (Tag t : tags) {
File newfile = null;
int id = 0;
if (t instanceof DefineSoundTag) {
id = ((DefineSoundTag) t).soundId;
}
if (t instanceof DefineSoundTag) {
final DefineSoundTag st = (DefineSoundTag) t;
if ((st.soundFormat == DefineSoundTag.FORMAT_ADPCM) && wave) {
final File file = new File(outdir + File.separator + st.getCharacterExportFileName() + ".wav");
newfile = file;
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
createWavFromAdpcm(os, st.soundRate, st.soundSize, st.soundType, st.soundData);
}
}
}, handler).run();
} else if ((st.soundFormat == DefineSoundTag.FORMAT_MP3) && mp3) {
final File file = new File(outdir + File.separator + st.getCharacterExportFileName() + ".mp3");
newfile = file;
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(st.soundData, 2, st.soundData.length - 2);
}
}
}, handler).run();
} else {
final File file = new File(outdir + File.separator + st.getCharacterExportFileName() + ".flv");
newfile = file;
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
FileOutputStream fos = new FileOutputStream(file);
try (FLVOutputStream flv = new FLVOutputStream(fos)) {
flv.writeHeader(true, false);
flv.writeTag(new FLVTAG(0, new AUDIODATA(st.soundFormat, st.soundRate, st.soundSize, st.soundType, st.soundData)));
}
}
}, handler).run();
}
}
if (t instanceof SoundStreamHeadTypeTag) {
final SoundStreamHeadTypeTag shead = (SoundStreamHeadTypeTag) t;
final List blocks = new ArrayList<>();
List objs = new ArrayList<>();
objs.addAll(this.tags);
populateSoundStreamBlocks(objs, t, blocks);
if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int b = 0; b < blocks.size(); b++) {
byte data[] = blocks.get(b).getData(SWF.DEFAULT_VERSION);
baos.write(data);
}
final File file = new File(outdir + File.separator + id + ".wav");
newfile = file;
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
createWavFromAdpcm(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray());
}
}
}, handler).run();
} else if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_MP3) && mp3) {
final File file = new File(outdir + File.separator + id + ".mp3");
newfile = file;
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
for (int b = 0; b < blocks.size(); b++) {
byte data[] = blocks.get(b).getData(SWF.DEFAULT_VERSION);
os.write(data, 2, data.length - 2);
}
}
}
}, handler).run();
} else {
final File file = new File(outdir + File.separator + id + ".flv");
newfile = file;
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
FLVOutputStream flv = new FLVOutputStream(os);
flv.writeHeader(true, false);
int ms = (int) (1000.0f / ((float) frameRate));
for (int b = 0; b < blocks.size(); b++) {
byte data[] = blocks.get(b).getData(SWF.DEFAULT_VERSION);
if (shead.getSoundFormat() == 2) { //MP3
data = Arrays.copyOfRange(data, 4, data.length);
}
flv.writeTag(new FLVTAG(ms * b, new AUDIODATA(shead.getSoundFormat(), shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), data)));
}
}
}
}, handler).run();
}
}
if (newfile != null) {
ret.add(newfile);
}
}
return ret;
}
public byte[] exportMovie(DefineVideoStreamTag videoStream) throws IOException {
HashMap frames = new HashMap<>();
populateVideoFrames(videoStream.characterID, this.tags, frames);
if (frames.isEmpty()) {
return new byte[0];
}
//double ms = 1000.0f / ((float) frameRate);
ByteArrayOutputStream fos = new ByteArrayOutputStream();
//CopyOutputStream cos = new CopyOutputStream(fos, new FileInputStream("f:\\trunk\\testdata\\xfl\\xfl\\_obj\\streamvideo 7.flv"));
OutputStream tos = fos;
FLVOutputStream flv = new FLVOutputStream(tos);
flv.writeHeader(false, true);
//flv.writeTag(new FLVTAG(0, SCRIPTDATA.onMetaData(ms * frames.size() / 1000.0, videoStream.width, videoStream.height, 0, frameRate, videoStream.codecID, 0, 0, false, 0, fileSize)));
int horizontalAdjustment = 0;
int verticalAdjustment = 0;
for (int i = 0; i < frames.size(); i++) {
VideoFrameTag tag = frames.get(i);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int frameType = 1;
if ((videoStream.codecID == DefineVideoStreamTag.CODEC_VP6)
|| (videoStream.codecID == DefineVideoStreamTag.CODEC_VP6_ALPHA)) {
SWFInputStream sis = new SWFInputStream(new ByteArrayInputStream(tag.videoData), SWF.DEFAULT_VERSION);
if (videoStream.codecID == DefineVideoStreamTag.CODEC_VP6_ALPHA) {
sis.readUI24(); //offsetToAlpha
}
int frameMode = (int) sis.readUB(1);
if (frameMode == 0) {
frameType = 1; //intra
} else {
frameType = 2; //inter
}
sis.readUB(6); //qp
int marker = (int) sis.readUB(1);
if (frameMode == 0) {
int version = (int) sis.readUB(5);
int version2 = (int) sis.readUB(2);
sis.readUB(1);//interlace
if (marker == 1 || version2 == 0) {
sis.readUI16();//offset
}
int dim_y = sis.readUI8();
int dim_x = sis.readUI8();
sis.readUI8(); //render_y
sis.readUI8(); //render_x
horizontalAdjustment = (int) (dim_x * Math.ceil(((double) videoStream.width) / (double) dim_x)) - videoStream.width;
verticalAdjustment = (int) (dim_y * Math.ceil(((double) videoStream.height) / (double) dim_y)) - videoStream.height;
}
SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION);
sos.writeUB(4, horizontalAdjustment);
sos.writeUB(4, verticalAdjustment);
}
if (videoStream.codecID == DefineVideoStreamTag.CODEC_SORENSON_H263) {
SWFInputStream sis = new SWFInputStream(new ByteArrayInputStream(tag.videoData), SWF.DEFAULT_VERSION);
sis.readUB(17);//pictureStartCode
sis.readUB(5); //version
sis.readUB(8); //temporalReference
int pictureSize = (int) sis.readUB(3); //pictureSize
if (pictureSize == 0) {
sis.readUB(8); //customWidth
sis.readUB(8); //customHeight
}
if (pictureSize == 1) {
sis.readUB(16); //customWidth
sis.readUB(16); //customHeight
}
int pictureType = (int) sis.readUB(2);
switch (pictureType) {
case 0: //intra
frameType = 1; //keyframe
break;
case 1://inter
frameType = 2;
break;
case 2: //disposable
frameType = 3;
break;
}
}
baos.write(tag.videoData);
flv.writeTag(new FLVTAG((int) Math.floor(i * 1000.0f / ((float) frameRate)), new VIDEODATA(frameType, videoStream.codecID, baos.toByteArray())));
}
return fos.toByteArray();
}
public List exportMovies(AbortRetryIgnoreHandler handler, String outdir, List tags) throws IOException {
List ret = new ArrayList<>();
if (tags.isEmpty()) {
return ret;
}
File foutdir = new File(outdir);
if (!foutdir.exists()) {
if (!foutdir.mkdirs()) {
if (!foutdir.exists()) {
throw new IOException("Cannot create directory " + outdir);
}
}
}
for (Tag t : tags) {
if (t instanceof DefineVideoStreamTag) {
final DefineVideoStreamTag videoStream = (DefineVideoStreamTag) t;
final File file = new File(outdir + File.separator + ((DefineVideoStreamTag) t).getCharacterExportFileName() + ".flv");
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(exportMovie(videoStream));
}
}
}, handler).run();
}
}
return ret;
}
public List exportTexts(AbortRetryIgnoreHandler handler, String outdir, List tags, final boolean formatted) throws IOException {
List ret = new ArrayList<>();
if (tags.isEmpty()) {
return ret;
}
File foutdir = new File(outdir);
if (!foutdir.exists()) {
if (!foutdir.mkdirs()) {
if (!foutdir.exists()) {
throw new IOException("Cannot create directory " + outdir);
}
}
}
for (final Tag t : tags) {
if (t instanceof TextTag) {
final File file = new File(outdir + File.separator + ((TextTag) t).getCharacterId() + ".txt");
final List ttags = this.tags;
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
try (FileOutputStream fos = new FileOutputStream(file)) {
if (formatted) {
fos.write(Utf8Helper.getBytes(((TextTag) t).getFormattedText(ttags)));
} else {
fos.write(Utf8Helper.getBytes(((TextTag) t).getText(ttags)));
}
}
}
}, handler).run();
ret.add(file);
}
}
return ret;
}
public void exportTexts(AbortRetryIgnoreHandler handler, String outdir, boolean formatted) throws IOException {
exportTexts(handler, outdir, tags, formatted);
}
public static List exportShapes(AbortRetryIgnoreHandler handler, String outdir, List tags) throws IOException {
List ret = new ArrayList<>();
if (tags.isEmpty()) {
return ret;
}
File foutdir = new File(outdir);
if (!foutdir.exists()) {
if (!foutdir.mkdirs()) {
if (!foutdir.exists()) {
throw new IOException("Cannot create directory " + outdir);
}
}
}
loopb:
for (final Tag t : tags) {
if (t instanceof ShapeTag) {
int characterID = 0;
if (t instanceof CharacterTag) {
characterID = ((CharacterTag) t).getCharacterId();
}
final File file = new File(outdir + File.separator + characterID + ".svg");
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(Utf8Helper.getBytes(((ShapeTag) t).toSVG()));
}
}
}, handler).run();
ret.add(file);
}
}
return ret;
}
public static List exportBinaryData(AbortRetryIgnoreHandler handler, String outdir, List tags) throws IOException {
List ret = new ArrayList<>();
if (tags.isEmpty()) {
return ret;
}
File foutdir = new File(outdir);
if (!foutdir.exists()) {
if (!foutdir.mkdirs()) {
if (!foutdir.exists()) {
throw new IOException("Cannot create directory " + outdir);
}
}
}
loopb:
for (final Tag t : tags) {
if (t instanceof DefineBinaryDataTag) {
int characterID = ((DefineBinaryDataTag) t).getCharacterId();
final File file = new File(outdir + File.separator + characterID + ".bin");
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(((DefineBinaryDataTag) t).binaryData);
}
}
}, handler).run();
ret.add(file);
}
}
return ret;
}
public List exportImages(AbortRetryIgnoreHandler handler, String outdir, List tags) throws IOException {
List ret = new ArrayList<>();
if (tags.isEmpty()) {
return ret;
}
File foutdir = new File(outdir);
if (!foutdir.exists()) {
if (!foutdir.mkdirs()) {
if (!foutdir.exists()) {
throw new IOException("Cannot create directory " + outdir);
}
}
}
for (final Tag t : tags) {
if (t instanceof ImageTag) {
final File file = new File(outdir + File.separator + ((ImageTag) t).getCharacterId() + "." + ((ImageTag) t).getImageFormat());
final List ttags = this.tags;
new RetryTask(new RunnableIOEx() {
@Override
public void run() throws IOException {
ImageIO.write(((ImageTag) t).getImage(ttags), ((ImageTag) t).getImageFormat().toUpperCase(Locale.ENGLISH), file);
}
}, handler).run();
ret.add(file);
}
}
return ret;
}
public void exportImages(AbortRetryIgnoreHandler handler, String outdir) throws IOException {
exportImages(handler, outdir, tags);
}
public void exportShapes(AbortRetryIgnoreHandler handler, String outdir) throws IOException {
exportShapes(handler, outdir, tags);
}
public void exportBinaryData(AbortRetryIgnoreHandler handler, String outdir) throws IOException {
exportBinaryData(handler, outdir, tags);
}
private HashMap deobfuscated = new HashMap<>();
private List> allVariableNames = new ArrayList<>();
private List allFunctions = new ArrayList<>();
private HashMap allStrings = new HashMap<>();
private HashMap usageTypes = new HashMap<>();
private ActionDeobfuscation deobfuscation = new ActionDeobfuscation();
private static void getVariables(ConstantPool constantPool, List