chore: merge dev

This commit is contained in:
Jindra Petřík
2026-05-24 10:33:20 +02:00
committed by GitHub
106 changed files with 2280 additions and 447 deletions

View File

@@ -390,7 +390,7 @@ public class FontNormalizer {
private Set<Integer> getDefineEditTextFonts(DefineEditTextTag text) {
Set<Integer> ret = new LinkedHashSet<>();
TextStyle style = new TextStyle();
if (text.fontClass != null) {
if (text.hasFontClass) {
style.font = text.getSwf().getFontByClass(text.fontClass);
} else {
style.font = text.getSwf().getFont(text.fontId);
@@ -513,7 +513,7 @@ public class FontNormalizer {
private void scaleDefineEditTextFonts(DefineEditTextTag text, Map<Integer, Double> fontNewScale, boolean inPlace, Map<Integer, TextTag> outTexts) {
String str = "";
TextStyle style = new TextStyle();
if (text.fontClass != null) {
if (text.hasFontClass) {
style.font = text.getSwf().getFontByClass(text.fontClass);
} else {
style.font = text.getSwf().getFont(text.fontId);

View File

@@ -3165,25 +3165,22 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
}
/**
* Makes scriptpacks unique. Unique = no two packs with same classpath
* Checks scriptpacks whether they are unique. Unique = no two packs with same classpath
* exist.
*
* @param packs List of ScriptPacks
* @return List of unique ScriptPacks
*/
private List<ScriptPack> uniqueAS3Packs(List<ScriptPack> packs) {
List<ScriptPack> ret = new ArrayList<>();
private void checkUniqueAS3Packs(List<ScriptPack> packs) {
Set<ClassPath> classPaths = new HashSet<>();
for (ScriptPack item : packs) {
ClassPath key = item.getClassPath();
if (classPaths.contains(key) && item.isSimple) {
logger.log(Level.SEVERE, "Duplicate pack path found ({0})!", key);
logger.log(Level.WARNING, "Duplicate scriptpack path found ({0})!", key);
} else {
classPaths.add(key);
ret.add(item);
classPaths.add(key);
}
}
return ret;
}
}
/**
@@ -3205,7 +3202,8 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
for (ABCContainerTag abcTag : abcList) {
packs.addAll(abcTag.getABC().getScriptPacks(null, allAbcList));
}
return uniqueAS3Packs(packs);
checkUniqueAS3Packs(packs);
return packs;
}
/**

View File

@@ -2031,7 +2031,9 @@ public class AVM2Code implements Cloneable {
if (code.get(ip + plus + 3).definition instanceof SetPropertyIns) {
functionName = abc.constants.getMultiname(code.get(ip + plus + 3).operands[0]).getName(usedDeobfuscations, abc, abc.constants, fullyQualifiedNames, true, true);
localScopeStack.pop(); // with
output.remove(output.size() - 1); // with
stack.finishBlock(output);
stack.moveToStack(output);
output.remove(output.size() - 1); // with
ip = ip + plus + 4; // +1 below
}
}

View File

@@ -47,7 +47,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
/**
* Indexing of ABCs for faster access. Indexes ABC classes for faster class and
@@ -1104,6 +1103,9 @@ public final class AbcIndexing {
*/
protected void indexTraits(ABC abc, int name_index, Traits ts, Map<PropertyDef, TraitIndex> map, Map<PropertyNsDef, TraitIndex> mapNs, Map<AmbiguousPropertyDef, List<TraitIndex>> mapAmbiguous, int scriptIndex) {
for (Trait t : ts.traits) {
if (t.deleted) {
continue;
}
ValueKind propValue = null;
if (t instanceof TraitSlotConst) {
TraitSlotConst tsc = (TraitSlotConst) t;
@@ -1233,6 +1235,9 @@ public final class AbcIndexing {
indexTraits(abc, 0, abc.script_info.get(i).traits, null, scriptProperties, scriptAmbiguousProperties, i);
for (int t = 0; t < abc.script_info.get(i).traits.traits.size(); t++) {
Trait tr = abc.script_info.get(i).traits.traits.get(t);
if (tr.deleted) {
continue;
}
if (tr instanceof TraitClass) {
TraitClass tc = (TraitClass) tr;
InstanceInfo ii = abc.instance_info.get(tc.class_info);

View File

@@ -224,7 +224,7 @@ public class CallAVM2Item extends AVM2Item {
}
if (callable instanceof TypeItem && propIndex != -1 && arguments.size() == 1) {
AVM2Instruction ins = NameAVM2Item.generateCoerce(localData, generator, callable);
AVM2Instruction ins = NameAVM2Item.generateConvert(localData, generator, callable);
if (ins != null) {
return toSourceMerge(localData, generator, arguments, ins);
}

View File

@@ -352,6 +352,45 @@ public class NameAVM2Item extends AssignableAVM2Item {
}
return ins;
}
/**
* Generates convert.
* @param localData Local data
* @param generator Generator
* @param ttype Target type
* @return Convert instruction
* @throws CompilationException On compilation error
*/
public static AVM2Instruction generateConvert(SourceGeneratorLocalData localData, SourceGenerator generator, GraphTargetItem ttype) throws CompilationException {
if (ttype instanceof UnresolvedAVM2Item) {
ttype = ((UnresolvedAVM2Item) ttype).resolved;
}
AVM2Instruction ins = null;
switch (ttype.toString()) {
case "int":
ins = ins(AVM2Instructions.ConvertI);
break;
case "String":
ins = ins(AVM2Instructions.ConvertS);
break;
case "Boolean":
ins = ins(AVM2Instructions.ConvertB);
break;
case "uint":
ins = ins(AVM2Instructions.ConvertU);
break;
case "Number":
ins = ins(AVM2Instructions.ConvertD);
break;
case "float":
ins = ins(AVM2Instructions.ConvertF);
break;
case "float4":
ins = ins(AVM2Instructions.ConvertF4);
break;
}
return ins;
}
private List<GraphSourceItem> toSource(SourceGeneratorLocalData localData, SourceGenerator generator, boolean needsReturn) throws CompilationException {
addTraitUsage(localData, localData.callStack);

View File

@@ -342,8 +342,10 @@ public class PropertyAVM2Item extends AssignableAVM2Item {
boolean found = false;
String nsName = ns.getName(propValueAbc.constants).toRawString();
while (ci != null) {
DottedChain clsName = ci.abc.instance_info.get(ci.index).getName(ci.abc.constants).getNameWithNamespace(new HashSet<>(), ci.abc, ci.abc.constants, false);
String clsNsName = clsName.isTopLevel() ? clsName.getLast() : clsName.getWithoutLast().toRawString() + ":" + clsName.getLast();
DottedChain clsFullName = ci.abc.instance_info.get(ci.index).getName(ci.abc.constants).getNameWithNamespace(new HashSet<>(), ci.abc, ci.abc.constants, false);
DottedChain clsPkg = clsFullName.getWithoutLast();
String clsName = clsFullName.getLast();
String clsNsName = clsPkg.isTopLevel() ? clsName : clsPkg.toRawString() + ":" + clsName;
if (Objects.equals(nsName, clsNsName)) {
found = true;
break;

View File

@@ -433,6 +433,8 @@ public class ActionGraph extends Graph {
targetStartItem = st;
target = new DirectValueActionItem(null, null, 0, st.target, new ArrayList<>());
}
} else if (targetStart > -1) {
targetEnd = t;
}
}
if (it instanceof SetTarget2ActionItem) {
@@ -445,9 +447,15 @@ public class ActionGraph extends Graph {
targetStartItem = st;
target = st.target;
}
} else if (targetStart > -1) {
targetEnd = t;
}
}
if (it instanceof TellTargetActionItem && targetStart > -1) {
targetEnd = t;
}
if (targetStart > -1 && targetEnd > -1) {
List<GraphTargetItem> newlist = new ArrayList<>();
for (int i = 0; i < targetStart; i++) {
@@ -459,7 +467,7 @@ public class ActionGraph extends Graph {
}
newlist.add(new TellTargetActionItem(targetStartItem.getSrc(), targetStartItem.getLineStartItem(), target, tellist));
//TODO: maybe set nested flag
for (int i = targetEnd + 1; i < list.size(); i++) {
for (int i = targetEnd + (it instanceof TellTargetActionItem ? 0 : 1); i < list.size(); i++) {
newlist.add(list.get(i));
}
list.clear();
@@ -467,7 +475,7 @@ public class ActionGraph extends Graph {
targetStart = -1;
targetEnd = -1;
target = null;
t = 0;
t = -1;
}
}
for (int t = 1/*not first*/; t < list.size(); t++) {
@@ -1043,7 +1051,7 @@ public class ActionGraph extends Graph {
ActionSecondPassData spd = new ActionSecondPassData();
Set<GraphPart> processedIfs = new HashSet<>();
checkSecondPassSwitches(localData, loops, throwStates, spd.switchCases, spd.switchBreaks, processedIfs, list, spd.switchParts, spd.switchOnFalseParts, spd.switchCaseExpressions);
return spd;
}
@@ -1174,7 +1182,7 @@ public class ActionGraph extends Graph {
allSwitchParts.add(switchParts);
allSwitchOnFalseParts.add(switchOnFalseParts);
allSwitchExpressions.add(switchExpressions);
allSwitchCases.add(switchCases);
allSwitchCases.add(switchCases);
try {
allSwitchBreaks.add(getMostCommonPart(localData, switchCases, loops, throwStates, new ArrayList<>()));
} catch (InterruptedException ex) {

View File

@@ -496,57 +496,57 @@ public class FunctionActionItem extends ActionItem {
}
int regCount = 0;
if (actions != null && !actions.isEmpty()) {
localDataCopy.inFunction++;
localDataCopy.inFunction++;
for (VariableActionItem v : variables) {
String varName = v.getVariableName();
GraphTargetItem stored = v.getStoreValue();
if (needsFun2) {
if (v.isDefinition() && !registerNames.contains(varName) && !deeplyUsedVariableNames.contains(varName)
&& !hasEval) {
registerNames.add(varName);
}
for (VariableActionItem v : variables) {
String varName = v.getVariableName();
GraphTargetItem stored = v.getStoreValue();
if (needsFun2) {
if (v.isDefinition() && !registerNames.contains(varName) && !deeplyUsedVariableNames.contains(varName)
&& !hasEval) {
registerNames.add(varName);
}
}
if (registerNames.contains(varName)) {
if (stored != null) {
v.setBoxedValue(new StoreRegisterActionItem(null, null, new RegisterNumber(registerNames.indexOf(varName), varName), stored, false));
} else {
v.setBoxedValue(new DirectValueActionItem(new RegisterNumber(registerNames.indexOf(varName), varName)));
}
} else if (v.isDefinition()) {
v.setBoxedValue(new DefineLocalActionItem(null, null, ((ActionSourceGenerator) generator).pushConstTargetItem(varName), stored));
} else if (stored != null) {
v.setBoxedValue(new SetVariableActionItem(null, null, ((ActionSourceGenerator) generator).pushConstTargetItem(varName), stored));
if (registerNames.contains(varName)) {
if (stored != null) {
v.setBoxedValue(new StoreRegisterActionItem(null, null, new RegisterNumber(registerNames.indexOf(varName), varName), stored, false));
} else {
v.setBoxedValue(new GetVariableActionItem(null, null, ((ActionSourceGenerator) generator).pushConstTargetItem(varName)));
v.setBoxedValue(new DirectValueActionItem(new RegisterNumber(registerNames.indexOf(varName), varName)));
}
}
for (int i = 1 /* zero is not preloaded*/; i < registerNames.size(); i++) {
localDataCopy.registerVars.put(registerNames.get(i), i);
} else if (v.isDefinition()) {
v.setBoxedValue(new DefineLocalActionItem(null, null, ((ActionSourceGenerator) generator).pushConstTargetItem(varName), stored));
} else if (stored != null) {
v.setBoxedValue(new SetVariableActionItem(null, null, ((ActionSourceGenerator) generator).pushConstTargetItem(varName), stored));
} else {
v.setBoxedValue(new GetVariableActionItem(null, null, ((ActionSourceGenerator) generator).pushConstTargetItem(varName)));
}
}
for (int i = 1 /* zero is not preloaded*/; i < registerNames.size(); i++) {
localDataCopy.registerVars.put(registerNames.get(i), i);
}
if (actions != null && !actions.isEmpty()) {
ret.addAll(asGenerator.toActionList(asGenerator.generate(localDataCopy, actions)));
}
regCount = registerNames.size();
regCount = registerNames.size();
//some temporary registers can exceed variable+param count
for (GraphSourceItem a : ret) {
if (a instanceof ActionPush) {
ActionPush apu = (ActionPush) a;
for (Object o : apu.values) {
if (o instanceof RegisterNumber) {
RegisterNumber rn = (RegisterNumber) o;
if (rn.number >= regCount) {
regCount++;
}
//some temporary registers can exceed variable+param count
for (GraphSourceItem a : ret) {
if (a instanceof ActionPush) {
ActionPush apu = (ActionPush) a;
for (Object o : apu.values) {
if (o instanceof RegisterNumber) {
RegisterNumber rn = (RegisterNumber) o;
if (rn.number >= regCount) {
regCount++;
}
}
}
}
}
}
int len = Action.actionsToBytes(asGenerator.toActionList(ret), false, SWF.DEFAULT_VERSION).length;
if (len > 0xFFFF) {
throw new CompilationException("Function body is too large to fit into UI16.", line);

View File

@@ -188,7 +188,7 @@ public class ActionSetMember extends Action {
((GetMemberActionItem) ((DecrementActionItem) value).object).object = ((GetMemberActionItem) ((DecrementActionItem) value).object).object.getThroughDuplicate();
cleanupTemp(((GetMemberActionItem) ((DecrementActionItem) value).object).object, object, output, stack);
if (setter) {
stack.addToOutput(new PreDecrementActionItem(action, lineStartAction, ((IncrementActionItem) value).object.getThroughDuplicate()));
stack.addToOutput(new PreDecrementActionItem(action, lineStartAction, ((DecrementActionItem) value).object.getThroughDuplicate()));
} else {
stack.addToOutput(new PostDecrementActionItem(action, lineStartAction, ((DecrementActionItem) value).object.getThroughDuplicate()));
}
@@ -252,8 +252,14 @@ public class ActionSetMember extends Action {
} finally {
if (setter) {
stack.finishBlock(output);
stack.push(output.remove(output.size() - 1));
stack.moveToStack(output);
// Guard against an empty output: if the try block exited via an
// exception before producing a statement, removing from an empty
// list would throw IndexOutOfBoundsException here and mask the
// original exception. Mirrors the check used in cleanupTemp().
if (!output.isEmpty()) {
stack.push(output.remove(output.size() - 1));
stack.moveToStack(output);
}
}
}
}

View File

@@ -1237,6 +1237,14 @@ public final class Configuration {
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> useMinimumStrokeWidth1Px = null;
@ConfigurationDefaultBoolean(true)
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> showLoadingSpinner = null;
@ConfigurationDefaultString("")
@ConfigurationName("xmlExport.formats")
public static ConfigurationItem<String> lastSelectedXmlExportFormats = null;
private static Map<String, String> configurationDescriptions = new LinkedHashMap<>();
private static Map<String, String> configurationTitles = new LinkedHashMap<>();

View File

@@ -16,6 +16,7 @@
*/
package com.jpexs.decompiler.flash.configuration;
import com.jpexs.helpers.AllowedObjectInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -42,7 +43,7 @@ public class LegacyConfigurationStorage implements ConfigurationStorage {
@Override
public Map<String, Object> loadFromFile(String file) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
try (ObjectInputStream ois = new AllowedObjectInputStream(new FileInputStream(file))) {
@SuppressWarnings("unchecked")
Map<String, Object> cfg = (HashMap<String, Object>) ois.readObject();

View File

@@ -607,7 +607,9 @@ public class FrameExporter {
final Color fbackgroundColor = backgroundColor;
final boolean usesTransparency = settings.mode == FrameExportMode.PNG
|| settings.mode == FrameExportMode.GIF
|| settings.mode == FrameExportMode.WEBP;
|| settings.mode == FrameExportMode.WEBP
|| settings.mode == FrameExportMode.WEBP_ANIMATED
|| settings.mode == FrameExportMode.APNG;
final MyFrameIterator frameImages = new MyFrameIterator(tim, fframes, evl, usesTransparency, backgroundColor, settings, subFramesLength);
switch (settings.mode) {

View File

@@ -56,6 +56,41 @@ import javax.imageio.ImageIO;
* @author JPEXS
*/
public class ImageExporter {
private static ImageFormat getExportFormat(ImageTag imageTag, ImageExportSettings settings) {
ImageFormat fileFormat = imageTag.getOriginalImageFormat();
boolean hasSeparateAlpha = false;
if (imageTag instanceof HasSeparateAlphaChannel) {
HasSeparateAlphaChannel hsac = (HasSeparateAlphaChannel) imageTag;
hasSeparateAlpha = hsac.hasAlphaChannel();
}
if (settings.mode == ImageExportMode.PNG_GIF_JPEG && hasSeparateAlpha) {
fileFormat = ImageFormat.PNG;
}
if (settings.mode == ImageExportMode.PNG) {
fileFormat = ImageFormat.PNG;
}
if (settings.mode == ImageExportMode.JPEG) {
fileFormat = ImageFormat.JPEG;
}
if (settings.mode == ImageExportMode.BMP) {
fileFormat = ImageFormat.BMP;
}
if (settings.mode == ImageExportMode.WEBP) {
fileFormat = ImageFormat.WEBP;
}
return fileFormat;
}
public static String getExportExtension(ImageTag imageTag, ImageExportSettings settings) {
ImageFormat fileFormat = getExportFormat(imageTag, settings);
return ImageHelper.getImageFormatString(fileFormat);
}
public List<File> exportImages(AbortRetryIgnoreHandler handler, String outdir, ReadOnlyTagList tags, ImageExportSettings settings, EventListener evl) throws IOException, InterruptedException {
List<File> ret = new ArrayList<>();
@@ -90,31 +125,9 @@ public class ImageExporter {
final ImageTag imageTag = (ImageTag) t;
ImageFormat fileFormat = imageTag.getOriginalImageFormat();
ImageFormat originalFormat = fileFormat;
boolean hasSeparateAlpha = false;
if (imageTag instanceof HasSeparateAlphaChannel) {
HasSeparateAlphaChannel hsac = (HasSeparateAlphaChannel) imageTag;
hasSeparateAlpha = hsac.hasAlphaChannel();
}
if (settings.mode == ImageExportMode.PNG_GIF_JPEG && hasSeparateAlpha) {
fileFormat = ImageFormat.PNG;
}
if (settings.mode == ImageExportMode.PNG) {
fileFormat = ImageFormat.PNG;
}
if (settings.mode == ImageExportMode.JPEG) {
fileFormat = ImageFormat.JPEG;
}
if (settings.mode == ImageExportMode.BMP) {
fileFormat = ImageFormat.BMP;
}
if (settings.mode == ImageExportMode.WEBP) {
fileFormat = ImageFormat.WEBP;
}
ImageFormat originalFormat = imageTag.getOriginalImageFormat();
ImageFormat fileFormat = getExportFormat(imageTag, settings);
final File file = new File(outdir + File.separator + Helper.makeFileName(imageTag.getCharacterExportFileName() + "." + ImageHelper.getImageFormatString(fileFormat)));

View File

@@ -188,7 +188,7 @@ public class MorphShapeExporter {
}
m = Matrix.getScaleInstance(settings.zoom);
m.translate(-rect.Xmin, -rect.Ymin);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, settings.aaScale);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, new Matrix(), m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, settings.aaScale);
BufferedImage bim = img.getBufferedImage();
if (settings.mode == MorphShapeExportMode.PNG_START_END) {
@@ -222,7 +222,7 @@ public class MorphShapeExporter {
}
m = Matrix.getScaleInstance(settings.zoom);
m.translate(-rect.Xmin, -rect.Ymin);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, settings.aaScale);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, new Matrix(), m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, settings.aaScale);
bim = img.getBufferedImage();

View File

@@ -149,7 +149,7 @@ public class ShapeExporter {
Matrix m2 = Matrix.getScaleInstance(settings.zoom);
m2.translate(-rect.Xmin, -rect.Ymin);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m2, m2, m2, m2, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, aaScale);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m2, new Matrix(), m2, m2, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, aaScale);
BufferedImage bim = img.getBufferedImage();

View File

@@ -64,6 +64,27 @@ import java.util.Set;
*/
public class SoundExporter {
public static String getExportExtension(SoundTag soundTag, SoundExportSettings settings) {
String ext = "wav";
SoundFormat fmt = soundTag.getSoundFormat();
switch (fmt.getNativeExportFormat()) {
case MP3:
if (settings.mode.hasMP3()) {
ext = "mp3";
}
break;
case FLV:
if (settings.mode.hasFlv()) {
ext = "flv";
}
break;
}
if (settings.mode == SoundExportMode.FLV) {
ext = "flv";
}
return ext;
}
public List<File> exportSounds(AbortRetryIgnoreHandler handler, String outdir, ReadOnlyTagList tags, final SoundExportSettings settings, EventListener evl) throws IOException, InterruptedException {
List<SoundTag> sounds = new ArrayList<>();
for (Tag t : tags) {
@@ -72,7 +93,7 @@ public class SoundExporter {
}
}
return exportSounds(handler, outdir, sounds, settings, evl);
}
}
public List<File> exportSounds(AbortRetryIgnoreHandler handler, String outdir, List<SoundTag> tags, final SoundExportSettings settings, EventListener evl) throws IOException, InterruptedException {
List<File> ret = new ArrayList<>();
@@ -97,24 +118,7 @@ public class SoundExporter {
evl.handleExportingEvent("sound", currentIndex, tags.size(), st.getName());
}
String ext = ".wav";
SoundFormat fmt = st.getSoundFormat();
switch (fmt.getNativeExportFormat()) {
case MP3:
if (settings.mode.hasMP3()) {
ext = ".mp3";
}
break;
case FLV:
if (settings.mode.hasFlv()) {
ext = ".flv";
}
break;
}
if (settings.mode == SoundExportMode.FLV) {
ext = ".flv";
}
String ext = "." + getExportExtension(st, settings);
final File file = new File(outdir + File.separator + Helper.makeFileName(st.getCharacterExportFileName()) + ext);
new RetryTask(() -> {
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {

View File

@@ -402,7 +402,7 @@ public class XamlExporter {
int fontId = defineEditText.fontId;
FontTag font = null;
if (fontId == -1) {
if (defineEditText.fontClass != null) {
if (defineEditText.hasFontClass) {
font = swf.getFontByClass(defineEditText.fontClass);
fontId = swf.getCharacterId(font);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2010-2026 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.settings;
import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.exporters.modes.SoundExportMode;
/**
*
* @author JPEXS
*/
public class XmlSwfExportSettings {
public ScriptExportMode as12ExportMode;
public ImageExportMode imageExportMode;
public SoundExportMode defineSoundExportMode;
public XmlSwfExportSettings() {
}
public XmlSwfExportSettings(ScriptExportMode as12ExportMode, ImageExportMode imageExportMode, SoundExportMode defineSoundExportMode) {
if (as12ExportMode != null && as12ExportMode != ScriptExportMode.AS) {
throw new IllegalArgumentException("Unsupported script export mode");
}
this.as12ExportMode = as12ExportMode;
if (
imageExportMode != null
&& imageExportMode != ImageExportMode.PNG_GIF_JPEG
&& imageExportMode != ImageExportMode.PNG_GIF_JPEG_ALPHA
) {
throw new IllegalArgumentException("Unsupported image export mode");
}
this.imageExportMode = imageExportMode;
if (defineSoundExportMode != null && defineSoundExportMode != SoundExportMode.MP3_WAV_FLV) {
throw new IllegalArgumentException("Unsupported sound export mode");
}
this.defineSoundExportMode = defineSoundExportMode;
}
}

View File

@@ -154,11 +154,11 @@ public class AntialiasTools {
for (int i = 1; i < iPts.length; i++) {
path.lineTo(iPts[i].x / (float) FIXED_ONE, iPts[i].y / (float) FIXED_ONE);
}
if (close) {
path.closePath();
}
}
}
if (close) {
path.closePath();
}
return path;

View File

@@ -204,6 +204,7 @@ public class AntialiasedBitmapExporter extends BitmapExporter {
*
* @return Image
*/
@Override
public SerializableImage getImage() {
return image;
}

View File

@@ -16,13 +16,35 @@
*/
package com.jpexs.decompiler.flash.exporters.swf;
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
import com.jpexs.decompiler.flash.ApplicationInfo;
import com.jpexs.decompiler.flash.EventListener;
import com.jpexs.decompiler.flash.ReadOnlyTagList;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.exporters.ImageExporter;
import com.jpexs.decompiler.flash.exporters.SoundExporter;
import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.exporters.script.AS2ScriptExporter;
import com.jpexs.decompiler.flash.exporters.settings.ImageExportSettings;
import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings;
import com.jpexs.decompiler.flash.exporters.settings.SoundExportSettings;
import com.jpexs.decompiler.flash.exporters.settings.XmlSwfExportSettings;
import com.jpexs.decompiler.flash.helpers.InternalClass;
import com.jpexs.decompiler.flash.helpers.LazyObject;
import com.jpexs.decompiler.flash.tags.DefineButtonTag;
import com.jpexs.decompiler.flash.tags.DefineSoundTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.UnknownTag;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.tags.base.ButtonAction;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.base.SoundTag;
import com.jpexs.decompiler.flash.types.annotations.Conditional;
import com.jpexs.decompiler.flash.types.annotations.Internal;
import com.jpexs.decompiler.flash.types.annotations.parser.AnnotationParseException;
import com.jpexs.decompiler.flash.types.annotations.parser.ConditionEvaluator;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.ReflectionTools;
@@ -36,9 +58,14 @@ import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.stream.XMLOutputFactory;
@@ -58,10 +85,14 @@ public class SwfXmlExporter {
*/
public static final int XML_EXPORT_VERSION_MAJOR = 2;
public static final int XML_EXPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES = 3;
/**
* XML export version minor.
*
* Version 2 - export only fields that meet conditions
*/
public static final int XML_EXPORT_VERSION_MINOR = 1;
public static final int XML_EXPORT_VERSION_MINOR = 2;
private static final Logger logger = Logger.getLogger(SwfXmlExporter.class.getName());
@@ -75,16 +106,100 @@ public class SwfXmlExporter {
* @throws IOException On I/O error
*/
public void exportXml(SWF swf, File outFile) throws IOException {
exportXml(swf, outFile, new XmlSwfExportSettings(), null, new AbortRetryIgnoreHandler() {
@Override
public int handle(Throwable thrown) {
return AbortRetryIgnoreHandler.ABORT;
}
@Override
public AbortRetryIgnoreHandler getNewInstance() {
return this;
}
});
}
/**
* Exports SWF to XML.
*
* @param swf SWF to export
* @param outFile Target file to save to
* @param settings Export settings
* @param evl Event listener
* @param handler Abort/Retry/Ignore handler
*
* @throws IOException On I/O error
*/
public void exportXml(SWF swf, File outFile, XmlSwfExportSettings settings, EventListener evl, AbortRetryIgnoreHandler handler) throws IOException {
try {
File tmp = File.createTempFile("FFDEC", "XML");
String assetsDirName = outFile.getName();
if (assetsDirName.contains(".")) {
assetsDirName = assetsDirName.substring(0, assetsDirName.lastIndexOf("."));
}
assetsDirName = assetsDirName + "_assets";
Map<ASMSource, String> asmExternalFiles = new HashMap<>();
if (settings.as12ExportMode != null) {
Map<String, ASMSource> externalNameToAsm = swf.getASMs(true);
Set<String> existingNames = new HashSet<>();
for (String key : externalNameToAsm.keySet()) {
ASMSource asm = externalNameToAsm.get(key);
String currentOutDir = key + "/";
currentOutDir = new File(currentOutDir).getParentFile().toString();
currentOutDir = currentOutDir.replace("\\", "/");
if (!"/".equals(currentOutDir)) {
currentOutDir += "/";
}
String name = Helper.makeFileName(asm.getExportFileName());
int i = 1;
String baseName = name;
while (existingNames.contains(currentOutDir + name)) {
i++;
name = baseName + "_" + i;
}
existingNames.add(currentOutDir + name);
asmExternalFiles.put(asm, assetsDirName + "/scripts" + currentOutDir + name + ".as");
}
}
Map<Tag, String> tagExternalFiles = new IdentityHashMap<>();
List<Tag> imagesList = new ArrayList<>();
if (settings.imageExportMode != null) {
ImageExportSettings imageExportSetttings = new ImageExportSettings(settings.imageExportMode);
Map<Integer, CharacterTag> chars = swf.getCharacters(false);
for (int charId : chars.keySet()) {
CharacterTag ch = chars.get(charId);
if (ch instanceof ImageTag) {
ImageTag imageTag = (ImageTag) ch;
tagExternalFiles.put(imageTag, assetsDirName + "/images/" + Helper.makeFileName(imageTag.getCharacterExportFileName()) + "." + ImageExporter.getExportExtension(imageTag, imageExportSetttings));
imagesList.add(imageTag);
}
}
}
List<SoundTag> soundList = new ArrayList<>();
if (settings.defineSoundExportMode != null) {
SoundExportSettings soundExportSetttings = new SoundExportSettings(settings.defineSoundExportMode);
Map<Integer, CharacterTag> chars = swf.getCharacters(false);
for (int charId : chars.keySet()) {
CharacterTag ch = chars.get(charId);
if (ch instanceof DefineSoundTag) {
DefineSoundTag soundTag = (DefineSoundTag) ch;
tagExternalFiles.put(soundTag, assetsDirName + "/sounds/" + Helper.makeFileName(soundTag.getCharacterExportFileName()) + "." + SoundExporter.getExportExtension(soundTag, soundExportSetttings));
soundList.add(soundTag);
}
}
}
try (Writer writer = new Utf8OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(tmp)))) {
XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
xmlWriter.writeStartDocument();
xmlWriter.writeComment("\r\nWARNING: The structure of this XML is not final.\r\nIn later versions of FFDec it can be changed.\r\nMake sure you use compatible reader/writer based on _xmlExportMajor/_xmlExportMinor keys.\r\n");
exportXml(swf, xmlWriter);
exportXml(asmExternalFiles, tagExternalFiles, swf, xmlWriter);
xmlWriter.writeEndDocument();
xmlWriter.flush();
@@ -100,6 +215,27 @@ public class SwfXmlExporter {
logger.log(Level.SEVERE, "Cannot prettyformat XML");
}
tmp.delete();
if (settings.as12ExportMode != null) {
AS2ScriptExporter exporter = new AS2ScriptExporter();
exporter.exportActionScript2(swf, handler, outFile.getParentFile().toPath().resolve(assetsDirName + "/scripts").toFile().getAbsolutePath(), new ScriptExportSettings(settings.as12ExportMode, false, false, false, false), true, evl);
}
if (settings.imageExportMode != null) {
ImageExporter exporter = new ImageExporter();
try {
exporter.exportImages(handler, outFile.getParentFile().toPath().resolve(assetsDirName + "/images").toFile().getAbsolutePath(), new ReadOnlyTagList(imagesList), new ImageExportSettings(settings.imageExportMode), evl);
} catch (InterruptedException ex) {
return;
}
}
if (settings.defineSoundExportMode != null) {
SoundExporter exporter = new SoundExporter();
try {
exporter.exportSounds(handler, outFile.getParentFile().toPath().resolve(assetsDirName + "/sounds").toFile().getAbsolutePath(), soundList, new SoundExportSettings(settings.defineSoundExportMode), evl);
} catch (InterruptedException ex) {
return;
}
}
} catch (XMLStreamException ex) {
logger.log(Level.SEVERE, null, ex);
}
@@ -108,13 +244,30 @@ public class SwfXmlExporter {
/**
* Exports SWF to XML.
*
* @param asmExternalFiles ASM external files
* @param tagExternalFiles Tag external files
* @param swf SWF to export
* @param writer XML writer
* @throws IOException On I/O error
* @throws XMLStreamException On XML error
*/
public void exportXml(SWF swf, XMLStreamWriter writer) throws IOException, XMLStreamException {
generateXml(writer, "swf", swf, false);
private void exportXml(
Map<ASMSource, String> asmExternalFiles,
Map<Tag, String> tagExternalFiles,
SWF swf,
XMLStreamWriter writer
) throws IOException, XMLStreamException {
generateXml(
asmExternalFiles.isEmpty() && tagExternalFiles.isEmpty() ? XML_EXPORT_VERSION_MAJOR : XML_EXPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES,
asmExternalFiles,
tagExternalFiles,
swf,
null,
writer,
"swf",
swf,
false
);
}
public List<Field> getSwfFieldsCached(Class cls) {
@@ -167,7 +320,17 @@ public class SwfXmlExporter {
return cls != null && (cls.isArray() || List.class.isAssignableFrom(cls));
}
private void generateXml(XMLStreamWriter writer, String name, Object obj, boolean isListItem) throws XMLStreamException {
private void generateXml(
int major,
Map<ASMSource, String> asmExternalFiles,
Map<Tag, String> tagExternalFiles,
SWF swf,
Tag currentTag,
XMLStreamWriter writer,
String name,
Object obj,
boolean isListItem
) throws XMLStreamException {
Class cls = obj != null ? obj.getClass() : null;
/*if (obj != null && cls == String.class) {
@@ -215,7 +378,7 @@ public class SwfXmlExporter {
writer.writeStartElement(name);
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
generateXml(writer, "item", Array.get(value, i), true);
generateXml(major, asmExternalFiles, tagExternalFiles, swf, currentTag, writer, "item", Array.get(value, i), true);
}
writer.writeEndElement();
} else if (obj != null) {
@@ -233,9 +396,10 @@ public class SwfXmlExporter {
writer.writeStartElement(name);
if (obj instanceof SWF) {
writer.writeAttribute("_xmlExportMajor", "" + XML_EXPORT_VERSION_MAJOR);
writer.writeAttribute("_xmlExportMajor", "" + major);
writer.writeAttribute("_xmlExportMinor", "" + XML_EXPORT_VERSION_MINOR);
writer.writeAttribute("_generator", ApplicationInfo.applicationVerName);
swf = (SWF) obj;
}
writer.writeAttribute("type", clazz.getSimpleName());
@@ -247,12 +411,80 @@ public class SwfXmlExporter {
writer.writeAttribute("charset", ((SWF) obj).getCharset());
}
boolean isExternal = false;
if (obj instanceof Tag) {
currentTag = (Tag) obj;
if (tagExternalFiles.containsKey((Tag) obj)) {
writer.writeAttribute("_externalFile", tagExternalFiles.get((Tag) obj));
isExternal = true;
}
}
for (Field f : fields) {
//Multiline multilineA = f.getAnnotation(Multiline.class);
//Multiline multilineA = f.getAnnotation(Multiline.class);
if (isExternal && !"characterID".equals(f.getName()) && !"soundId".equals(f.getName())) {
continue;
}
Conditional cond = f.getAnnotation(Conditional.class);
if (cond != null) {
ConditionEvaluator ev = new ConditionEvaluator(cond);
try {
Set<String> condFields = ev.getFields();
Map<String, Boolean> fieldMap = new HashMap<>();
for (String sf : condFields) {
try {
Object value = ReflectionTools.getValue(obj, clazz.getField(sf));
if (value instanceof Boolean) {
fieldMap.put(sf, (Boolean) value);
}
if (value instanceof Integer) {
int intValue = (Integer) value;
boolean found = false;
for (int i : cond.options()) {
if (i == intValue) {
found = true;
}
}
fieldMap.put(sf, found);
}
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
fieldMap.put(sf, true);
}
}
if (!ev.eval(fieldMap, currentTag.getId())) {
continue;
}
} catch (AnnotationParseException ex) {
logger.log(Level.SEVERE, null, ex);
}
}
try {
f.setAccessible(true);
generateXml(writer, f.getName(), f.get(obj), false);
Object value = f.get(obj);
if ("actionBytes".equals(f.getName())) {
if (obj instanceof ASMSource && asmExternalFiles.containsKey((ASMSource) obj)) {
value = new ByteArrayRange("00");
writer.writeAttribute("_externalActions", asmExternalFiles.get((ASMSource) obj));
} else if (obj instanceof DefineButtonTag) {
for (ASMSource s : asmExternalFiles.keySet()) {
if (s instanceof ButtonAction) {
ButtonAction ba = (ButtonAction) s;
if (ba.getSourceTag() == obj) {
value = new ByteArrayRange("00");
writer.writeAttribute("_externalActions", asmExternalFiles.get(s));
break;
}
}
}
}
}
generateXml(major, asmExternalFiles, tagExternalFiles, swf, currentTag, writer, f.getName(), value, false);
} catch (IllegalArgumentException | IllegalAccessException ex) {
logger.log(Level.SEVERE, null, ex);
}

View File

@@ -44,7 +44,6 @@ public class AS2ScriptImporter {
private static final Logger logger = Logger.getLogger(AS2ScriptImporter.class.getName());
/**
* Constructor.
*/
@@ -52,8 +51,58 @@ public class AS2ScriptImporter {
}
/**
* Imports actionScript 1/2 (not P-code) from given file
*
* @param fileName File to import
* @param asm Target to import into
* @param listener Import listener
* @return True on success
* @throws InterruptedException
*/
public boolean importActionScript(String fileName, ASMSource asm, ScriptImporterProgressListener listener) throws InterruptedException {
asm.getSwf().informListeners("importing_as", fileName);
String txt = Helper.readTextFile(fileName);
ActionScript2Parser par = new ActionScript2Parser(asm.getSwf(), asm);
boolean errored = false;
try {
asm.setActions(par.actionsFromString(txt, asm.getSwf().getCharset()));
} catch (ValueTooLargeException ex) {
logger.log(Level.SEVERE, "Script or some of its functions are too large, file: {0}", fileName);
errored = true;
} catch (ActionParseException ex) {
logger.log(Level.SEVERE, "%error% on line %line%, file: %file%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)).replace("%file%", fileName), ex);
errored = true;
} catch (CompilationException ex) {
logger.log(Level.SEVERE, "%error% on line %line%, file: %file%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)).replace("%file%", fileName), ex);
errored = true;
} catch (IOException ex) {
logger.log(Level.SEVERE, "error during script import, file: %file%".replace("%file%", fileName), ex);
errored = true;
} catch (InterruptedException ex) {
throw ex;
} catch (Exception ex) {
logger.log(Level.SEVERE, "error during script import, file: %file%".replace("%file%", fileName), ex);
errored = true;
}
if (!errored) {
asm.setModified();
if (listener != null) {
listener.scriptImported();
}
} else {
if (listener != null) {
listener.scriptImportError();
}
}
return !errored;
}
/**
* Imports scripts from given folder.
*
* @param scriptsFolder Folder with scripts
* @param asms Map of ASMSource objects
* @return Number of imported scripts
@@ -65,6 +114,7 @@ public class AS2ScriptImporter {
/**
* Imports scripts from given folder.
*
* @param scriptsFolder Folder with scripts
* @param asms Map of ASMSource objects
* @param listener Progress listener
@@ -104,42 +154,12 @@ public class AS2ScriptImporter {
String fileName = Path.combine(currentOutDir, name) + ".as";
if (new File(fileName).exists()) {
asm.getSwf().informListeners("importing_as", fileName);
String txt = Helper.readTextFile(fileName);
ActionScript2Parser par = new ActionScript2Parser(asm.getSwf(), asm);
boolean errored = false;
try {
asm.setActions(par.actionsFromString(txt, asm.getSwf().getCharset()));
} catch (ValueTooLargeException ex) {
logger.log(Level.SEVERE, "Script or some of its functions are too large, file: {0}", fileName);
errored = true;
} catch (ActionParseException ex) {
logger.log(Level.SEVERE, "%error% on line %line%, file: %file%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)).replace("%file%", fileName), ex);
errored = true;
} catch (CompilationException ex) {
logger.log(Level.SEVERE, "%error% on line %line%, file: %file%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)).replace("%file%", fileName), ex);
errored = true;
} catch (IOException ex) {
logger.log(Level.SEVERE, "error during script import, file: %file%".replace("%file%", fileName), ex);
errored = true;
if (importActionScript(fileName, asm, listener)) {
importCount++;
}
} catch (InterruptedException ex) {
return importCount;
} catch (Exception ex) {
logger.log(Level.SEVERE, "error during script import, file: %file%".replace("%file%", fileName), ex);
errored = true;
}
if (!errored) {
asm.setModified();
importCount++;
if (listener != null) {
listener.scriptImported();
}
} else {
if (listener != null) {
listener.scriptImportError();
}
}
}

View File

@@ -37,11 +37,17 @@ import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter;
import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst;
import com.jpexs.decompiler.flash.abc.types.traits.Traits;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.exporters.swf.SwfXmlExporter;
import com.jpexs.decompiler.flash.tags.CSMSettingsTag;
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.Tag;
import com.jpexs.decompiler.flash.tags.TagTypeInfo;
import com.jpexs.decompiler.flash.tags.UnknownTag;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.base.SoundImportException;
import com.jpexs.decompiler.flash.types.ALPHABITMAPDATA;
import com.jpexs.decompiler.flash.types.ALPHACOLORMAPDATA;
import com.jpexs.decompiler.flash.types.ARGB;
@@ -108,12 +114,16 @@ import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.decompiler.flash.types.sound.SoundFormat;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.HashArrayList;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.IdentityKey;
import com.jpexs.helpers.ReflectionTools;
import com.jpexs.helpers.utf8.Utf8InputStreamReader;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
@@ -124,7 +134,9 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -145,7 +157,12 @@ public class SwfXmlImporter {
/**
* Maximum XML import version major.
*/
public static final int MAX_XML_IMPORT_VERSION_MAJOR = 2;
public static final int MAX_XML_IMPORT_VERSION_MAJOR = 3;
/**
* Minimum version for using external files - attributes _externalActions, _externalFile
*/
public static final int XML_IMPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES = 3;
private static final Logger logger = Logger.getLogger(SwfXmlImporter.class.getName());
@@ -221,11 +238,15 @@ public class SwfXmlImporter {
* Imports SWF from input stream.
* @param swf SWF object
* @param in Input stream
* @param directory Directory where XML resides for external files resolving
* @throws IOException On I/O error
*/
public void importSwf(SWF swf, InputStream in) throws IOException {
public void importSwf(SWF swf, InputStream in, File directory) throws IOException {
XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
Map<IdentityKey<Object>, String> asmExternalActions = new LinkedHashMap<>();
Map<IdentityKey<Tag>, String> tagExternalFiles = new LinkedHashMap<>();
try {
try (Reader reader = new Utf8InputStreamReader(new BufferedInputStream(in))) {
XMLStreamReader xmlReader = xmlFactory.createXMLStreamReader(reader);
@@ -233,7 +254,7 @@ public class SwfXmlImporter {
xmlReader.nextTag();
xmlReader.require(XMLStreamConstants.START_ELEMENT, null, "swf");
processElement(xmlReader, swf, swf, null, MAX_XML_IMPORT_VERSION_MAJOR);
processElement(xmlReader, swf, swf, null, MAX_XML_IMPORT_VERSION_MAJOR, asmExternalActions, tagExternalFiles);
}
swf.clearAllCache();
@@ -241,16 +262,78 @@ public class SwfXmlImporter {
} catch (XMLStreamException ex) {
logger.log(Level.SEVERE, null, ex);
}
if (!asmExternalActions.isEmpty()) {
for (IdentityKey<Object> objKey : asmExternalActions.keySet()) {
ASMSource asm = null;
String fileName = asmExternalActions.get(objKey);
Object obj = objKey.get();
if (obj instanceof ASMSource) {
asm = (ASMSource) obj;
}
if (obj instanceof DefineButtonTag) {
DefineButtonTag defineButton = (DefineButtonTag) obj;
asm = defineButton.getSubItems().get(0);
}
if (asm != null) {
AS2ScriptImporter importer = new AS2ScriptImporter();
try {
importer.importActionScript(directory.toPath().resolve(fileName).toFile().getAbsolutePath(), asm, null);
} catch (InterruptedException ex) {
break;
}
}
}
}
if (!tagExternalFiles.isEmpty()) {
for (IdentityKey<Tag> tagKey : tagExternalFiles.keySet()) {
String fileName = tagExternalFiles.get(tagKey);
Tag tag = tagKey.get();
if (tag == null) {
continue;
}
if (tag instanceof ImageTag) {
ImageTag imageTag = (ImageTag) tag;
ImageImporter importer = new ImageImporter();
importer.importImage(imageTag, Helper.readFile(directory.toPath().resolve(fileName).toFile().getAbsolutePath()), -1);
String baseName = new File(fileName).getName();
if (baseName.contains(".")) {
baseName = baseName.substring(0, baseName.lastIndexOf("."));
}
String alphaFile = new File(fileName).getParentFile().getAbsolutePath() + "/" + baseName + ".alpha.png";
if (new File(alphaFile).exists()) {
importer.importImageAlpha(imageTag, Helper.readFile(alphaFile));
}
} else if (tag instanceof DefineSoundTag) {
DefineSoundTag defineSoundTag = (DefineSoundTag) tag;
SoundImporter importer = new SoundImporter();
int format = SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN;
if (fileName.toLowerCase(Locale.ENGLISH).endsWith(".mp3")) {
format = SoundFormat.FORMAT_MP3;
}
try (FileInputStream fis = new FileInputStream(directory.toPath().resolve(fileName).toFile().getAbsolutePath())) {
importer.importDefineSound(defineSoundTag, fis, format);
} catch (SoundImportException ex) {
logger.log(Level.SEVERE, "Cannot import sound", ex);
} catch (IOException ex) {
logger.log(Level.SEVERE, "Cannot read sound", ex);
}
} else {
logger.log(Level.WARNING, "Unrecognized tag type for external file: {0}", tag.getTagName());
}
}
}
}
private void setSwfAndTimelined(SWF swf) {
for (Tag t : swf.getTags()) {
t.setSwf(swf);
t.setSwf(swf, true);
t.setTimelined(swf);
if (t instanceof DefineSpriteTag) {
DefineSpriteTag s = (DefineSpriteTag) t;
for (Tag st : s.getTags()) {
st.setSwf(swf);
st.setSwf(swf, true);
st.setTimelined(s);
}
}
@@ -269,7 +352,7 @@ public class SwfXmlImporter {
XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
try {
XMLStreamReader reader = xmlFactory.createXMLStreamReader(new StringReader(xml));
return processObject(reader, requiredType, swf, null, 1);
return processObject(reader, requiredType, swf, null, 1, new HashMap<>(), new HashMap<>());
} catch (IllegalArgumentException | IllegalAccessException | NoSuchMethodException | InstantiationException
| InvocationTargetException | XMLStreamException ex) {
Logger.getLogger(SwfXmlImporter.class.getName()).log(Level.SEVERE, null, ex);
@@ -311,7 +394,7 @@ public class SwfXmlImporter {
}*/
}
private void processElement(XMLStreamReader reader, Object obj, SWF swf, Tag tag, int xmlExportMajor) throws XMLStreamException {
private void processElement(XMLStreamReader reader, Object obj, SWF swf, Tag tag, int xmlExportMajor, Map<IdentityKey<Object>, String> asmExternalActions, Map<IdentityKey<Tag>, String> tagExternalFiles) throws XMLStreamException {
// Check if element started and start if needed
if (!reader.isStartElement()) {
reader.nextTag();
@@ -369,6 +452,30 @@ public class SwfXmlImporter {
if (name.equals("reserved3") && "FileAttributesTag".equals(attributes.get("type"))) {
name = "reservedB";
}
if (name.equals("_externalActions")) {
if (xmlExportMajor < XML_IMPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES) {
logger.log(Level.WARNING, "For _externalActions attribute _xmlExportMajor must be >= 3. The attribute is ignored.");
continue;
}
asmExternalActions.put(new IdentityKey<>(obj), val);
continue;
}
if (obj instanceof Tag && name.equals("_externalFile")) {
if (xmlExportMajor < XML_IMPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES) {
logger.log(Level.WARNING, "For _externalFile attribute _xmlExportMajor must be >= 3. The attribute is ignored.");
continue;
}
tagExternalFiles.put(new IdentityKey<>((Tag) obj), val);
continue;
}
if (name.equals("actionBytes") && attributes.containsKey("_externalActions")) {
if (xmlExportMajor >= XML_IMPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES) {
continue;
}
}
if (!name.equals("type")) {
try {
@@ -397,7 +504,7 @@ public class SwfXmlImporter {
// Check for list item elements
reader.nextTag();
while (reader.isStartElement()) {
Object childObj = processObject(reader, reqType, swf, tag, xmlExportMajor);
Object childObj = processObject(reader, reqType, swf, tag, xmlExportMajor, asmExternalActions, tagExternalFiles);
list.add(childObj);
reader.nextTag();
@@ -414,7 +521,7 @@ public class SwfXmlImporter {
setFieldValue(field, obj, value);
} else {
Object childObj = processObject(reader, null, swf, tag, xmlExportMajor);
Object childObj = processObject(reader, null, swf, tag, xmlExportMajor, asmExternalActions, tagExternalFiles);
setFieldValue(field, obj, childObj);
}
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException
@@ -432,7 +539,7 @@ public class SwfXmlImporter {
}
}
private Object processObject(XMLStreamReader reader, Class requiredType, SWF swf, Tag tag, int xmlExportMajor) throws IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, XMLStreamException {
private Object processObject(XMLStreamReader reader, Class requiredType, SWF swf, Tag tag, int xmlExportMajor, Map<IdentityKey<Object>, String> asmExternalActions, Map<IdentityKey<Tag>, String> tagExternalFiles) throws IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, XMLStreamException {
// Check if element started and start if needed
if (!reader.isStartElement()) {
reader.nextTag();
@@ -465,7 +572,7 @@ public class SwfXmlImporter {
tag = (Tag) childObj;
}
processElement(reader, childObj, swf, tag, xmlExportMajor);
processElement(reader, childObj, swf, tag, xmlExportMajor, asmExternalActions, tagExternalFiles);
ret = childObj;
} else {
String isNullAttr = attributes.get("isNull");

View File

@@ -63,3 +63,6 @@ configurationFile.configuration = B\u00f6l\u00fcm - Ger\u00e7ek yap\u0131land\u0
configuration.removed = UYARI: Bu yap\u0131land\u0131rma KALDIRILDI. Kullan\u0131lm\u0131yor.
#after 24.0.1
decompilationWarning.as2.noUninitializedClassFieldsDetection = UYARI: Bu s\u0131n\u0131f, ba\u015flat\u0131lmam\u0131\u015f s\u0131n\u0131f alanlar\u0131 alg\u0131lanmadan derlendi.
decompilationWarning.obfuscatedIdentifiers = UYARI: Orijinal kodda gizlenmi\u015f tan\u0131mlay\u0131c\u0131lar bulunmaktad\u0131r.
decompilationWarning.replacementsFollow = De\u011fi\u015ftirilen \u00f6\u011felerin listesi a\u015fa\u011f\u0131dad\u0131r:
frame.withoutShowFrame = ShowFrame olmadan

View File

@@ -24,6 +24,7 @@ import com.jpexs.decompiler.flash.abc.ScriptPack;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.flash.treeitems.Openable;
import com.jpexs.decompiler.graph.DottedChain;
import com.jpexs.helpers.AllowedObjectInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
@@ -96,7 +97,7 @@ public class ABCSearchResult implements Serializable, ScriptSearchResult {
*/
@SuppressWarnings("unchecked")
public ABCSearchResult(Openable openable, InputStream is) throws IOException, ScriptNotFoundException {
ObjectInputStream ois = new ObjectInputStream(is);
ObjectInputStream ois = new AllowedObjectInputStream(is);
int versionMajor = ois.read();
ois.read(); //minor
if (versionMajor == 1) {

View File

@@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.search;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.treeitems.Openable;
import com.jpexs.helpers.AllowedObjectInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
@@ -52,7 +53,7 @@ public class ActionSearchResult implements ScriptSearchResult {
*/
public ActionSearchResult(SWF swf, InputStream is) throws IOException, ScriptNotFoundException {
Map<String, ASMSource> asms = swf.getASMs(false);
ObjectInputStream ois = new ObjectInputStream(is);
ObjectInputStream ois = new AllowedObjectInputStream(is);
int versionMajor = ois.read();
ois.read(); //minor
if (versionMajor != SERIAL_VERSION_MAJOR) {

View File

@@ -377,4 +377,17 @@ public class DefineButton2Tag extends ButtonTag implements ASMSourceContainer {
needed.add(rec.characterId);
}
}
@Override
public void setSwf(SWF swf, boolean deep) {
super.setSwf(swf, deep);
if (deep) {
if (actions != null) {
for (BUTTONCONDACTION action : actions) {
action.setSourceTag(this);
}
}
}
}
}

View File

@@ -329,5 +329,5 @@ public class DefineButtonTag extends ButtonTag implements ASMSourceContainer {
for (BUTTONRECORD rec : characters) {
needed.add(rec.characterId);
}
}
}
}

View File

@@ -538,7 +538,7 @@ public class DefineEditTextTag extends TextTag {
}
String str = "";
TextStyle style = new TextStyle();
if (fontClass != null) {
if (hasFontClass) {
style.font = swf.getFontByClass(fontClass);
} else {
style.font = swf.getFont(fontId);
@@ -1355,15 +1355,22 @@ public class DefineEditTextTag extends TextTag {
}
if (hasText) {
List<TEXTRECORD> allTextRecords = getTextRecords(swf, normalizedFonts);
MATRIX textMatrix = new MATRIX();
int borderPadding = 40;
textMatrix.translateX = bounds.Xmin + borderPadding;
textMatrix.translateY = bounds.Ymin + borderPadding;
switch (renderMode) {
case BITMAP:
staticTextToImage(swf, allTextRecords, 2, image, getTextMatrix(), transformation, colorTransform, selectionStart, selectionEnd, aaScale);
staticTextToImage(swf, allTextRecords, 2, image, textMatrix, transformation, colorTransform, selectionStart, selectionEnd, aaScale);
break;
case HTML5_CANVAS:
staticTextToHtmlCanvas(zoom, swf, allTextRecords, 2, htmlCanvasBuilder, getBounds(), getTextMatrix(), colorTransform);
staticTextToHtmlCanvas(zoom, swf, allTextRecords, 2, htmlCanvasBuilder, getBounds(), textMatrix, colorTransform);
break;
case SVG:
staticTextToSVG(swf, allTextRecords, 2, svgExporter, getBounds(), getTextMatrix(), colorTransform, zoom, transformation);
staticTextToSVG(swf, allTextRecords, 2, svgExporter, getBounds(), textMatrix, colorTransform, zoom, transformation);
break;
}
}
@@ -1607,8 +1614,8 @@ public class DefineEditTextTag extends TextTag {
for (SameStyleTextRecord tr : line) {
AdvancedTextRecord tr2 = new AdvancedTextRecord();
int fid = fontId;
if (fontClass != null) {
tr2.fontClass = fontClass;
if (hasFontClass) {
tr2.fontClass = fontClass; //FIXME?
}
if (tr.style.font != null) {
fid = swf.getCharacterId(tr.style.font);

View File

@@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.types.CLIPACTIONRECORD;
import com.jpexs.decompiler.flash.types.CLIPACTIONS;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.MATRIX;
@@ -361,4 +362,19 @@ public abstract class PlaceObjectTypeTag extends Tag implements CharacterIdTag,
result += "_" + getDepth();
return result;
}
@Override
public void setSwf(SWF swf, boolean deep) {
super.setSwf(swf, deep);
if (deep) {
CLIPACTIONS clipActions = getClipActions();
if (clipActions != null) {
for (CLIPACTIONRECORD rec : clipActions.clipActionRecords) {
rec.setParentClipActions(clipActions);
rec.setSourceTag(this);
}
}
}
}
}

View File

@@ -17,6 +17,7 @@ import com.jpexs.decompiler.flash.types.RGB;
import com.jpexs.decompiler.flash.types.RGBA;
import com.jpexs.decompiler.flash.types.TEXTRECORD;
import com.jpexs.decompiler.flash.xfl.XFLXmlWriter;
import java.awt.Color;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
@@ -99,6 +100,12 @@ public class TextTypeConverter {
throw new IllegalArgumentException("defineTextVersion should be either 1 or 2");
}
List<TEXTRECORD> records = tag.getTextRecords(tag.getSwf(), new HashMap<>());
int borderPadding = 40;
int moveX = tag.bounds.Xmin + borderPadding;
int moveY = tag.bounds.Ymin + borderPadding;
boolean first = true;
for (TEXTRECORD rec : records) {
if (defineTextVersion == 1 && rec.textColorA != null) {
rec.textColor = new RGB(rec.textColorA);
@@ -107,8 +114,24 @@ public class TextTypeConverter {
if (defineTextVersion == 2 && rec.textColor != null) {
rec.textColorA = new RGBA(rec.textColor);
rec.textColor = null;
}
}
if (first) {
rec.styleFlagsHasXOffset = true;
rec.xOffset += moveX;
rec.styleFlagsHasYOffset = true;
rec.yOffset += moveY;
first = false;
} else {
if (rec.styleFlagsHasXOffset) {
rec.xOffset += moveX;
}
if (rec.styleFlagsHasYOffset) {
rec.yOffset += moveY;
}
}
}
ret.textRecords = records;
ret.textMatrix = new MATRIX();
ExportRectangle bounds = ret.calculateTextBounds();
@@ -132,20 +155,27 @@ public class TextTypeConverter {
List<Integer> leftMargins = (List<Integer>) attrs.get("allLeftMargins");
@SuppressWarnings("unchecked")
List<Integer> letterSpacings = (List<Integer>) attrs.get("allLetterSpacings");
int leftMargin = leftMargins.isEmpty() ? 0 : leftMargins.get(0);
det.bounds = new RECT(tag.getBounds());
det.wasStatic = true;
det.noSelect = true;
det.useOutlines = true;
det.multiline = true;
det.hasLayout = true;
det.align = DefineEditTextTag.ALIGN_LEFT;
det.indent = (int) attrs.get("indent");
det.leftMargin = leftMargins.isEmpty() ? 0 : leftMargins.get(0);
det.leftMargin = 0;
det.leading = (int) attrs.get("lineSpacing");
det.rightMargin = (int) attrs.get("rightMargin");
XFLXmlWriter writer = new XFLXmlWriter();
writer.setMakeNewLines(false);
RGBA firstTextColor = new RGBA(Color.BLACK);
int firstFontId = -1;
int firstTextHeight = -1;
try {
int fontId;
FontTag font = null;
@@ -160,10 +190,16 @@ public class TextTypeConverter {
for (int r = 0; r < textRecords.size(); r++) {
TEXTRECORD rec = textRecords.get(r);
if (rec.styleFlagsHasColor) {
RGBA newTextColor;
if (tag instanceof DefineTextTag) {
textColor = rec.textColor;
newTextColor = new RGBA(textColor);
} else {
textColorA = rec.textColorA;
newTextColor = rec.textColorA;
}
if (r == 0) {
firstTextColor = newTextColor;
}
}
if (rec.styleFlagsHasFont) {
@@ -182,6 +218,10 @@ public class TextTypeConverter {
if (fontName == null) {
fontName = FontTag.getDefaultFontName();
}
if (r == 0) {
firstFontId = fontId;
firstTextHeight = textHeight;
}
}
newline = false;
if (!firstRun && rec.styleFlagsHasYOffset) {
@@ -190,6 +230,7 @@ public class TextTypeConverter {
firstRun = false;
if (font != null) {
writer.writeStartElement("p");
writer.writeAttribute("align", "left");
writer.writeStartElement("font");
writer.writeAttribute("face", fontName);
writer.writeAttribute("size", doubleToString(twipToPixel(textHeight)));
@@ -225,9 +266,16 @@ public class TextTypeConverter {
det.html = true;
det.hasText = true;
det.initialText = writer.toString();
det.hasTextColor = true;
det.textColor = firstTextColor;
if (firstFontId > -1) {
det.hasFont = true;
det.fontId = firstFontId;
det.fontHeight = firstTextHeight;
}
ExportRectangle bounds = det.calculateTextBounds();
det.bounds = new RECT((int) Math.round(bounds.xMin), (int) Math.round(bounds.xMax), (int) Math.round(bounds.yMin), (int) Math.round(bounds.yMax));
det.bounds = new RECT((int) Math.round(bounds.xMin + leftMargin), (int) Math.round(bounds.xMax + leftMargin), (int) Math.round(bounds.yMin), (int) Math.round(bounds.yMax));
return det;
}

View File

@@ -273,10 +273,15 @@ public class AS3Package extends AS3ClassTreeItem {
*
* @param script ScriptPack
*/
public void addScriptPack(ScriptPack script) {
/*ClassPath cp = script.getClassPath();
scripts.put(cp.className + cp.namespaceSuffix, script);*/
scripts.put(script.getPrintableNameWithNamespaceSuffix(), script);
public void addScriptPack(ScriptPack script) {
int i = 1;
String baseKey = script.getPrintableNameWithNamespaceSuffix();
String key = baseKey;
while (scripts.containsKey(key)) {
i++;
key = baseKey + i;
}
scripts.put(key, script);
sortedScripts = null;
}

View File

@@ -120,7 +120,7 @@ public class BUTTONRECORD implements Serializable, TreeItem, HasSwfAndTag, HasCh
* If within DefineButton2Tag and buttonHasBlendMode: Blend mode
*/
@SWFType(BasicType.UI8)
@Conditional(value = {"buttonHasBlendMode"}, tags = {DefineButton2Tag.ID})
@Conditional(value = "buttonHasBlendMode", tags = {DefineButton2Tag.ID})
@EnumValue(value = 0, text = "normal")
@EnumValue(value = BlendMode.NORMAL, text = "normal")
@EnumValue(value = BlendMode.LAYER, text = "layer")

View File

@@ -24,21 +24,20 @@ import com.jpexs.flash.fla.converter.FlaFormatVersion;
* @author JPEXS
*/
public enum FLAVersion {
F5("F5", "Flash 5", FlaFormatVersion.F5, null, 5),
MX("MX", "Flash MX", FlaFormatVersion.MX, null, 6),
MX2004("MX2004", "Flash MX 2004", FlaFormatVersion.MX2004, null, 7),
F8("F8", "Flash 8", FlaFormatVersion.F8, null, 8),
CS3("CS3", "Flash CS 3", FlaFormatVersion.CS3, null, 9),
CS4("CS4", "Flash CS 4", FlaFormatVersion.CS4, null, 10),
CS5("CS5", "Flash CS 5", null, "2.0", 10),
CS5_5("CS5.5", "Flash CS 5.5", null, "2.1", 11),
CS6("CS6", "Flash CS 6", null, "2.2", 17),
CC("CC", "Flash CC", null, "2.4", Integer.MAX_VALUE) {
@Override
public int minASVersion() {
return 3; //AS 1/2 not supported anymore
}
};
F1("F1", "FutureSplash Animator", FlaFormatVersion.F1, null, 1, 1, 1),
F2("F2", "Macromedia Flash 2", FlaFormatVersion.F2, null, 2, 1, 1),
F3("F3", "Macromedia Flash 3", FlaFormatVersion.F3, null, 3, 1, 1),
F4("F4", "Macromedia Flash 4", FlaFormatVersion.F4, null, 4, 1, 1),
F5("F5", "Macromedia Flash 5", FlaFormatVersion.F5, null, 5, 1, 1),
MX("MX", "Macromedia Flash MX", FlaFormatVersion.MX, null, 6, 1, 1),
MX2004("MX2004", "Macromedia Flash MX 2004", FlaFormatVersion.MX2004, null, 7, 1, 2),
F8("F8", "Macromedia Flash 8", FlaFormatVersion.F8, null, 8, 1, 2),
CS3("CS3", "Adobe Flash Professional CS 3", FlaFormatVersion.CS3, null, 9, 1, 3),
CS4("CS4", "Adobe Flash Professional CS 4", FlaFormatVersion.CS4, null, 10, 1, 3),
CS5("CS5", "Adobe Flash Professional CS 5", null, "2.0", 10, 1, 3),
CS5_5("CS5.5", "Adobe Flash Professional CS 5.5", null, "2.1", 11, 1, 3),
CS6("CS6", "Adobe Flash Professional CS 6", null, "2.2", 17, 1, 3),
CC("CC", "Adobe Flash Professional CC", null, "2.4", Integer.MAX_VALUE, 3, 3);
private final FlaFormatVersion cfbFlaVersion;
private final String xflVersion;
@@ -48,13 +47,19 @@ public enum FLAVersion {
private final String applicationName;
private final int maxSwfVersion;
private final int minASVersion;
private FLAVersion(String shortName, String applicationName, FlaFormatVersion cfbFlaVersion, String xflVersion, int maxSwfVersion) {
private final int maxASVersion;
private FLAVersion(String shortName, String applicationName, FlaFormatVersion cfbFlaVersion, String xflVersion, int maxSwfVersion, int minASVersion, int maxASVersion) {
this.cfbFlaVersion = cfbFlaVersion;
this.xflVersion = xflVersion;
this.shortName = shortName;
this.applicationName = applicationName;
this.maxSwfVersion = maxSwfVersion;
this.minASVersion = minASVersion;
this.maxASVersion = maxASVersion;
}
public FlaFormatVersion getCfbFlaVersion() {
@@ -70,7 +75,11 @@ public enum FLAVersion {
}
public int minASVersion() {
return 1;
return minASVersion;
}
public int maxASVersion() {
return maxASVersion;
}
public String applicationName() {

View File

@@ -34,6 +34,19 @@ import javax.imageio.ImageIO;
*/
public class LosslessImageBinDataReader {
/*
Major versions:
0 = basic uncompressed, no alpha
1 = ?? - not encountered
2 = has compression (variant field)
3 = adds alpha channel (flags field)
Minor version: always 5
*/
private final DataInputStream is;
public LosslessImageBinDataReader(InputStream is) {
@@ -41,9 +54,9 @@ public class LosslessImageBinDataReader {
}
public BufferedImage readImage() throws IOException {
int sign1 = readEx();
int sign2 = readEx();
if (sign1 != 0x03 || sign2 != 0x05) {
int major = readEx();
int minor = readEx();
if (major > 3 || minor != 0x05) {
throw new IOException("Invalid image");
}
int rowSize = readUI16();
@@ -53,29 +66,38 @@ public class LosslessImageBinDataReader {
long frameRight = readUI32();
long frameTop = readUI32();
long frameBottom = readUI32();
int flags = readEx();
boolean hasAlpha = (flags & 1) == 1;
boolean hasAlpha = false;
if (major >= 3) {
int flags = readEx();
hasAlpha = (flags & 1) == 1;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int variant = readEx();
if (variant == 1) { //compressed
while (true) {
int chunkLen = readUI16();
if (chunkLen == 0) {
break;
InputStream dis = is;
if (major >= 2) {
int variant = readEx();
if (variant == 1) { //compressed
while (true) {
int chunkLen = readUI16();
if (chunkLen == 0) {
break;
}
byte[] chunk = new byte[chunkLen];
is.readFully(chunk);
baos.write(chunk);
}
byte[] chunk = new byte[chunkLen];
is.readFully(chunk);
baos.write(chunk);
dis = new InflaterInputStream(new ByteArrayInputStream(baos.toByteArray()));
}
}
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
InflaterInputStream iis = new InflaterInputStream(new ByteArrayInputStream(baos.toByteArray()));
byte[] buf = new byte[4096];
int cnt;
while ((cnt = iis.read(buf)) > 0) {
while ((cnt = dis.read(buf)) > 0) {
baos2.write(buf, 0, cnt);
}
@@ -96,6 +118,10 @@ public class LosslessImageBinDataReader {
g = (int) Math.floor(g * 256f / a);
b = (int) Math.floor(b * 256f / a);
}
if (!hasAlpha) {
a = 0xFF;
}
int rgba = r + (g << 8) + (b << 16) + (a << 24);
img.setRGB(x, y, rgba);
@@ -121,9 +147,9 @@ public class LosslessImageBinDataReader {
}
public static void main(String[] args) throws IOException {
File f = new File("in.bin");
File f = new File("c:\\Dropbox\\Programovani\\JavaSE\\FlaComDoc\\out\\media\\M 6 1776533974.dat");
LosslessImageBinDataReader r = new LosslessImageBinDataReader(new FileInputStream(f));
BufferedImage i = r.readImage();
ImageIO.write(i, "PNG", new File("out.png"));
ImageIO.write(i, "PNG", new File("c:\\Dropbox\\Programovani\\JavaSE\\FlaComDoc\\out\\media\\out.png"));
}
}

View File

@@ -181,7 +181,7 @@ public class Graph {
if (heads != null) {
return;
}
heads = makeGraph(code, new ArrayList<>(), exceptions);
heads = makeGraph(code, new ArrayList<>(), exceptions);
}
/**
@@ -270,16 +270,16 @@ public class Graph {
return;
}
allParts.add(part);
Queue<GraphPart> q = new ArrayDeque<>();
q.offer(part);
while (!q.isEmpty()) {
part = q.poll();
part = q.poll();
for (GraphPart p : part.nextParts) {
if (!allParts.contains(p)) {
allParts.add(p);
q.offer(p);
}
}
}
}
}
@@ -2545,7 +2545,7 @@ public class Graph {
}
for (Loop l : loops) {
if (l.phase == 2) {
if (l.phase != 1) {
continue;
}
if (l.loopContinue == part) {
@@ -4086,7 +4086,7 @@ public class Graph {
GraphTargetItem pushedValue = pi.value;
GraphTargetItem rightSide = ((PushItem) filteredOnTrue.get(filteredOnTrue.size() - 1)).value;
GraphTargetItem prevExpr = stack.pop();
GraphTargetItem leftSide = expr.getNotCoercedNoDup();
GraphTargetItem leftSide = expr.getNotCoercedNoDup();
GraphTargetItem invertedLeftSide = leftSide;
if (invertedLeftSide instanceof NotItem) {
invertedLeftSide = ((NotItem) invertedLeftSide).value;
@@ -4096,7 +4096,7 @@ public class Graph {
prevExpr = prevExpr.getThroughDuplicate();
boolean hideEmptyTrueFalse = true;
boolean hideEmptyTrueFalse = true;
if (leftSide instanceof DuplicateItem
|| leftSide.getNotCoerced() == prevExpr) {
@@ -4205,7 +4205,7 @@ public class Graph {
loopStack.push(new LoopLocalData(part, isLoop, loopItem, li, currentLoop, loopTypeFound, doWhileCandidate, precontinueCommands, stopPart, stopPartKind, ret, sPreLoop));
}
parent = part;
part = nextOnePart;
part = nextOnePart;
nextOnePart = null;
isLoop = false;
li = null;
@@ -4223,7 +4223,7 @@ public class Graph {
}
break;
}
while (!loopStack.isEmpty()) {
LoopLocalData loopLocalData = loopStack.pop();
if (loopLocalData.isLoop && loopLocalData.loopItem != null && loopLocalData.currentLoop != null) {
@@ -4624,10 +4624,9 @@ public class Graph {
HashMap<Integer, List<Integer>> refs = code.visitCode(alternateEntries);
List<GraphPart> gret = new ArrayList<>();
boolean[] visited = new boolean[code.size()];
Queue<MakeGraphWindow> q = new ArrayDeque<>();
//ret.add(makeGraph(null, new GraphPath(), code, startIp, 0, allBlocks, refs, visited));
q.offer(new MakeGraphWindow(null, new GraphPath(), startIp, 0));
for (int pos : alternateEntries) {
@@ -4635,14 +4634,14 @@ public class Graph {
e1.path = new GraphPath("e");
//ret.add(makeGraph(e1, new GraphPath("e"), code, pos, pos, allBlocks, refs, visited));
q.offer(new MakeGraphWindow(e1, new GraphPath("e"), pos, pos));
}
}
loopq: while (!q.isEmpty()) {
loopq:
while (!q.isEmpty()) {
if (CancellableWorker.isInterrupted()) {
throw new InterruptedException();
}
MakeGraphWindow window = q.poll();
MakeGraphWindow window = q.poll();
GraphPart parent = window.parent;
GraphPath path = window.path;
int startIp = window.startIp;
@@ -4768,14 +4767,14 @@ public class Graph {
part.nextParts.add(gp);
allBlocks.add(part);
}
}
}
}
gret.add(searchPart(startIp, allBlocks));
gret.add(searchPart(startIp, allBlocks));
for (int pos : alternateEntries) {
gret.add(searchPart(pos, allBlocks));
}
if (Configuration.autoDeobfuscate.get()) {
flattenJumps(gret, allBlocks);
}
@@ -4783,8 +4782,8 @@ public class Graph {
return gret;
}
private class MakeGraphWindow {
GraphPart parent;
GraphPath path;
int startIp;
@@ -4795,10 +4794,9 @@ public class Graph {
this.path = path;
this.startIp = startIp;
this.lastIp = lastIp;
}
}
}
/**
* Converts list of TreeItems to string.
*

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2010-2026 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.helpers;
import com.jpexs.decompiler.flash.configuration.SwfSpecificConfiguration;
import com.jpexs.decompiler.flash.configuration.SwfSpecificCustomConfiguration;
import com.jpexs.decompiler.flash.configuration.enums.GridSnapAccuracy;
import com.jpexs.decompiler.flash.configuration.enums.GuidesSnapAccuracy;
import com.jpexs.decompiler.flash.exporters.modes.ExeExportMode;
import com.jpexs.decompiler.flash.importers.TextImportResizeTextBoundsMode;
import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
/**
* ObjectInputStream that limits deserialized classes to safe ones.
* @author JPEXS
*/
public class AllowedObjectInputStream extends ObjectInputStream {
private static final List<String> ALLOWED_CLASSES = Arrays.asList(
"java.lang.String",
"java.lang.Number",
"java.lang.Short",
"java.lang.Long",
"java.lang.Integer",
"java.lang.Double",
"java.lang.Float",
"java.lang.Boolean",
"java.util.ArrayList",
"java.util.LinkedList",
"java.util.HashSet",
"java.util.LinkedHashSet",
"java.util.HashMap",
"java.util.LinkedHashMap",
SwfSpecificConfiguration.class.getName(),
SwfSpecificCustomConfiguration.class.getName(),
ExeExportMode.class.getName(),
TextImportResizeTextBoundsMode.class.getName(),
Color.class.getName(),
GridSnapAccuracy.class.getName(),
GuidesSnapAccuracy.class.getName(),
Calendar.class.getName()
);
public AllowedObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
String className = desc.getName();
if (!ALLOWED_CLASSES.contains(className)) {
throw new InvalidClassException(
"Unauthorized deserialization attempt", className);
}
return super.resolveClass(desc);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2010-2026 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.helpers;
/**
*
* @author JPEXS
*/
public class IdentityKey<T> {
private final T value;
public IdentityKey(T value) {
this.value = value;
}
@Override
public int hashCode() {
return System.identityHashCode(value);
}
@Override
public boolean equals(Object obj) {
return obj instanceof IdentityKey
&& ((IdentityKey<?>) obj).value == value;
}
public T get() {
return value;
}
}